From 8daa83a594a2e98f39d764422bfbdbc62c9efd44 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 19:20:00 +0200 Subject: Adding upstream version 2:4.20.0+dfsg. Signed-off-by: Daniel Baumann --- lib/ldb/tests/guidindexpackv1.ldb | Bin 0 -> 65536 bytes lib/ldb/tests/init.ldif | 41 + lib/ldb/tests/init_slapd.sh | 40 + lib/ldb/tests/kill_slapd.sh | 12 + lib/ldb/tests/ldapi_url.sh | 11 + lib/ldb/tests/ldb_filter_attrs_in_place_test.c | 940 ++++ lib/ldb/tests/ldb_filter_attrs_test.c | 989 ++++ .../tests/ldb_key_value_sub_txn_mdb_test.valgrind | 97 + lib/ldb/tests/ldb_key_value_sub_txn_test.c | 844 ++++ lib/ldb/tests/ldb_key_value_test.c | 388 ++ lib/ldb/tests/ldb_kv_ops_test.c | 1819 ++++++++ lib/ldb/tests/ldb_kv_ops_test.valgrind | 97 + lib/ldb/tests/ldb_lmdb_free_list_test.c | 661 +++ lib/ldb/tests/ldb_lmdb_size_test.c | 249 ++ lib/ldb/tests/ldb_lmdb_test.c | 590 +++ lib/ldb/tests/ldb_match_test.c | 313 ++ lib/ldb/tests/ldb_match_test.valgrind | 16 + lib/ldb/tests/ldb_mod_op_test.c | 4724 ++++++++++++++++++++ lib/ldb/tests/ldb_msg.c | 380 ++ lib/ldb/tests/ldb_no_lmdb_test.c | 159 + lib/ldb/tests/ldb_parse_test.c | 172 + lib/ldb/tests/ldb_tdb_test.c | 389 ++ lib/ldb/tests/lldb_ldap.c | 105 + lib/ldb/tests/photo.ldif | 5 + lib/ldb/tests/python/api.py | 3858 ++++++++++++++++ lib/ldb/tests/python/crash.py | 45 + lib/ldb/tests/python/index.py | 1454 ++++++ lib/ldb/tests/python/repack.py | 204 + lib/ldb/tests/samba4.png | Bin 0 -> 6239 bytes lib/ldb/tests/sample_module.c | 122 + lib/ldb/tests/schema-tests/schema-add-test.ldif | 66 + lib/ldb/tests/schema-tests/schema-mod-test-1.ldif | 5 + lib/ldb/tests/schema-tests/schema-mod-test-2.ldif | 5 + lib/ldb/tests/schema-tests/schema-mod-test-3.ldif | 5 + lib/ldb/tests/schema-tests/schema-mod-test-4.ldif | 5 + lib/ldb/tests/schema-tests/schema-mod-test-5.ldif | 5 + lib/ldb/tests/schema-tests/schema.ldif | 100 + lib/ldb/tests/slapd.conf | 26 + lib/ldb/tests/start_slapd.sh | 14 + lib/ldb/tests/test-attribs.ldif | 6 + lib/ldb/tests/test-config.ldif | 67 + lib/ldb/tests/test-controls.sh | 43 + lib/ldb/tests/test-default-config.ldif | 17 + lib/ldb/tests/test-dup-2.ldif | 6 + lib/ldb/tests/test-dup.ldif | 13 + lib/ldb/tests/test-extended.sh | 69 + lib/ldb/tests/test-generic.sh | 158 + lib/ldb/tests/test-index.ldif | 7 + lib/ldb/tests/test-ldap.sh | 54 + lib/ldb/tests/test-modify-modrdn.ldif | 12 + lib/ldb/tests/test-modify-unmet-2.ldif | 7 + lib/ldb/tests/test-modify-unmet.ldif | 15 + lib/ldb/tests/test-modify.ldif | 23 + lib/ldb/tests/test-schema.sh | 33 + lib/ldb/tests/test-soloading.sh | 31 + lib/ldb/tests/test-sqlite3.sh | 23 + lib/ldb/tests/test-tdb-features.sh | 179 + lib/ldb/tests/test-tdb-subunit.sh | 7 + lib/ldb/tests/test-tdb.sh | 38 + lib/ldb/tests/test-wildcard.ldif | 5 + lib/ldb/tests/test-wrong_attributes.ldif | 3 + lib/ldb/tests/test.ldif | 440 ++ lib/ldb/tests/test_ldb_dn.c | 232 + lib/ldb/tests/test_ldb_qsort.c | 65 + lib/ldb/tests/testdata.txt | 8 + lib/ldb/tests/testsearch.txt | 5 + 66 files changed, 20521 insertions(+) create mode 100644 lib/ldb/tests/guidindexpackv1.ldb create mode 100644 lib/ldb/tests/init.ldif create mode 100755 lib/ldb/tests/init_slapd.sh create mode 100755 lib/ldb/tests/kill_slapd.sh create mode 100755 lib/ldb/tests/ldapi_url.sh create mode 100644 lib/ldb/tests/ldb_filter_attrs_in_place_test.c create mode 100644 lib/ldb/tests/ldb_filter_attrs_test.c create mode 100644 lib/ldb/tests/ldb_key_value_sub_txn_mdb_test.valgrind create mode 100644 lib/ldb/tests/ldb_key_value_sub_txn_test.c create mode 100644 lib/ldb/tests/ldb_key_value_test.c create mode 100644 lib/ldb/tests/ldb_kv_ops_test.c create mode 100644 lib/ldb/tests/ldb_kv_ops_test.valgrind create mode 100644 lib/ldb/tests/ldb_lmdb_free_list_test.c create mode 100644 lib/ldb/tests/ldb_lmdb_size_test.c create mode 100644 lib/ldb/tests/ldb_lmdb_test.c create mode 100644 lib/ldb/tests/ldb_match_test.c create mode 100644 lib/ldb/tests/ldb_match_test.valgrind create mode 100644 lib/ldb/tests/ldb_mod_op_test.c create mode 100644 lib/ldb/tests/ldb_msg.c create mode 100644 lib/ldb/tests/ldb_no_lmdb_test.c create mode 100644 lib/ldb/tests/ldb_parse_test.c create mode 100644 lib/ldb/tests/ldb_tdb_test.c create mode 100644 lib/ldb/tests/lldb_ldap.c create mode 100644 lib/ldb/tests/photo.ldif create mode 100755 lib/ldb/tests/python/api.py create mode 100644 lib/ldb/tests/python/crash.py create mode 100755 lib/ldb/tests/python/index.py create mode 100644 lib/ldb/tests/python/repack.py create mode 100644 lib/ldb/tests/samba4.png create mode 100644 lib/ldb/tests/sample_module.c create mode 100644 lib/ldb/tests/schema-tests/schema-add-test.ldif create mode 100644 lib/ldb/tests/schema-tests/schema-mod-test-1.ldif create mode 100644 lib/ldb/tests/schema-tests/schema-mod-test-2.ldif create mode 100644 lib/ldb/tests/schema-tests/schema-mod-test-3.ldif create mode 100644 lib/ldb/tests/schema-tests/schema-mod-test-4.ldif create mode 100644 lib/ldb/tests/schema-tests/schema-mod-test-5.ldif create mode 100644 lib/ldb/tests/schema-tests/schema.ldif create mode 100644 lib/ldb/tests/slapd.conf create mode 100755 lib/ldb/tests/start_slapd.sh create mode 100644 lib/ldb/tests/test-attribs.ldif create mode 100644 lib/ldb/tests/test-config.ldif create mode 100755 lib/ldb/tests/test-controls.sh create mode 100644 lib/ldb/tests/test-default-config.ldif create mode 100644 lib/ldb/tests/test-dup-2.ldif create mode 100644 lib/ldb/tests/test-dup.ldif create mode 100755 lib/ldb/tests/test-extended.sh create mode 100755 lib/ldb/tests/test-generic.sh create mode 100644 lib/ldb/tests/test-index.ldif create mode 100755 lib/ldb/tests/test-ldap.sh create mode 100644 lib/ldb/tests/test-modify-modrdn.ldif create mode 100644 lib/ldb/tests/test-modify-unmet-2.ldif create mode 100644 lib/ldb/tests/test-modify-unmet.ldif create mode 100644 lib/ldb/tests/test-modify.ldif create mode 100755 lib/ldb/tests/test-schema.sh create mode 100755 lib/ldb/tests/test-soloading.sh create mode 100755 lib/ldb/tests/test-sqlite3.sh create mode 100644 lib/ldb/tests/test-tdb-features.sh create mode 100755 lib/ldb/tests/test-tdb-subunit.sh create mode 100755 lib/ldb/tests/test-tdb.sh create mode 100644 lib/ldb/tests/test-wildcard.ldif create mode 100644 lib/ldb/tests/test-wrong_attributes.ldif create mode 100644 lib/ldb/tests/test.ldif create mode 100644 lib/ldb/tests/test_ldb_dn.c create mode 100644 lib/ldb/tests/test_ldb_qsort.c create mode 100644 lib/ldb/tests/testdata.txt create mode 100644 lib/ldb/tests/testsearch.txt (limited to 'lib/ldb/tests') diff --git a/lib/ldb/tests/guidindexpackv1.ldb b/lib/ldb/tests/guidindexpackv1.ldb new file mode 100644 index 0000000..96d783c Binary files /dev/null and b/lib/ldb/tests/guidindexpackv1.ldb differ diff --git a/lib/ldb/tests/init.ldif b/lib/ldb/tests/init.ldif new file mode 100644 index 0000000..97b4561 --- /dev/null +++ b/lib/ldb/tests/init.ldif @@ -0,0 +1,41 @@ +dn: o=University of Michigan,c=TEST +objectclass: organization +objectclass: domainRelatedObject +l: Ann Arbor, Michigan +st: Michigan +o: University of Michigan +o: UMICH +o: UM +o: U-M +o: U of M +description: The University of Michigan at Ann Arbor +postaladdress: University of Michigan $ 535 W. William St. $ Ann Arbor, MI 481 + 09 $ US +telephonenumber: +1 313 764-1817 +associateddomain: example.com + +# there was an empty "seeAlso" here + +dn: ou=People,o=University of Michigan,c=TEST +objectclass: organizationalUnit +objectclass: extensibleObject +ou: People +uidNumber: 0 +gidNumber: 0 + +dn: ou=Ldb Test,ou=People,o=University of Michigan,c=TEST +objectclass: organizationalUnit +objectclass: extensibleObject +ou: People +ou: Ldb Test +uidNumber: 0 +gidNumber: 0 + +dn: ou=LdbTspace,ou=People,o=University of Michigan,c=TEST +objectclass: organizationalUnit +objectclass: extensibleObject +ou: People +ou: LdbTspace +description: test white space removal in comparisons +uidNumber: 0 +gidNumber: 0 diff --git a/lib/ldb/tests/init_slapd.sh b/lib/ldb/tests/init_slapd.sh new file mode 100755 index 0000000..4629cf1 --- /dev/null +++ b/lib/ldb/tests/init_slapd.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +if [ -z "$LDBDIR" ]; then + LDBDIR=$(dirname $0)/.. + export LDBDIR +fi + +rm -rf tests/tmp/db +mkdir -p tests/tmp/db + +if [ -f tests/tmp/slapd.pid ]; then + kill $(cat tests/tmp/slapd.pid) + sleep 1 +fi +if [ -f tests/tmp/slapd.pid ]; then + kill -9 $(cat tests/tmp/slapd.pid) + rm -f tests/tmp/slapd.pid +fi + +# we don't consider a slapadd failure as a test suite failure, as it +# has nothing to do with ldb + +MODCONF=tests/tmp/modules.conf +rm -f $MODCONF +touch $MODCONF || exit 1 + +slaptest -u -f $LDBDIR/tests/slapd.conf >/dev/null 2>&1 || { + echo "enabling sladp modules" + cat >$MODCONF < 2019 + * + * 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 . + * + */ + +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + */ +#include +#include +#include +#include +#include +#include + +#include "../include/ldb.h" +#include "../include/ldb_module.h" + +struct ldbtest_ctx { + struct tevent_context *ev; + struct ldb_context *ldb; +}; + +/* + * NOTE WELL: + * + * This test checks the current behaviour of the function, however + * this is not in a public ABI and many of the tested behaviours are + * not ideal. If the behaviour is deliberately improved, this test + * should be updated without worry to the new better behaviour. + * + * In particular the test is particularly to ensure the current + * behaviour is memory-safe. + */ + +static int setup(void **state) +{ + struct ldbtest_ctx *test_ctx; + + 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); + + *state = test_ctx; + return 0; +} + +static int teardown(void **state) +{ + talloc_free(*state); + return 0; +} + +static void msg_add_dn(struct ldb_message *msg) +{ + const char *dn_attr = "distinguishedName"; + char *dn = NULL; + int ret; + + assert_null(ldb_msg_find_element(msg, dn_attr)); + + assert_non_null(msg->dn); + dn = ldb_dn_alloc_linearized(msg, msg->dn); + assert_non_null(dn); + + /* + * The message's elements must be talloc allocated to call + * ldb_msg_add_steal_string(). + */ + msg->elements = talloc_memdup(msg, + msg->elements, + msg->num_elements * sizeof(msg->elements[0])); + assert_non_null(msg->elements); + + ret = ldb_msg_add_steal_string(msg, dn_attr, dn); + assert_int_equal(ret, LDB_SUCCESS); +} + +/* + * Test against a record with only one attribute, matching the one in + * the list + */ +static void test_filter_attrs_in_place_one_attr_matched(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *msg = ldb_msg_new(ctx); + + const char *attrs[] = {"foo", NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 1; + msg->elements = &element_1; + + assert_non_null(msg->dn); + msg_add_dn(msg); + + ret = ldb_filter_attrs_in_place(msg, attrs); + assert_int_equal(ret, LDB_SUCCESS); + + assert_non_null(msg->dn); + assert_int_equal(msg->num_elements, 1); + assert_string_equal(msg->elements[0].name, "foo"); + assert_int_equal(msg->elements[0].num_values, 1); + assert_int_equal(msg->elements[0].values[0].length, + strlen(value)); + assert_memory_equal(msg->elements[0].values[0].data, + value, strlen(value)); +} + +/* + * Test against a record with only one attribute, matching the one of + * the multiple attributes in the list + */ +static void test_filter_attrs_in_place_one_attr_matched_of_many(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *msg = ldb_msg_new(ctx); + + const char *attrs[] = {"foo", "bar", "baz", NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 1; + msg->elements = &element_1; + + assert_non_null(msg->dn); + msg_add_dn(msg); + + ret = ldb_filter_attrs_in_place(msg, attrs); + assert_int_equal(ret, LDB_SUCCESS); + + assert_non_null(msg->dn); + assert_int_equal(msg->num_elements, 1); + assert_string_equal(msg->elements[0].name, "foo"); + assert_int_equal(msg->elements[0].num_values, 1); + assert_int_equal(msg->elements[0].values[0].length, + strlen(value)); + assert_memory_equal(msg->elements[0].values[0].data, + value, strlen(value)); +} + +/* + * Test against a record with only one attribute, matching both + * attributes in the list + */ +static void test_filter_attrs_in_place_two_attr_matched_attrs(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *msg = ldb_msg_new(ctx); + + /* deliberately the other order */ + const char *attrs[] = {"bar", "foo", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + + /* foo and bar are the other order to in attrs */ + struct ldb_message_element elements[] = { + { + .name = "foo", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 2; + msg->elements = elements; + + assert_non_null(msg->dn); + msg_add_dn(msg); + + ret = ldb_filter_attrs_in_place(msg, attrs); + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(msg->num_elements, 2); + + assert_non_null(msg->dn); + + /* Assert that DB order is preserved */ + assert_string_equal(msg->elements[0].name, "foo"); + assert_int_equal(msg->elements[0].num_values, 1); + assert_int_equal(msg->elements[0].values[0].length, + strlen(value1)); + assert_memory_equal(msg->elements[0].values[0].data, + value1, strlen(value1)); + assert_string_equal(msg->elements[1].name, "bar"); + assert_int_equal(msg->elements[1].num_values, 1); + assert_int_equal(msg->elements[1].values[0].length, + strlen(value2)); + assert_memory_equal(msg->elements[1].values[0].data, + value2, strlen(value2)); +} + +/* + * Test against a record with two attributes, only of which is in + * the list + */ +static void test_filter_attrs_in_place_two_attr_matched_one_attr(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *msg = ldb_msg_new(ctx); + + const char *attrs[] = {"bar", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + + struct ldb_message_element elements[] = { + { + .name = "foo", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 2; + msg->elements = elements; + + assert_non_null(msg->dn); + msg_add_dn(msg); + + ret = ldb_filter_attrs_in_place(msg, attrs); + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(msg->num_elements, 1); + + assert_non_null(msg->dn); + + /* Assert that DB order is preserved */ + assert_string_equal(msg->elements[0].name, "bar"); + assert_int_equal(msg->elements[0].num_values, 1); + assert_int_equal(msg->elements[0].values[0].length, + strlen(value2)); + assert_memory_equal(msg->elements[0].values[0].data, + value2, strlen(value2)); +} + +/* + * Test against a record with two attributes, both matching the one + * specified attribute in the list (a corrupt record) + */ +static void test_filter_attrs_in_place_two_dup_attr_matched_one_attr(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *msg = ldb_msg_new(ctx); + + const char *attrs[] = {"bar", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + + struct ldb_message_element elements[] = { + { + .name = "bar", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 2; + msg->elements = elements; + + assert_non_null(msg->dn); + msg_add_dn(msg); + + ret = ldb_filter_attrs_in_place(msg, attrs); + + /* Both elements match the filter */ + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(msg->num_elements, 2); + + assert_non_null(msg->dn); + + /* Assert that DB order is preserved */ + assert_string_equal(msg->elements[0].name, "bar"); + assert_int_equal(msg->elements[0].num_values, 1); + assert_int_equal(msg->elements[0].values[0].length, + strlen(value1)); + assert_memory_equal(msg->elements[0].values[0].data, + value1, strlen(value1)); + + assert_string_equal(msg->elements[1].name, "bar"); + assert_int_equal(msg->elements[1].num_values, 1); + assert_int_equal(msg->elements[1].values[0].length, + strlen(value2)); + assert_memory_equal(msg->elements[1].values[0].data, + value2, strlen(value2)); +} + +/* + * Test against a record with two attributes, both matching the one + * specified attribute in the list (a corrupt record) + */ +static void test_filter_attrs_in_place_two_dup_attr_matched_dup(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *msg = ldb_msg_new(ctx); + + const char *attrs[] = {"bar", "bar", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + + struct ldb_message_element elements[] = { + { + .name = "bar", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 2; + msg->elements = elements; + + assert_non_null(msg->dn); + msg_add_dn(msg); + + ret = ldb_filter_attrs_in_place(msg, attrs); + + /* This does not fail the pidgenhole test */ + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(msg->num_elements, 2); + + /* Assert that DB order is preserved */ + assert_string_equal(msg->elements[0].name, "bar"); + assert_int_equal(msg->elements[0].num_values, 1); + assert_int_equal(msg->elements[0].values[0].length, + strlen(value1)); + assert_memory_equal(msg->elements[0].values[0].data, + value1, strlen(value1)); + assert_string_equal(msg->elements[1].name, "bar"); + assert_int_equal(msg->elements[1].num_values, 1); + assert_int_equal(msg->elements[1].values[0].length, + strlen(value2)); + assert_memory_equal(msg->elements[1].values[0].data, + value2, strlen(value2)); +} + +/* + * Test against a record with two attributes, both matching one of the + * specified attributes in the list (a corrupt record) + */ +static void test_filter_attrs_in_place_two_dup_attr_matched_one_of_two(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *msg = ldb_msg_new(ctx); + + const char *attrs[] = {"bar", "foo", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + + struct ldb_message_element elements[] = { + { + .name = "bar", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 2; + msg->elements = elements; + + assert_non_null(msg->dn); + msg_add_dn(msg); + + ret = ldb_filter_attrs_in_place(msg, attrs); + + /* This does not fail the pidgenhole test */ + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(msg->num_elements, 2); + + /* Assert that DB order is preserved */ + assert_string_equal(msg->elements[0].name, "bar"); + assert_int_equal(msg->elements[0].num_values, 1); + assert_int_equal(msg->elements[0].values[0].length, + strlen(value1)); + assert_memory_equal(msg->elements[0].values[0].data, + value1, strlen(value1)); + assert_string_equal(msg->elements[1].name, "bar"); + assert_int_equal(msg->elements[1].num_values, 1); + assert_int_equal(msg->elements[1].values[0].length, + strlen(value2)); + assert_memory_equal(msg->elements[1].values[0].data, + value2, strlen(value2)); +} + +/* + * Test against a record with two attributes against * (but not the + * other named attribute) (a corrupt record) + */ +static void test_filter_attrs_in_place_two_dup_attr_matched_star(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *msg = ldb_msg_new(ctx); + + const char *attrs[] = {"*", "foo", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + + struct ldb_message_element elements[] = { + { + .name = "bar", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 2; + msg->elements = elements; + + assert_non_null(msg->dn); + msg_add_dn(msg); + + ret = ldb_filter_attrs_in_place(msg, attrs); + + /* This does not fail the pidgenhole test */ + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(msg->num_elements, 3); + + /* Assert that DB order is preserved */ + assert_string_equal(msg->elements[0].name, "bar"); + assert_int_equal(msg->elements[0].num_values, 1); + assert_int_equal(msg->elements[0].values[0].length, + strlen(value1)); + assert_memory_equal(msg->elements[0].values[0].data, + value1, strlen(value1)); + assert_string_equal(msg->elements[1].name, "bar"); + assert_int_equal(msg->elements[1].num_values, 1); + assert_int_equal(msg->elements[1].values[0].length, + strlen(value2)); + assert_memory_equal(msg->elements[1].values[0].data, + value2, strlen(value2)); + + assert_non_null(msg->dn); + assert_string_equal(ldb_msg_find_attr_as_string(msg, + "distinguishedName", + NULL), + ldb_dn_get_linearized(msg->dn)); +} + +/* + * Test against a record with only one attribute, matching the * in + * the list + */ +static void test_filter_attrs_in_place_one_attr_matched_star(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *msg = ldb_msg_new(ctx); + + const char *attrs[] = {"*", NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 1; + msg->elements = &element_1; + + assert_non_null(msg->dn); + msg_add_dn(msg); + + ret = ldb_filter_attrs_in_place(msg, attrs); + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(msg->num_elements, 2); + + assert_non_null(msg->dn); + assert_string_equal(ldb_msg_find_attr_as_string(msg, + "distinguishedName", + NULL), + ldb_dn_get_linearized(msg->dn)); + assert_string_equal(ldb_msg_find_attr_as_string(msg, + "foo", + NULL), + value); +} + +/* + * Test against a record with two attributes, matching the * in + * the list + */ +static void test_filter_attrs_in_place_two_attr_matched_star(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *msg = ldb_msg_new(ctx); + + const char *attrs[] = {"*", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + struct ldb_message_element elements[] = { + { + .name = "foo", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 2; + msg->elements = elements; + + assert_non_null(msg->dn); + msg_add_dn(msg); + + ret = ldb_filter_attrs_in_place(msg, attrs); + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(msg->num_elements, 3); + + assert_non_null(msg->dn); + assert_string_equal(ldb_msg_find_attr_as_string(msg, + "distinguishedName", + NULL), + ldb_dn_get_linearized(msg->dn)); + assert_string_equal(ldb_msg_find_attr_as_string(msg, + "foo", + NULL), + value1); + assert_string_equal(ldb_msg_find_attr_as_string(msg, + "bar", + NULL), + value2); +} + +/* + * Test against a record with only one attribute, matching the * in + * the list, but without the DN being pre-filled. Succeeds, but the + * distinguishedName is not added. + */ +static void test_filter_attrs_in_place_one_attr_matched_star_no_dn(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *msg = ldb_msg_new(ctx); + + const char *attrs[] = {"*", NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + + assert_non_null(msg); + msg->dn = NULL; + msg->num_elements = 1; + msg->elements = &element_1; + + assert_null(msg->dn); + + ret = ldb_filter_attrs_in_place(msg, attrs); + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(msg->num_elements, 1); +} + +/* + * Test against a record with only one attribute, matching the * in + * the list plus requsesting distinguishedName + */ +static void test_filter_attrs_in_place_one_attr_matched_star_dn(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *msg = ldb_msg_new(ctx); + + const char *attrs[] = {"*", "distinguishedName", NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 1; + msg->elements = &element_1; + + assert_non_null(msg->dn); + msg_add_dn(msg); + + ret = ldb_filter_attrs_in_place(msg, attrs); + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(msg->num_elements, 2); + + assert_non_null(msg->dn); + + assert_string_equal(ldb_msg_find_attr_as_string(msg, + "distinguishedName", + NULL), + ldb_dn_get_linearized(msg->dn)); + assert_string_equal(ldb_msg_find_attr_as_string(msg, + "foo", + NULL), + value); +} + +/* + * Test against a record with only one attribute, but returning + * distinguishedName from the list (only) + */ +static void test_filter_attrs_in_place_one_attr_matched_dn(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *msg = ldb_msg_new(ctx); + + const char *attrs[] = {"distinguishedName", NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 1; + msg->elements = &element_1; + + assert_non_null(msg->dn); + msg_add_dn(msg); + + ret = ldb_filter_attrs_in_place(msg, attrs); + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(msg->num_elements, 1); + + assert_non_null(msg->dn); + assert_string_equal(msg->elements[0].name, "distinguishedName"); + assert_int_equal(msg->elements[0].num_values, 1); + assert_string_equal((char *)msg->elements[0].values[0].data, + ldb_dn_get_linearized(msg->dn)); +} + +/* + * Test against a record with only one attribute, not matching the + * empty attribute list + */ +static void test_filter_attrs_in_place_one_attr_empty_list(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *msg = ldb_msg_new(ctx); + + const char *attrs[] = {NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 1; + msg->elements = &element_1; + + assert_non_null(msg->dn); + msg_add_dn(msg); + + ret = ldb_filter_attrs_in_place(msg, attrs); + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(msg->num_elements, 0); + assert_non_null(msg->dn); +} + +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + test_filter_attrs_in_place_one_attr_matched, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_in_place_one_attr_matched_of_many, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_in_place_two_attr_matched_attrs, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_in_place_two_attr_matched_one_attr, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_in_place_two_dup_attr_matched_one_attr, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_in_place_two_dup_attr_matched_dup, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_in_place_two_dup_attr_matched_one_of_two, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_in_place_two_dup_attr_matched_star, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_in_place_one_attr_matched_star, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_in_place_two_attr_matched_star, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_in_place_one_attr_matched_star_no_dn, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_in_place_one_attr_matched_star_dn, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_in_place_one_attr_matched_dn, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_in_place_one_attr_empty_list, + setup, + teardown), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/ldb/tests/ldb_filter_attrs_test.c b/lib/ldb/tests/ldb_filter_attrs_test.c new file mode 100644 index 0000000..291350a --- /dev/null +++ b/lib/ldb/tests/ldb_filter_attrs_test.c @@ -0,0 +1,989 @@ +/* + * Tests exercising the ldb_filter_attrs(). + * + * + * Copyright (C) Catalyst.NET Ltd 2017 + * Copyright (C) Andrew Bartlett 2019 + * + * 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 . + * + */ + +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + */ +#include +#include +#include +#include +#include +#include + +#include "../include/ldb.h" +#include "../include/ldb_module.h" + +struct ldbtest_ctx { + struct tevent_context *ev; + struct ldb_context *ldb; +}; + +/* + * NOTE WELL: + * + * This test checks the current behaviour of the function, however + * this is not in a public ABI and many of the tested behaviours are + * not ideal. If the behaviour is deliberately improved, this test + * should be updated without worry to the new better behaviour. + * + * In particular the test is particularly to ensure the current + * behaviour is memory-safe. + */ + +static int setup(void **state) +{ + struct ldbtest_ctx *test_ctx; + + 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); + + *state = test_ctx; + return 0; +} + +static int teardown(void **state) +{ + talloc_free(*state); + return 0; +} + + +/* + * Test against a record with only one attribute, matching the one in + * the list + */ +static void test_filter_attrs_one_attr_matched(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"foo", NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 1, + .elements = &element_1, + }; + + assert_non_null(in.dn); + + ret = ldb_filter_attrs(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(filtered_msg); + + /* + * assert the ldb_filter_attrs does not read or modify + * filtered_msg.dn in this case + */ + assert_null(filtered_msg->dn); + assert_int_equal(filtered_msg->num_elements, 1); + assert_string_equal(filtered_msg->elements[0].name, "foo"); + assert_int_equal(filtered_msg->elements[0].num_values, 1); + assert_int_equal(filtered_msg->elements[0].values[0].length, + strlen(value)); + assert_memory_equal(filtered_msg->elements[0].values[0].data, + value, strlen(value)); +} + +/* + * Test against a record with only one attribute, matching the one of + * the multiple attributes in the list + */ +static void test_filter_attrs_one_attr_matched_of_many(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"foo", "bar", "baz", NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 1, + .elements = &element_1, + }; + + assert_non_null(in.dn); + + ret = ldb_filter_attrs(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(filtered_msg); + + /* + * assert the ldb_filter_attrs does not read or modify + * filtered_msg.dn in this case + */ + assert_null(filtered_msg->dn); + assert_int_equal(filtered_msg->num_elements, 1); + assert_string_equal(filtered_msg->elements[0].name, "foo"); + assert_int_equal(filtered_msg->elements[0].num_values, 1); + assert_int_equal(filtered_msg->elements[0].values[0].length, + strlen(value)); + assert_memory_equal(filtered_msg->elements[0].values[0].data, + value, strlen(value)); +} + +/* + * Test against a record with only one attribute, matching both + * attributes in the list + */ +static void test_filter_attrs_two_attr_matched_attrs(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + /* deliberately the other order */ + const char *attrs[] = {"bar", "foo", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + + /* foo and bar are the other order to in attrs */ + struct ldb_message_element elements[] = { + { + .name = "foo", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 2, + .elements = elements, + }; + + assert_non_null(in.dn); + + ret = ldb_filter_attrs(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(filtered_msg); + assert_int_equal(filtered_msg->num_elements, 2); + + /* + * assert the ldb_filter_attrs does not read or modify + * filtered_msg.dn in this case + */ + assert_null(filtered_msg->dn); + + /* Assert that DB order is preserved */ + assert_string_equal(filtered_msg->elements[0].name, "foo"); + assert_int_equal(filtered_msg->elements[0].num_values, 1); + assert_int_equal(filtered_msg->elements[0].values[0].length, + strlen(value1)); + assert_memory_equal(filtered_msg->elements[0].values[0].data, + value1, strlen(value1)); + assert_string_equal(filtered_msg->elements[1].name, "bar"); + assert_int_equal(filtered_msg->elements[1].num_values, 1); + assert_int_equal(filtered_msg->elements[1].values[0].length, + strlen(value2)); + assert_memory_equal(filtered_msg->elements[1].values[0].data, + value2, strlen(value2)); +} + +/* + * Test against a record with two attributes, only of which is in + * the list + */ +static void test_filter_attrs_two_attr_matched_one_attr(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + /* deliberately the other order */ + const char *attrs[] = {"bar", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + + /* foo and bar are the other order to in attrs */ + struct ldb_message_element elements[] = { + { + .name = "foo", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 2, + .elements = elements, + }; + + assert_non_null(in.dn); + + ret = ldb_filter_attrs(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(filtered_msg); + assert_int_equal(filtered_msg->num_elements, 1); + + /* + * assert the ldb_filter_attrs does not read or modify + * filtered_msg.dn in this case + */ + assert_null(filtered_msg->dn); + + /* Assert that DB order is preserved */ + assert_string_equal(filtered_msg->elements[0].name, "bar"); + assert_int_equal(filtered_msg->elements[0].num_values, 1); + assert_int_equal(filtered_msg->elements[0].values[0].length, + strlen(value2)); + assert_memory_equal(filtered_msg->elements[0].values[0].data, + value2, strlen(value2)); +} + +/* + * Test against a record with two attributes, both matching the one + * specified attribute in the list (a corrupt record) + */ +static void test_filter_attrs_two_dup_attr_matched_one_attr(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + /* deliberately the other order */ + const char *attrs[] = {"bar", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + + /* foo and bar are the other order to in attrs */ + struct ldb_message_element elements[] = { + { + .name = "bar", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 2, + .elements = elements, + }; + + assert_non_null(in.dn); + + ret = ldb_filter_attrs(ctx->ldb, + &in, + attrs, + filtered_msg); + + /* This should fail the pidgenhole test */ + assert_int_equal(ret, -1); + assert_null(filtered_msg->elements); +} + +/* + * Test against a record with two attributes, both matching the one + * specified attribute in the list (a corrupt record) + */ +static void test_filter_attrs_two_dup_attr_matched_dup(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"bar", "bar", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + + /* foo and bar are the other order to in attrs */ + struct ldb_message_element elements[] = { + { + .name = "bar", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 2, + .elements = elements, + }; + + assert_non_null(in.dn); + + ret = ldb_filter_attrs(ctx->ldb, + &in, + attrs, + filtered_msg); + + /* This does not fail the pidgenhole test */ + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(filtered_msg->num_elements, 2); + + /* Assert that DB order is preserved */ + assert_string_equal(filtered_msg->elements[0].name, "bar"); + assert_int_equal(filtered_msg->elements[0].num_values, 1); + assert_int_equal(filtered_msg->elements[0].values[0].length, + strlen(value1)); + assert_memory_equal(filtered_msg->elements[0].values[0].data, + value1, strlen(value1)); + assert_string_equal(filtered_msg->elements[1].name, "bar"); + assert_int_equal(filtered_msg->elements[1].num_values, 1); + assert_int_equal(filtered_msg->elements[1].values[0].length, + strlen(value2)); + assert_memory_equal(filtered_msg->elements[1].values[0].data, + value2, strlen(value2)); +} + +/* + * Test against a record with two attributes, both matching one of the + * specified attributes in the list (a corrupt record) + */ +static void test_filter_attrs_two_dup_attr_matched_one_of_two(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"bar", "foo", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + + /* foo and bar are the other order to in attrs */ + struct ldb_message_element elements[] = { + { + .name = "bar", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 2, + .elements = elements, + }; + + assert_non_null(in.dn); + + ret = ldb_filter_attrs(ctx->ldb, + &in, + attrs, + filtered_msg); + + /* This does not fail the pidgenhole test */ + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(filtered_msg->num_elements, 2); + + /* Assert that DB order is preserved */ + assert_string_equal(filtered_msg->elements[0].name, "bar"); + assert_int_equal(filtered_msg->elements[0].num_values, 1); + assert_int_equal(filtered_msg->elements[0].values[0].length, + strlen(value1)); + assert_memory_equal(filtered_msg->elements[0].values[0].data, + value1, strlen(value1)); + assert_string_equal(filtered_msg->elements[1].name, "bar"); + assert_int_equal(filtered_msg->elements[1].num_values, 1); + assert_int_equal(filtered_msg->elements[1].values[0].length, + strlen(value2)); + assert_memory_equal(filtered_msg->elements[1].values[0].data, + value2, strlen(value2)); +} + +/* + * Test against a record with two attributes against * (but not the + * other named attribute) (a corrupt record) + */ +static void test_filter_attrs_two_dup_attr_matched_star(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"*", "foo", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + + /* foo and bar are the other order to in attrs */ + struct ldb_message_element elements[] = { + { + .name = "bar", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 2, + .elements = elements, + }; + + assert_non_null(in.dn); + + /* Needed as * implies distinguishedName */ + filtered_msg->dn = in.dn; + + ret = ldb_filter_attrs(ctx->ldb, + &in, + attrs, + filtered_msg); + + /* This does not fail the pidgenhole test */ + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(filtered_msg->num_elements, 3); + + /* Assert that DB order is preserved */ + assert_string_equal(filtered_msg->elements[0].name, "bar"); + assert_int_equal(filtered_msg->elements[0].num_values, 1); + assert_int_equal(filtered_msg->elements[0].values[0].length, + strlen(value1)); + assert_memory_equal(filtered_msg->elements[0].values[0].data, + value1, strlen(value1)); + assert_string_equal(filtered_msg->elements[1].name, "bar"); + assert_int_equal(filtered_msg->elements[1].num_values, 1); + assert_int_equal(filtered_msg->elements[1].values[0].length, + strlen(value2)); + assert_memory_equal(filtered_msg->elements[1].values[0].data, + value2, strlen(value2)); + /* + * assert the ldb_filter_attrs does not modify filtered_msg.dn + * in this case + */ + assert_ptr_equal(filtered_msg->dn, in.dn); + assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + "distinguishedName", + NULL), + ldb_dn_get_linearized(in.dn)); +} + +/* + * Test against a record with only one attribute, matching the * in + * the list + */ +static void test_filter_attrs_one_attr_matched_star(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"*", NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 1, + .elements = &element_1, + }; + + assert_non_null(in.dn); + + /* Needed as * implies distinguishedName */ + filtered_msg->dn = in.dn; + + ret = ldb_filter_attrs(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(filtered_msg); + assert_int_equal(filtered_msg->num_elements, 2); + + /* + * assert the ldb_filter_attrs does not modify filtered_msg.dn + * in this case + */ + assert_ptr_equal(filtered_msg->dn, in.dn); + assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + "distinguishedName", + NULL), + ldb_dn_get_linearized(in.dn)); + assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + "foo", + NULL), + (const char *)value); +} + +/* + * Test against a record with two attributes, matching the * in + * the list + */ +static void test_filter_attrs_two_attr_matched_star(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"*", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + struct ldb_message_element elements[] = { + { + .name = "foo", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 2, + .elements = elements, + }; + + assert_non_null(in.dn); + + /* Needed as * implies distinguishedName */ + filtered_msg->dn = in.dn; + + ret = ldb_filter_attrs(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(filtered_msg); + assert_int_equal(filtered_msg->num_elements, 3); + + /* + * assert the ldb_filter_attrs does not modify filtered_msg.dn + * in this case + */ + assert_ptr_equal(filtered_msg->dn, in.dn); + assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + "distinguishedName", + NULL), + ldb_dn_get_linearized(in.dn)); + assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + "foo", + NULL), + (const char *)value1); + assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + "bar", + NULL), + (const char *)value2); +} + +/* + * Test against a record with only one attribute, matching the * in + * the list, but without the DN being pre-filled. Fails due to need + * to construct the distinguishedName + */ +static void test_filter_attrs_one_attr_matched_star_no_dn(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"*", NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 1, + .elements = &element_1, + }; + + assert_non_null(in.dn); + + ret = ldb_filter_attrs(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, -1); + assert_null(filtered_msg->elements); +} + +/* + * Test against a record with only one attribute, matching the * in + * the list plus requsesting distinguishedName + */ +static void test_filter_attrs_one_attr_matched_star_dn(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"*", "distinguishedName", NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 1, + .elements = &element_1, + }; + + assert_non_null(in.dn); + + /* Needed for distinguishedName */ + filtered_msg->dn = in.dn; + + ret = ldb_filter_attrs(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(filtered_msg); + assert_int_equal(filtered_msg->num_elements, 2); + + /* show that ldb_filter_attrs does not modify in.dn */ + assert_ptr_equal(filtered_msg->dn, in.dn); + + assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + "distinguishedName", + NULL), + ldb_dn_get_linearized(in.dn)); + assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + "foo", + NULL), + (const char *)value); +} + +/* + * Test against a record with only one attribute, but returning + * distinguishedName from the list (only) + */ +static void test_filter_attrs_one_attr_matched_dn(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"distinguishedName", NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 1, + .elements = &element_1, + }; + + assert_non_null(in.dn); + + /* Needed for distinguishedName */ + filtered_msg->dn = in.dn; + + ret = ldb_filter_attrs(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(filtered_msg); + assert_int_equal(filtered_msg->num_elements, 1); + + /* show that ldb_filter_attrs does not modify in.dn */ + assert_ptr_equal(filtered_msg->dn, in.dn); + assert_string_equal(filtered_msg->elements[0].name, "distinguishedName"); + assert_int_equal(filtered_msg->elements[0].num_values, 1); + assert_string_equal((const char *)filtered_msg->elements[0].values[0].data, + ldb_dn_get_linearized(in.dn)); +} + +/* + * Test against a record with only one attribute, not matching the + * empty attribute list + */ +static void test_filter_attrs_one_attr_empty_list(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 1, + .elements = &element_1, + }; + + assert_non_null(in.dn); + + ret = ldb_filter_attrs(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(filtered_msg); + assert_int_equal(filtered_msg->num_elements, 0); + assert_null(filtered_msg->dn); + assert_null(filtered_msg->elements); +} + +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + test_filter_attrs_one_attr_matched, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_one_attr_matched_of_many, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_two_attr_matched_attrs, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_two_attr_matched_one_attr, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_two_dup_attr_matched_one_attr, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_two_dup_attr_matched_dup, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_two_dup_attr_matched_one_of_two, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_two_dup_attr_matched_star, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_one_attr_matched_star, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_two_attr_matched_star, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_one_attr_matched_star_no_dn, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_one_attr_matched_star_dn, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_one_attr_matched_dn, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_one_attr_empty_list, + setup, + teardown), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/ldb/tests/ldb_key_value_sub_txn_mdb_test.valgrind b/lib/ldb/tests/ldb_key_value_sub_txn_mdb_test.valgrind new file mode 100644 index 0000000..1747076 --- /dev/null +++ b/lib/ldb/tests/ldb_key_value_sub_txn_mdb_test.valgrind @@ -0,0 +1,97 @@ +{ + Memory allocated by setup + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:setup +} +{ + Memory allocated by setup + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + ... + fun:setup +} +{ + Memory allocated by ldb_init + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:ldb_init +} +{ + Memory allocated by ldb_init + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + ... + fun:ldb_init +} +{ + Memory allocated by noconn_setup + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:noconn_setup +} +{ + Memory allocated by parse, which allocates on the NULL context + Memcheck:Leak + match-leak-kinds: all + fun:malloc + ... + fun:parse +} +{ + Memory allocated by tdb_parse_data + Memcheck:Leak + match-leak-kinds: all + fun:malloc + ... + fun:tdb_parse_data +} +{ + Memory allocated by ldb_connect + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:ldb_connect +} +{ + Memory allocated by ldb_connect + Memcheck:Leak + match-leak-kinds: possible + fun:calloc + ... + fun:ldb_connect +} +{ + Memory allocated by ldb_kv_cache_load + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:ldb_kv_cache_load +} +{ + Memory allocated by ldb_kv_index_load + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:ldb_kv_index_load +} +{ + Memory allocated by ldb_asprintf_errstring + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:ldb_asprintf_errstring +} + diff --git a/lib/ldb/tests/ldb_key_value_sub_txn_test.c b/lib/ldb/tests/ldb_key_value_sub_txn_test.c new file mode 100644 index 0000000..1eafd2d --- /dev/null +++ b/lib/ldb/tests/ldb_key_value_sub_txn_test.c @@ -0,0 +1,844 @@ +/* + * Tests exercising the ldb key value operations. + * + * Copyright (C) Andrew Bartlett 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 . + * + */ + +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + * + */ + +/* + */ +#include +#include +#include +#include +#include + +#include +#define NO_FAILURE INT_MAX +#define FAILURE_LDB_ERR LDB_ERR_OTHER + +/* + * To test failure in ldb_kv_add, ldb_kv_delete, ldb_kv_modify and ldb_kv_rename + * we use the following global variables and macros to trigger a failure in + * the ldb_kv__internal functions. This allows testing of the sub + * transaction commits and roll backs in those operations. + * + * NOTE: Not all back ends support nested/sub transactions + */ +int cmocka_unit_test_fail_add_internal_after = NO_FAILURE; +#define CMOCKA_UNIT_TEST_ADD_INTERNAL_FAIL \ + {\ + cmocka_unit_test_fail_add_internal_after--;\ + if (cmocka_unit_test_fail_add_internal_after <= 0) {\ + assert_int_equal(LDB_SUCCESS, ret);\ + ret = FAILURE_LDB_ERR;\ + }\ + }\ + +int cmocka_unit_test_fail_delete_internal_after = NO_FAILURE; +#define CMOCKA_UNIT_TEST_DELETE_INTERNAL_FAIL \ + {\ + cmocka_unit_test_fail_delete_internal_after--;\ + if (cmocka_unit_test_fail_delete_internal_after <= 0) {\ + assert_int_equal(LDB_SUCCESS, ret);\ + ret = FAILURE_LDB_ERR;\ + }\ + }\ + +int cmocka_unit_test_fail_rename_internal_after = NO_FAILURE; +#define CMOCKA_UNIT_TEST_RENAME_INTERNAL_FAIL \ + {\ + cmocka_unit_test_fail_rename_internal_after--;\ + if (cmocka_unit_test_fail_rename_internal_after <= 0) {\ + assert_int_equal(LDB_SUCCESS, ret);\ + ret = FAILURE_LDB_ERR;\ + }\ + }\ + +int cmocka_unit_test_fail_modify_internal_after = NO_FAILURE; +#define CMOCKA_UNIT_TEST_MODIFY_INTERNAL_FAIL \ + {\ + cmocka_unit_test_fail_modify_internal_after--;\ + if (cmocka_unit_test_fail_modify_internal_after <= 0) {\ + assert_int_equal(LDB_SUCCESS, ret);\ + ret = FAILURE_LDB_ERR;\ + }\ + }\ + +#include "ldb_key_value/ldb_kv.c" + + +#define DEFAULT_BE "tdb" + +#ifndef TEST_BE +#define TEST_BE DEFAULT_BE +#endif /* TEST_BE */ + +#define NUM_RECS 1024 + + +struct test_ctx { + struct tevent_context *ev; + struct ldb_context *ldb; + + const char *dbfile; + const char *lockfile; /* lockfile is separate */ + + const char *dbpath; +}; + +/* + * Remove the database files + */ +static void unlink_old_db(struct test_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(); + } +} + +/* + * Test setup + */ +static int noconn_setup(void **state) +{ + struct test_ctx *test_ctx; + cmocka_unit_test_fail_add_internal_after = NO_FAILURE; + cmocka_unit_test_fail_delete_internal_after = NO_FAILURE; + cmocka_unit_test_fail_rename_internal_after = NO_FAILURE; + cmocka_unit_test_fail_modify_internal_after = NO_FAILURE; + + test_ctx = talloc_zero(NULL, struct test_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->dbfile = talloc_strdup(test_ctx, "kvopstest.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); + *state = test_ctx; + return 0; +} + +/* + * Test teardown + */ +static int noconn_teardown(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + + unlink_old_db(test_ctx); + talloc_free(test_ctx); + return 0; +} + +/* + * Test setup + */ +static int setup(void **state) +{ + struct test_ctx *test_ctx; + int ret; + struct ldb_ldif *ldif; + const char *index_ldif = \ + "dn: @INDEXLIST\n" + "@IDXGUID: objectUUID\n" + "@IDX_DN_GUID: GUID\n" + "\n"; + + noconn_setup((void **) &test_ctx); + + ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, 0); + + while ((ldif = ldb_ldif_read_string(test_ctx->ldb, &index_ldif))) { + ret = ldb_add(test_ctx->ldb, ldif->msg); + assert_int_equal(ret, LDB_SUCCESS); + } + *state = test_ctx; + return 0; +} + +/* + * Test teardown + */ +static int teardown(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + noconn_teardown((void **) &test_ctx); + return 0; +} + +/* + * Build an ldb_kv_context that can be passed to the ldb_kv operation under test + */ +static struct ldb_kv_context* build_ldb_kv_context( + TALLOC_CTX *ctx, + struct ldb_module *module, + struct ldb_request *req) +{ + struct ldb_kv_context *ldb_kv_ctx = NULL; + + ldb_kv_ctx = talloc_zero(ctx, struct ldb_kv_context); + assert_non_null(ldb_kv_ctx); + + ldb_kv_ctx->module = module; + ldb_kv_ctx->req = req; + + return ldb_kv_ctx; +} + +/* + * Build an add request + */ +static struct ldb_request *build_add_request( + TALLOC_CTX *ctx, + struct ldb_context *ldb, + const char* dc, + const char* uuid, + const char* cn) +{ + int ret; + struct ldb_message *msg; + struct ldb_request *req; + + msg = ldb_msg_new(ctx); + assert_non_null(msg); + + msg->dn = ldb_dn_new_fmt(msg, ldb, "dc=%s", dc); + assert_non_null(msg->dn); + + ret = ldb_msg_add_string(msg, "cn", cn); + assert_int_equal(ret, 0); + + ret = ldb_msg_add_string(msg, "objectUUID", uuid); + assert_int_equal(ret, 0); + + ret = ldb_msg_sanity_check(ldb, msg); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_build_add_req( + &req, ldb, ldb, msg, NULL, NULL, ldb_op_default_callback, NULL); + assert_int_equal(ret, LDB_SUCCESS); + return req; +} + +/* + * Build a delete request + */ +static struct ldb_request *build_delete_request( + TALLOC_CTX *ctx, + struct ldb_context *ldb, + const char* dc) +{ + int ret = LDB_SUCCESS; + struct ldb_dn *dn = NULL; + struct ldb_request *req = NULL; + + dn = ldb_dn_new_fmt(ctx, ldb, "dc=%s", dc); + assert_non_null(dn); + + ret = ldb_build_del_req( + &req, ldb, ctx, dn, NULL, NULL, ldb_op_default_callback, NULL); + assert_int_equal(ret, LDB_SUCCESS); + return req; +} + +/* + * Build a rename request + */ +static struct ldb_request *build_rename_request( + TALLOC_CTX *ctx, + struct ldb_context *ldb, + const char* old_dc, + const char* new_dc) +{ + int ret = LDB_SUCCESS; + struct ldb_dn *old_dn = NULL; + struct ldb_dn *new_dn = NULL; + struct ldb_request *req = NULL; + + old_dn = ldb_dn_new_fmt(ctx, ldb, "dc=%s", old_dc); + assert_non_null(old_dn); + + new_dn = ldb_dn_new_fmt(ctx, ldb, "dc=%s", new_dc); + assert_non_null(new_dn); + + ret = ldb_build_rename_req( + &req, + ldb, + ctx, + old_dn, + new_dn, + NULL, + NULL, + ldb_op_default_callback, + NULL); + assert_int_equal(ret, LDB_SUCCESS); + return req; +} + +/* + * Build a ldb modify request + */ +static struct ldb_request *build_modify_request( + TALLOC_CTX *ctx, + struct ldb_context *ldb, + const char* dc, + const char* cn) +{ + int ret; + struct ldb_message *msg; + struct ldb_request *req; + + msg = ldb_msg_new(ctx); + assert_non_null(msg); + + msg->dn = ldb_dn_new_fmt(msg, ldb, "dc=%s", dc); + assert_non_null(msg->dn); + + ret = ldb_msg_add_empty(msg, "cn", LDB_FLAG_MOD_REPLACE, NULL); + assert_int_equal(ret, LDB_SUCCESS); + ret = ldb_msg_add_string(msg, "cn", cn); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_msg_sanity_check(ldb, msg); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_build_mod_req( + &req, ldb, ldb, msg, NULL, NULL, ldb_op_default_callback, NULL); + assert_int_equal(ret, LDB_SUCCESS); + return req; +} + +/* + * Delete a record from the database + */ +static void delete_record( + TALLOC_CTX *ctx, + struct ldb_context *ldb, + const char* dc) +{ + struct ldb_kv_context *ldb_kv_ctx = NULL; + struct ldb_dn *basedn = NULL; + struct ldb_result *result = NULL; + struct ldb_request *req = NULL; + int ret = LDB_SUCCESS; + + req = build_delete_request(ctx, ldb, dc); + ldb_kv_ctx = build_ldb_kv_context(ctx, ldb->modules, req); + + ret = ldb_transaction_start(ldb); + assert_int_equal(ret, LDB_SUCCESS); + + cmocka_unit_test_fail_delete_internal_after = NO_FAILURE; + cmocka_unit_test_fail_modify_internal_after = NO_FAILURE; + ret = ldb_kv_delete(ldb_kv_ctx); + assert_int_equal(ret, LDB_SUCCESS); + TALLOC_FREE(ldb_kv_ctx); + TALLOC_FREE(req); + + ret = ldb_transaction_commit(ldb); + assert_int_equal(ret, LDB_SUCCESS); + + /* + * Ensure that the record was actually deleted. + */ + basedn = ldb_dn_new_fmt(ctx, ldb, "dc=%s", dc); + assert_non_null(basedn); + + /* + * DN search, indexed + */ + ret = ldb_search(ldb, ctx, &result, basedn, LDB_SCOPE_BASE, NULL, NULL); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(result); + assert_int_equal(result->count, 0); + TALLOC_FREE(basedn); + TALLOC_FREE(result); +} + +/* + * Add a record to the database + */ +static void add_record( + TALLOC_CTX *ctx, + struct ldb_context *ldb, + const char* dc, + const char* uuid, + const char* cn) +{ + + struct ldb_request *req = NULL; + int ret = LDB_SUCCESS; + struct ldb_kv_context *ldb_kv_ctx = NULL; + struct ldb_dn *basedn = NULL; + struct ldb_result *result = NULL; + + req = build_add_request(ctx, ldb, dc, uuid, cn); + + ldb_req_set_location(req, "add_record"); + + assert_int_equal(ret, LDB_SUCCESS); + + + ldb_kv_ctx = build_ldb_kv_context(ctx, ldb->modules, req); + cmocka_unit_test_fail_add_internal_after = NO_FAILURE; + cmocka_unit_test_fail_modify_internal_after = NO_FAILURE; + + ret = ldb_transaction_start(ldb); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_kv_add(ldb_kv_ctx); + assert_int_equal(ret, LDB_SUCCESS); + TALLOC_FREE(ldb_kv_ctx); + TALLOC_FREE(req); + + ret = ldb_transaction_commit(ldb); + assert_int_equal(ret, LDB_SUCCESS); + + /* + * Ensure that the record was actually written. + */ + basedn = ldb_dn_new_fmt(ctx, ldb, "dc=%s", dc); + assert_non_null(basedn); + + /* + * DN search, indexed + */ + ret = ldb_search(ldb, ctx, &result, basedn, LDB_SCOPE_BASE, NULL, NULL); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(result); + assert_int_equal(result->count, 1); + TALLOC_FREE(result); + + + /* + * CN search unindexed + */ + ret = ldb_search( + ldb, + ctx, + &result, + basedn, + LDB_SCOPE_SUBTREE, + NULL, + "(cn=%s)", + cn); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(result); + assert_int_equal(result->count, 1); + TALLOC_FREE(result); + TALLOC_FREE(basedn); +} + +/* + * Test that a failed add operation does not change the database. + */ +static void test_add_failure(void **state) +{ + int ret = LDB_SUCCESS; + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + struct ldb_request *req = NULL; + struct ldb_dn *basedn = NULL; + struct ldb_result *result = NULL; + struct ldb_kv_context *ldb_kv_ctx = NULL; + + TALLOC_CTX *tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + req = build_add_request( + tmp_ctx, + test_ctx->ldb, + "test_add_failure", + "0123456789abcdef", + "test_add_failure_value"); + + ldb_req_set_location(req, "test_add_failure"); + + ldb_kv_ctx = build_ldb_kv_context(tmp_ctx, test_ctx->ldb->modules, req); + cmocka_unit_test_fail_add_internal_after = 1; + + ret = ldb_transaction_start(test_ctx->ldb); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_kv_add(ldb_kv_ctx); + + assert_int_equal(ret, FAILURE_LDB_ERR); + TALLOC_FREE(ldb_kv_ctx); + TALLOC_FREE(req); + + + /* + * a search for "cn=test_add_failure_value" should fail + * as the transaction containing the operation should have been + * rolled back leaving the database consistent + * + * This should be an un-indexed search so the index caches won't be + * used. + */ + basedn = ldb_dn_new_fmt( + tmp_ctx, + test_ctx->ldb, + "dc=%s", + "test_add_failure"); + assert_non_null(basedn); + + ret = ldb_search( + test_ctx->ldb, tmp_ctx, + &result, + basedn, + LDB_SCOPE_SUBTREE, + NULL, + "(cn=%s)", + "test_add_failure_value"); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(result); + assert_int_equal(result->count, 0); + TALLOC_FREE(basedn); + TALLOC_FREE(result); + + ldb_transaction_cancel(test_ctx->ldb); + TALLOC_FREE(tmp_ctx); +} + + +/* + * Test that a failed delete operation does not modify the database. + */ +static void test_delete_failure(void **state) +{ + int ret = LDB_SUCCESS; + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + struct ldb_request *req = NULL; + struct ldb_dn *basedn = NULL; + struct ldb_result *result = NULL; + struct ldb_kv_context *ldb_kv_ctx = NULL; + + TALLOC_CTX *tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + add_record( + tmp_ctx, + test_ctx->ldb, + "test_delete_failure", + "0123456789abcded", + "test_delete_failure_value"); + + req = build_delete_request( + tmp_ctx, + test_ctx->ldb, + "test_delete_failure"); + + ldb_kv_ctx = build_ldb_kv_context(tmp_ctx, test_ctx->ldb->modules, req); + cmocka_unit_test_fail_delete_internal_after = 1; + + ret = ldb_transaction_start(test_ctx->ldb); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_kv_delete(ldb_kv_ctx); + assert_int_equal(ret, FAILURE_LDB_ERR); + TALLOC_FREE(ldb_kv_ctx); + TALLOC_FREE(req); + + /* + * a search for "cn=test_add_failure_value" should succeed + * as the transaction containing the operation should have been + * rolled back leaving the database consistent + * + * This should be an un-indexed search so the index caches won't be + * used. + */ + basedn = ldb_dn_new_fmt( + tmp_ctx, + test_ctx->ldb, + "dc=%s", + "test_delete_failure"); + assert_non_null(basedn); + + ret = ldb_search( + test_ctx->ldb, tmp_ctx, + &result, + basedn, + LDB_SCOPE_SUBTREE, + NULL, + "(cn=%s)", + "test_delete_failure_value"); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(result); + assert_int_equal(result->count, 1); + TALLOC_FREE(basedn); + TALLOC_FREE(result); + + + ldb_transaction_cancel(test_ctx->ldb); + delete_record( + tmp_ctx, + test_ctx->ldb, + "test_delete_failure"); + TALLOC_FREE(tmp_ctx); +} + +/* + * Test that a failed rename operation dies not change the database + */ +static void test_rename_failure(void **state) +{ + int ret = LDB_SUCCESS; + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + struct ldb_request *req = NULL; + struct ldb_dn *basedn = NULL; + struct ldb_result *result = NULL; + struct ldb_kv_context *ldb_kv_ctx = NULL; + + TALLOC_CTX *tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + add_record( + tmp_ctx, + test_ctx->ldb, + "test_rename_failure", + "0123456789abcdec", + "test_rename_failure_value"); + + req = build_rename_request( + tmp_ctx, + test_ctx->ldb, + "test_rename_failure", + "test_rename_failure_renamed"); + + ldb_kv_ctx = build_ldb_kv_context(tmp_ctx, test_ctx->ldb->modules, req); + cmocka_unit_test_fail_rename_internal_after = 1; + + ret = ldb_transaction_start(test_ctx->ldb); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_kv_rename(ldb_kv_ctx); + assert_int_equal(ret, FAILURE_LDB_ERR); + TALLOC_FREE(ldb_kv_ctx); + TALLOC_FREE(req); + + /* + * The original record should be present + */ + basedn = ldb_dn_new_fmt( + tmp_ctx, + test_ctx->ldb, + "dc=%s", + "test_rename_failure"); + assert_non_null(basedn); + + ret = ldb_search( + test_ctx->ldb, tmp_ctx, + &result, + basedn, + LDB_SCOPE_SUBTREE, + NULL, + "(cn=%s)", + "test_rename_failure_value"); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(result); + assert_int_equal(result->count, 1); + TALLOC_FREE(basedn); + TALLOC_FREE(result); + + /* + * And the renamed record should not be present + */ + basedn = ldb_dn_new_fmt( + tmp_ctx, + test_ctx->ldb, + "dc=%s", + "test_rename_failure_renamed"); + assert_non_null(basedn); + + ret = ldb_search( + test_ctx->ldb, tmp_ctx, + &result, + basedn, + LDB_SCOPE_SUBTREE, + NULL, + "(cn=%s)", + "test_rename_failure_value"); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(result); + assert_int_equal(result->count, 0); + TALLOC_FREE(basedn); + TALLOC_FREE(result); + + ldb_transaction_cancel(test_ctx->ldb); + delete_record( + tmp_ctx, + test_ctx->ldb, + "test_rename_failure"); + TALLOC_FREE(tmp_ctx); +} + +/* + * Test that a failed modification operation does not change the database + */ +static void test_modify_failure(void **state) +{ + int ret = LDB_SUCCESS; + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + struct ldb_request *req = NULL; + struct ldb_dn *basedn = NULL; + struct ldb_result *result = NULL; + struct ldb_kv_context *ldb_kv_ctx = NULL; + + TALLOC_CTX *tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + add_record( + tmp_ctx, + test_ctx->ldb, + "test_modify_failure", + "0123456789abcdeb", + "test_modify_failure_value"); + + req = build_modify_request( + tmp_ctx, + test_ctx->ldb, + "test_modify_failure", + "test_modify_failure_value_modified"); + + ldb_kv_ctx = build_ldb_kv_context(tmp_ctx, test_ctx->ldb->modules, req); + cmocka_unit_test_fail_modify_internal_after = 2; + + ret = ldb_transaction_start(test_ctx->ldb); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_kv_modify(ldb_kv_ctx); + assert_int_equal(ret, FAILURE_LDB_ERR); + TALLOC_FREE(ldb_kv_ctx); + TALLOC_FREE(req); + + + /* + * The original value should be present + */ + basedn = ldb_dn_new_fmt( + tmp_ctx, + test_ctx->ldb, + "dc=%s", + "test_modify_failure"); + assert_non_null(basedn); + + ret = ldb_search( + test_ctx->ldb, tmp_ctx, + &result, + basedn, + LDB_SCOPE_SUBTREE, + NULL, + "(cn=%s)", + "test_modify_failure_value"); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(result); + assert_int_equal(result->count, 1); + TALLOC_FREE(result); + + /* + * And the modified record should not be present + */ + ret = ldb_search( + test_ctx->ldb, tmp_ctx, + &result, + basedn, + LDB_SCOPE_SUBTREE, + NULL, + "(cn=%s)", + "test_modify_failure_value_modified"); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(result); + assert_int_equal(result->count, 0); + TALLOC_FREE(basedn); + TALLOC_FREE(result); + + ldb_transaction_cancel(test_ctx->ldb); + delete_record( + tmp_ctx, + test_ctx->ldb, + "test_modify_failure"); + TALLOC_FREE(tmp_ctx); +} + +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + test_add_failure, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_delete_failure, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_rename_failure, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_modify_failure, + setup, + teardown), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/ldb/tests/ldb_key_value_test.c b/lib/ldb/tests/ldb_key_value_test.c new file mode 100644 index 0000000..97f717b --- /dev/null +++ b/lib/ldb/tests/ldb_key_value_test.c @@ -0,0 +1,388 @@ +/* + * Tests exercising the ldb key value operations. + * + * Copyright (C) Andrew Bartlett 2019 + * + * 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 . + * + */ + +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + * + */ + +/* + * + * Tests for the ldb key value layer + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "ldb_key_value/ldb_kv.c" +#include "ldb_key_value/ldb_kv_index.c" +#include "ldb_key_value/ldb_kv_search.c" + +#define DEFAULT_BE "tdb" + +#ifndef TEST_BE +#define TEST_BE DEFAULT_BE +#endif /* TEST_BE */ + +#define NUM_RECS 1024 +int ldb_kv_cache_reload(struct ldb_module *module) { + return LDB_SUCCESS; +} +int ldb_kv_cache_load(struct ldb_module *module) { + return LDB_SUCCESS; +} +int ldb_kv_check_at_attributes_values(const struct ldb_val *value) { + return LDB_SUCCESS; +} +int ldb_kv_increase_sequence_number(struct ldb_module *module) { + return LDB_SUCCESS; +} + +struct test_ctx { uint8_t dummy; }; + +static int setup(void **state) +{ + struct test_ctx *test_ctx; + + test_ctx = talloc_zero(NULL, struct test_ctx); + *state = test_ctx; + return 0; +} + +static int teardown(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + + talloc_free(test_ctx); + return 0; +} + +/* + * Test that the index cache is opened by ldb_kv_index_transaction_start + * and correctly initialised with the passed index cache size. + */ +static void test_index_cache_init(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort( + *state, + struct test_ctx); + struct ldb_module *module = NULL; + struct ldb_kv_private *ldb_kv = NULL; + int ret = LDB_SUCCESS; + + module = talloc_zero(test_ctx, struct ldb_module); + ldb_kv = talloc_zero(test_ctx, struct ldb_kv_private); + ldb_module_set_private(module, ldb_kv); + + ret = ldb_kv_index_transaction_start(module, 191); + assert_int_equal(LDB_SUCCESS, ret); + + assert_non_null(ldb_kv->idxptr); + assert_non_null(ldb_kv->idxptr->itdb); + assert_int_equal(191, tdb_hash_size(ldb_kv->idxptr->itdb)); + + TALLOC_FREE(ldb_kv); + TALLOC_FREE(module); +} + +static int mock_begin_write(struct ldb_kv_private* ldb_kv) { + return LDB_SUCCESS; +} +static int mock_abort_write(struct ldb_kv_private* ldb_kv) { + return LDB_SUCCESS; +} + +/* + * Test that the index cache is set to the default cache size at the start of + * a transaction. + */ +static void test_default_index_cache_size(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort( + *state, + struct test_ctx); + struct ldb_module *module = NULL; + struct ldb_kv_private *ldb_kv = NULL; + int ret = LDB_SUCCESS; + const struct kv_db_ops ops = { + .begin_write = mock_begin_write, + .abort_write = mock_abort_write + }; + + module = talloc_zero(test_ctx, struct ldb_module); + ldb_kv = talloc_zero(test_ctx, struct ldb_kv_private); + ldb_kv->pid = getpid(); + ldb_kv->kv_ops = &ops; + ldb_kv->index_transaction_cache_size = DEFAULT_INDEX_CACHE_SIZE; + ldb_module_set_private(module, ldb_kv); + + ret = ldb_kv_start_trans(module); + assert_int_equal(LDB_SUCCESS, ret); + + assert_int_equal( + DEFAULT_INDEX_CACHE_SIZE, + tdb_hash_size(ldb_kv->idxptr->itdb)); + + ret = ldb_kv_del_trans(module); + assert_int_equal(LDB_SUCCESS, ret); + + TALLOC_FREE(ldb_kv); + TALLOC_FREE(module); +} + +static int db_size = 0; +static size_t mock_get_size(struct ldb_kv_private *ldb_kv) { + return db_size; +} + +static int mock_iterate( + struct ldb_kv_private *ldb_kv, + ldb_kv_traverse_fn fn, + void *ctx) { + return 1; +} + +/* + * Test that the index cache is correctly sized by the re_index call + */ +static void test_reindex_cache_size(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort( + *state, + struct test_ctx); + struct ldb_module *module = NULL; + struct ldb_kv_private *ldb_kv = NULL; + int ret = LDB_SUCCESS; + const struct kv_db_ops ops = { + .iterate = mock_iterate, + .get_size = mock_get_size, + }; + + module = talloc_zero(test_ctx, struct ldb_module); + ldb_kv = talloc_zero(test_ctx, struct ldb_kv_private); + ldb_kv->kv_ops = &ops; + ldb_module_set_private(module, ldb_kv); + + /* + * Use a value less than the DEFAULT_INDEX_CACHE_SIZE + * Should get the DEFAULT_INDEX_CACHE_SIZE + */ + db_size = DEFAULT_INDEX_CACHE_SIZE - 1; + ret = ldb_kv_reindex(module); + assert_int_equal(LDB_SUCCESS, ret); + + assert_int_equal( + DEFAULT_INDEX_CACHE_SIZE, + tdb_hash_size(ldb_kv->idxptr->itdb)); + + /* + * Use a value greater than the DEFAULT_INDEX_CACHE_SIZE + * Should get the value specified. + */ + db_size = DEFAULT_INDEX_CACHE_SIZE + 1; + ret = ldb_kv_reindex(module); + assert_int_equal(LDB_SUCCESS, ret); + + assert_int_equal(db_size, tdb_hash_size(ldb_kv->idxptr->itdb)); + + TALLOC_FREE(ldb_kv); + TALLOC_FREE(module); +} + +/* + * Test that ldb_kv_init_store sets the default index transaction cache size + * if the option is not supplied. + */ +static void test_init_store_default_index_cache_size(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort( + *state, + struct test_ctx); + struct ldb_module *module = NULL; + struct ldb_kv_private *ldb_kv = NULL; + struct ldb_context *ldb = NULL; + int ret = LDB_SUCCESS; + + module = talloc_zero(test_ctx, struct ldb_module); + ldb = talloc_zero(test_ctx, struct ldb_context); + ldb_kv = talloc_zero(test_ctx, struct ldb_kv_private); + + ret = ldb_kv_init_store(ldb_kv, "test", ldb, NULL, &module); + assert_int_equal(LDB_SUCCESS, ret); + + assert_int_equal( + DEFAULT_INDEX_CACHE_SIZE, + ldb_kv->index_transaction_cache_size); + + TALLOC_FREE(ldb_kv); + TALLOC_FREE(module); + TALLOC_FREE(ldb); +} + +/* + * Test that ldb_kv_init_store sets the index transaction cache size + * to the value specified in the option. + */ +static void test_init_store_set_index_cache_size(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort( + *state, + struct test_ctx); + struct ldb_module *module = NULL; + struct ldb_kv_private *ldb_kv = NULL; + struct ldb_context *ldb = NULL; + const char *options[] = {"transaction_index_cache_size:1900", NULL}; + int ret = LDB_SUCCESS; + + module = talloc_zero(test_ctx, struct ldb_module); + ldb = talloc_zero(test_ctx, struct ldb_context); + ldb_kv = talloc_zero(test_ctx, struct ldb_kv_private); + + ret = ldb_kv_init_store(ldb_kv, "test", ldb, options, &module); + assert_int_equal(LDB_SUCCESS, ret); + + assert_int_equal( 1900, ldb_kv->index_transaction_cache_size); + + TALLOC_FREE(ldb_kv); + TALLOC_FREE(module); + TALLOC_FREE(ldb); +} + +/* + * Test that ldb_kv_init_store sets the default index transaction cache size + * if the value specified in the option is not a number. + */ +static void test_init_store_set_index_cache_size_non_numeric(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort( + *state, + struct test_ctx); + struct ldb_module *module = NULL; + struct ldb_kv_private *ldb_kv = NULL; + struct ldb_context *ldb = NULL; + const char *options[] = {"transaction_index_cache_size:fred", NULL}; + int ret = LDB_SUCCESS; + + module = talloc_zero(test_ctx, struct ldb_module); + ldb = talloc_zero(test_ctx, struct ldb_context); + ldb_kv = talloc_zero(test_ctx, struct ldb_kv_private); + + ret = ldb_kv_init_store(ldb_kv, "test", ldb, options, &module); + assert_int_equal(LDB_SUCCESS, ret); + + assert_int_equal( + DEFAULT_INDEX_CACHE_SIZE, + ldb_kv->index_transaction_cache_size); + + TALLOC_FREE(ldb_kv); + TALLOC_FREE(module); + TALLOC_FREE(ldb); +} + +/* + * Test that ldb_kv_init_store sets the default index transaction cache size + * if the value specified is too large + */ +static void test_init_store_set_index_cache_size_range(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort( + *state, + struct test_ctx); + struct ldb_module *module = NULL; + struct ldb_kv_private *ldb_kv = NULL; + struct ldb_context *ldb = NULL; + const char *options[] = { + "transaction_index_cache_size:0xfffffffffffffffffffffffffffff", + NULL}; + int ret = LDB_SUCCESS; + + module = talloc_zero(test_ctx, struct ldb_module); + ldb = talloc_zero(test_ctx, struct ldb_context); + ldb_kv = talloc_zero(test_ctx, struct ldb_kv_private); + + ret = ldb_kv_init_store(ldb_kv, "test", ldb, options, &module); + assert_int_equal(LDB_SUCCESS, ret); + + assert_int_equal( + DEFAULT_INDEX_CACHE_SIZE, + ldb_kv->index_transaction_cache_size); + + TALLOC_FREE(ldb_kv); + TALLOC_FREE(module); + TALLOC_FREE(ldb); +} + +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + test_index_cache_init, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_default_index_cache_size, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_reindex_cache_size, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_init_store_default_index_cache_size, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_init_store_set_index_cache_size, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_init_store_set_index_cache_size_non_numeric, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_init_store_set_index_cache_size_range, + setup, + teardown), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/ldb/tests/ldb_kv_ops_test.c b/lib/ldb/tests/ldb_kv_ops_test.c new file mode 100644 index 0000000..b84ed0c --- /dev/null +++ b/lib/ldb/tests/ldb_kv_ops_test.c @@ -0,0 +1,1819 @@ +/* + * Tests exercising the ldb key value operations. + * + * Copyright (C) Andrew Bartlett 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 . + * + */ + +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + * + */ + +/* + * A KV module is expected to have the following behaviour + * + * - A transaction must be open to perform any read, write or delete operation + * - Writes and Deletes should not be visible until a transaction is committed + * - Nested transactions are not permitted + * - transactions can be rolled back and committed. + * - supports iteration over all records in the database + * - supports the update_in_iterate operation allowing entries to be + * re-keyed. + * - has a get_size implementation that returns an estimate of the number of + * records in the database. Note that this can be an estimate rather than + * an accurate size. + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ldb_tdb/ldb_tdb.h" +#include "ldb_key_value/ldb_kv.h" + + +#define DEFAULT_BE "tdb" + +#ifndef TEST_BE +#define TEST_BE DEFAULT_BE +#endif /* TEST_BE */ + +#define NUM_RECS 1024 + + +struct test_ctx { + struct tevent_context *ev; + struct ldb_context *ldb; + + const char *dbfile; + const char *lockfile; /* lockfile is separate */ + + const char *dbpath; +}; + +static void unlink_old_db(struct test_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(); + } +} + +static int noconn_setup(void **state) +{ + struct test_ctx *test_ctx; + + test_ctx = talloc_zero(NULL, struct test_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->dbfile = talloc_strdup(test_ctx, "kvopstest.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); + *state = test_ctx; + return 0; +} + +static int noconn_teardown(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + + unlink_old_db(test_ctx); + talloc_free(test_ctx); + return 0; +} + +static int setup(void **state) +{ + struct test_ctx *test_ctx; + int ret; + struct ldb_ldif *ldif; + const char *index_ldif = \ + "dn: @INDEXLIST\n" + "@IDXGUID: objectUUID\n" + "@IDX_DN_GUID: GUID\n" + "\n"; + + noconn_setup((void **) &test_ctx); + + ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, 0); + + while ((ldif = ldb_ldif_read_string(test_ctx->ldb, &index_ldif))) { + ret = ldb_add(test_ctx->ldb, ldif->msg); + assert_int_equal(ret, LDB_SUCCESS); + } + *state = test_ctx; + return 0; +} + +static int teardown(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + noconn_teardown((void **) &test_ctx); + return 0; +} + +static struct ldb_kv_private *get_ldb_kv(struct ldb_context *ldb) +{ + void *data = NULL; + struct ldb_kv_private *ldb_kv = NULL; + + data = ldb_module_get_private(ldb->modules); + assert_non_null(data); + + ldb_kv = talloc_get_type(data, struct ldb_kv_private); + assert_non_null(ldb_kv); + + return ldb_kv; +} + +static int parse(struct ldb_val key, + struct ldb_val data, + void *private_data) +{ + struct ldb_val* read = private_data; + + /* Yes, we leak this. That is OK */ + read->data = talloc_size(NULL, + data.length); + assert_non_null(read->data); + + memcpy(read->data, data.data, data.length); + read->length = data.length; + return LDB_SUCCESS; +} + +/* + * Parse function that just returns the int we pass it. + */ +static int parse_return(struct ldb_val key, + struct ldb_val data, + void *private_data) +{ + int *rcode = private_data; + return *rcode; +} + +/* + * Test that data can be written to the kv store and be read back. + */ +static void test_add_get(void **state) +{ + int ret; + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb); + uint8_t key_val[] = "TheKey"; + struct ldb_val key = { + .data = key_val, + .length = sizeof(key_val) + }; + + uint8_t value[] = "The record contents"; + struct ldb_val data = { + .data = value, + .length = sizeof(value) + }; + + struct ldb_val read; + int rcode; + + int flags = 0; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* + * Begin a transaction + */ + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * Write the record + */ + ret = ldb_kv->kv_ops->store(ldb_kv, key, data, flags); + assert_int_equal(ret, 0); + + /* + * Commit the transaction + */ + ret = ldb_kv->kv_ops->finish_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * And now read it back + */ + ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read); + assert_int_equal(ret, 0); + + assert_int_equal(sizeof(value), read.length); + assert_memory_equal(value, read.data, sizeof(value)); + + /* + * Now check that the error code we return in the + * parse function is returned by fetch_and_parse. + */ + for (rcode=0; rcode<50; rcode++) { + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, + parse_return, + &rcode); + assert_int_equal(ret, rcode); + } + + ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + talloc_free(tmp_ctx); +} + +/* + * Test that attempts to read data without a read transaction fail. + */ +static void test_read_outside_transaction(void **state) +{ + int ret; + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb); + uint8_t key_val[] = "TheKey"; + struct ldb_val key = { + .data = key_val, + .length = sizeof(key_val) + }; + + uint8_t value[] = "The record contents"; + struct ldb_val data = { + .data = value, + .length = sizeof(value) + }; + + struct ldb_val read; + + int flags = 0; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* + * Begin a transaction + */ + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * Write the record + */ + ret = ldb_kv->kv_ops->store(ldb_kv, key, data, flags); + assert_int_equal(ret, 0); + + /* + * Commit the transaction + */ + ret = ldb_kv->kv_ops->finish_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * And now read it back + * Note there is no read transaction active + */ + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read); + assert_int_equal(ret, LDB_ERR_PROTOCOL_ERROR); + + talloc_free(tmp_ctx); +} + +/* + * Test that data can be deleted from the kv store + */ +static void test_delete(void **state) +{ + int ret; + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb); + uint8_t key_val[] = "TheKey"; + struct ldb_val key = { + .data = key_val, + .length = sizeof(key_val) + }; + + uint8_t value[] = "The record contents"; + struct ldb_val data = { + .data = value, + .length = sizeof(value) + }; + + struct ldb_val read; + + int flags = 0; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* + * Begin a transaction + */ + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * Write the record + */ + ret = ldb_kv->kv_ops->store(ldb_kv, key, data, flags); + assert_int_equal(ret, 0); + + /* + * Commit the transaction + */ + ret = ldb_kv->kv_ops->finish_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * And now read it back + */ + ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read); + assert_int_equal(ret, 0); + assert_int_equal(sizeof(value), read.length); + assert_memory_equal(value, read.data, sizeof(value)); + ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + + /* + * Begin a transaction + */ + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * Now delete it. + */ + ret = ldb_kv->kv_ops->delete (ldb_kv, key); + assert_int_equal(ret, 0); + + /* + * Commit the transaction + */ + ret = ldb_kv->kv_ops->finish_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * And now try to read it back + */ + ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read); + assert_int_equal(ret, LDB_ERR_NO_SUCH_OBJECT); + ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + + talloc_free(tmp_ctx); +} + +/* + * Check that writes are correctly rolled back when a transaction + * is rolled back. + */ +static void test_transaction_abort_write(void **state) +{ + int ret; + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb); + uint8_t key_val[] = "TheKey"; + struct ldb_val key = { + .data = key_val, + .length = sizeof(key_val) + }; + + uint8_t value[] = "The record contents"; + struct ldb_val data = { + .data = value, + .length = sizeof(value) + }; + + struct ldb_val read; + + int flags = 0; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* + * Begin a transaction + */ + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * Write the record + */ + ret = ldb_kv->kv_ops->store(ldb_kv, key, data, flags); + assert_int_equal(ret, 0); + + /* + * And now read it back + */ + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read); + assert_int_equal(ret, 0); + assert_int_equal(sizeof(value), read.length); + assert_memory_equal(value, read.data, sizeof(value)); + + + /* + * Now abort the transaction + */ + ret = ldb_kv->kv_ops->abort_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * And now read it back, should not be there + */ + ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read); + assert_int_equal(ret, LDB_ERR_NO_SUCH_OBJECT); + ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + + talloc_free(tmp_ctx); +} + +/* + * Check that deletes are correctly rolled back when a transaction is + * aborted. + */ +static void test_transaction_abort_delete(void **state) +{ + int ret; + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb); + uint8_t key_val[] = "TheKey"; + struct ldb_val key = { + .data = key_val, + .length = sizeof(key_val) + }; + + uint8_t value[] = "The record contents"; + struct ldb_val data = { + .data = value, + .length = sizeof(value) + }; + + struct ldb_val read; + + int flags = 0; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* + * Begin a transaction + */ + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * Write the record + */ + ret = ldb_kv->kv_ops->store(ldb_kv, key, data, flags); + assert_int_equal(ret, 0); + + /* + * Commit the transaction + */ + ret = ldb_kv->kv_ops->finish_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * And now read it back + */ + ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read); + assert_int_equal(ret, 0); + assert_int_equal(sizeof(value), read.length); + assert_memory_equal(value, read.data, sizeof(value)); + ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + + /* + * Begin a transaction + */ + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * Now delete it. + */ + ret = ldb_kv->kv_ops->delete (ldb_kv, key); + assert_int_equal(ret, 0); + + /* + * And now read it back + */ + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read); + assert_int_equal(ret, LDB_ERR_NO_SUCH_OBJECT); + + /* + * Abort the transaction + */ + ret = ldb_kv->kv_ops->abort_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * And now try to read it back + */ + ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read); + assert_int_equal(ret, 0); + assert_int_equal(sizeof(value), read.length); + assert_memory_equal(value, read.data, sizeof(value)); + ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + + talloc_free(tmp_ctx); +} + +/* + * Test that writes outside a transaction fail + */ +static void test_write_outside_transaction(void **state) +{ + int ret; + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb); + uint8_t key_val[] = "TheKey"; + struct ldb_val key = { + .data = key_val, + .length = sizeof(key_val) + }; + + uint8_t value[] = "The record contents"; + struct ldb_val data = { + .data = value, + .length = sizeof(value) + }; + + + int flags = 0; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* + * Attempt to write the record + */ + ret = ldb_kv->kv_ops->store(ldb_kv, key, data, flags); + assert_int_equal(ret, LDB_ERR_PROTOCOL_ERROR); + + talloc_free(tmp_ctx); +} + +/* + * Test data can not be deleted outside a transaction + */ +static void test_delete_outside_transaction(void **state) +{ + int ret; + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb); + uint8_t key_val[] = "TheKey"; + struct ldb_val key = { + .data = key_val, + .length = sizeof(key_val) + }; + + uint8_t value[] = "The record contents"; + struct ldb_val data = { + .data = value, + .length = sizeof(value) + }; + + struct ldb_val read; + + int flags = 0; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* + * Begin a transaction + */ + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * Write the record + */ + ret = ldb_kv->kv_ops->store(ldb_kv, key, data, flags); + assert_int_equal(ret, 0); + + /* + * Commit the transaction + */ + ret = ldb_kv->kv_ops->finish_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * And now read it back + */ + ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read); + assert_int_equal(ret, 0); + assert_int_equal(sizeof(value), read.length); + assert_memory_equal(value, read.data, sizeof(value)); + ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + + /* + * Now attempt to delete a record + */ + ret = ldb_kv->kv_ops->delete (ldb_kv, key); + assert_int_equal(ret, LDB_ERR_PROTOCOL_ERROR); + + /* + * And now read it back + */ + ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read); + assert_int_equal(ret, 0); + assert_int_equal(sizeof(value), read.length); + assert_memory_equal(value, read.data, sizeof(value)); + ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + + talloc_free(tmp_ctx); +} + +static int traverse_fn(struct ldb_kv_private *ldb_kv, + struct ldb_val key, + struct ldb_val data, + void *ctx) +{ + + int *visits = ctx; + int i; + + if (strncmp("key ", (char *) key.data, 4) == 0) { + i = strtol((char *) &key.data[4], NULL, 10); + visits[i]++; + } + return LDB_SUCCESS; +} + +/* + * Test that iterate visits all the records. + */ +static void test_iterate(void **state) +{ + int ret; + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb); + int i; + int num_recs = 1024; + int visits[num_recs]; + + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* + * Begin a transaction + */ + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * Write the records + */ + for (i = 0; i < num_recs; i++) { + struct ldb_val key; + struct ldb_val rec; + int flags = 0; + + visits[i] = 0; + key.data = (uint8_t *)talloc_asprintf(tmp_ctx, "key %04d", i); + key.length = strlen((char *)key.data) + 1; + + rec.data = (uint8_t *) talloc_asprintf(tmp_ctx, + "data for record (%04d)", + i); + rec.length = strlen((char *)rec.data) + 1; + + ret = ldb_kv->kv_ops->store(ldb_kv, key, rec, flags); + assert_int_equal(ret, 0); + + TALLOC_FREE(key.data); + TALLOC_FREE(rec.data); + } + + /* + * Commit the transaction + */ + ret = ldb_kv->kv_ops->finish_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * Now iterate over the kv store and ensure that all the + * records are visited. + */ + ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + ret = ldb_kv->kv_ops->iterate(ldb_kv, traverse_fn, visits); + for (i = 0; i kv_ops->unlock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + + TALLOC_FREE(tmp_ctx); +} + +static void do_iterate_range_test(void **state, int range_start, + int range_end, bool fail) +{ + int ret; + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + struct ldb_kv_private *ldb_kv = NULL; + int i; + int num_recs = 1024; + int skip_recs = 10; + int visits[num_recs]; + struct ldb_val sk, ek; + + TALLOC_CTX *tmp_ctx; + + ldb_kv = get_ldb_kv(test_ctx->ldb); + assert_non_null(ldb_kv); + + for (i = 0; i < num_recs; i++){ + visits[i] = 0; + } + + /* + * No iterate_range on tdb + */ + if (strcmp(TEST_BE, "tdb") == 0) { + return; + } + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* + * Begin a transaction + */ + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * Write the records + */ + for (i = skip_recs; i <= num_recs - skip_recs; i++) { + struct ldb_val key; + struct ldb_val rec; + int flags = 0; + + key.data = (uint8_t *)talloc_asprintf(tmp_ctx, + "key %04d", + i); + key.length = strlen((char *)key.data); + + rec.data = (uint8_t *)talloc_asprintf(tmp_ctx, + "data for record (%04d)", + i); + rec.length = strlen((char *)rec.data) + 1; + + ret = ldb_kv->kv_ops->store(ldb_kv, key, rec, flags); + assert_int_equal(ret, 0); + + TALLOC_FREE(key.data); + TALLOC_FREE(rec.data); + } + + /* + * Commit the transaction + */ + ret = ldb_kv->kv_ops->finish_write(ldb_kv); + assert_int_equal(ret, 0); + + sk.data = (uint8_t *)talloc_asprintf(tmp_ctx, "key %04d", range_start); + sk.length = strlen((char *)sk.data); + + ek.data = (uint8_t *)talloc_asprintf(tmp_ctx, "key %04d", range_end); + ek.length = strlen((char *)ek.data) + 1; + + ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + ret = ldb_kv->kv_ops->iterate_range(ldb_kv, sk, ek, + traverse_fn, visits); + if (fail){ + assert_int_equal(ret, LDB_ERR_PROTOCOL_ERROR); + TALLOC_FREE(tmp_ctx); + return; + } else{ + assert_int_equal(ret, 0); + } + for (i = 0; i < num_recs; i++) { + if (i >= skip_recs && i <= num_recs - skip_recs && + i >= range_start && i <= range_end){ + assert_int_equal(1, visits[i]); + } else { + assert_int_equal(0, visits[i]); + } + } + + ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + + TALLOC_FREE(tmp_ctx); +} + +/* + * Test that iterate_range visits all the records between two keys. + */ +static void test_iterate_range(void **state) +{ + do_iterate_range_test(state, 300, 900, false); + + /* + * test start_key = end_key + */ + do_iterate_range_test(state, 20, 20, false); + + /* + * test reverse range fails + */ + do_iterate_range_test(state, 50, 40, true); + + /* + * keys are between 10-1014 so test with keys outside that range + */ + do_iterate_range_test(state, 0, 20, false); + do_iterate_range_test(state, 1010, 1030, false); + do_iterate_range_test(state, 0, 1030, false); +} + +struct update_context { + struct ldb_context* ldb; + int visits[NUM_RECS]; +}; + +static int update_fn(struct ldb_kv_private *ldb_kv, + struct ldb_val key, + struct ldb_val data, + void *ctx) +{ + + struct ldb_val new_key; + struct ldb_module *module = NULL; + struct update_context *context =NULL; + int ret = LDB_SUCCESS; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(ldb_kv); + assert_non_null(tmp_ctx); + + context = talloc_get_type_abort(ctx, struct update_context); + + module = talloc_zero(tmp_ctx, struct ldb_module); + module->ldb = context->ldb; + + if (strncmp("key ", (char *) key.data, 4) == 0) { + int i = strtol((char *) &key.data[4], NULL, 10); + context->visits[i]++; + new_key.data = talloc_memdup(tmp_ctx, key.data, key.length); + new_key.length = key.length; + new_key.data[0] = 'K'; + + ret = ldb_kv->kv_ops->update_in_iterate( + ldb_kv, key, new_key, data, &module); + } + TALLOC_FREE(tmp_ctx); + return ret; +} + +/* + * Test that update_in_iterate behaves as expected. + */ +static void test_update_in_iterate(void **state) +{ + int ret; + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb); + int i; + struct update_context *context = NULL; + + + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + context = talloc_zero(tmp_ctx, struct update_context); + assert_non_null(context); + context->ldb = test_ctx->ldb; + /* + * Begin a transaction + */ + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * Write the records + */ + for (i = 0; i < NUM_RECS; i++) { + struct ldb_val key; + struct ldb_val rec; + int flags = 0; + + key.data = (uint8_t *)talloc_asprintf(tmp_ctx, "key %04d", i); + key.length = strlen((char *)key.data) + 1; + + rec.data = (uint8_t *) talloc_asprintf(tmp_ctx, + "data for record (%04d)", + i); + rec.length = strlen((char *)rec.data) + 1; + + ret = ldb_kv->kv_ops->store(ldb_kv, key, rec, flags); + assert_int_equal(ret, 0); + + TALLOC_FREE(key.data); + TALLOC_FREE(rec.data); + } + + /* + * Commit the transaction + */ + ret = ldb_kv->kv_ops->finish_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * Now iterate over the kv store and ensure that all the + * records are visited. + */ + + /* + * Needs to be done inside a transaction + */ + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, 0); + + ret = ldb_kv->kv_ops->iterate(ldb_kv, update_fn, context); + for (i = 0; i < NUM_RECS; i++) { + assert_int_equal(1, context->visits[i]); + } + + ret = ldb_kv->kv_ops->finish_write(ldb_kv); + assert_int_equal(ret, 0); + + TALLOC_FREE(tmp_ctx); +} + +/* + * Ensure that writes are not visible until the transaction has been + * committed. + */ +static void test_write_transaction_isolation(void **state) +{ + int ret; + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb); + struct ldb_val key; + struct ldb_val val; + + const char *KEY1 = "KEY01"; + const char *VAL1 = "VALUE01"; + + const char *KEY2 = "KEY02"; + const char *VAL2 = "VALUE02"; + + /* + * Pipes etc to coordinate the processes + */ + int to_child[2]; + int to_parent[2]; + char buf[2]; + pid_t pid, w_pid; + int wstatus; + + TALLOC_CTX *tmp_ctx; + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + + /* + * Add a record to the database + */ + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, 0); + + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1); + key.length = strlen(KEY1) + 1; + + val.data = (uint8_t *)talloc_strdup(tmp_ctx, VAL1); + val.length = strlen(VAL1) + 1; + + ret = ldb_kv->kv_ops->store(ldb_kv, key, val, 0); + assert_int_equal(ret, 0); + + ret = ldb_kv->kv_ops->finish_write(ldb_kv); + assert_int_equal(ret, 0); + + + ret = pipe(to_child); + assert_int_equal(ret, 0); + ret = pipe(to_parent); + assert_int_equal(ret, 0); + /* + * Now fork a new process + */ + + pid = fork(); + if (pid == 0) { + + struct ldb_context *ldb = NULL; + close(to_child[1]); + close(to_parent[0]); + + /* + * Wait for the transaction to start + */ + ret = read(to_child[0], buf, 2); + if (ret != 2) { + print_error(__location__": read returned (%d)\n", + ret); + exit(LDB_ERR_OPERATIONS_ERROR); + } + ldb = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb, test_ctx->dbpath, 0, NULL); + if (ret != LDB_SUCCESS) { + print_error(__location__": ldb_connect returned (%d)\n", + ret); + exit(ret); + } + + ldb_kv = get_ldb_kv(ldb); + + ret = ldb_kv->kv_ops->lock_read(ldb->modules); + if (ret != LDB_SUCCESS) { + print_error(__location__": lock_read returned (%d)\n", + ret); + exit(ret); + } + + /* + * Check that KEY1 is there + */ + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1); + key.length = strlen(KEY1) + 1; + + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val); + if (ret != LDB_SUCCESS) { + print_error(__location__": fetch_and_parse returned " + "(%d)\n", + ret); + exit(ret); + } + + if ((strlen(VAL1) + 1) != val.length) { + print_error(__location__": KEY1 value lengths different" + ", expected (%d) actual(%d)\n", + (int)(strlen(VAL1) + 1), (int)val.length); + exit(LDB_ERR_OPERATIONS_ERROR); + } + if (memcmp(VAL1, val.data, strlen(VAL1)) != 0) { + print_error(__location__": KEY1 values different, " + "expected (%s) actual(%s)\n", + VAL1, + val.data); + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_kv->kv_ops->unlock_read(ldb->modules); + if (ret != LDB_SUCCESS) { + print_error(__location__": unlock_read returned (%d)\n", + ret); + exit(ret); + } + + /* + * Check that KEY2 is not there + */ + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2); + key.length = strlen(KEY2 + 1); + + ret = ldb_kv->kv_ops->lock_read(ldb->modules); + if (ret != LDB_SUCCESS) { + print_error(__location__": lock_read returned (%d)\n", + ret); + exit(ret); + } + + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val); + if (ret != LDB_ERR_NO_SUCH_OBJECT) { + print_error(__location__": fetch_and_parse returned " + "(%d)\n", + ret); + exit(ret); + } + + ret = ldb_kv->kv_ops->unlock_read(ldb->modules); + if (ret != LDB_SUCCESS) { + print_error(__location__": unlock_read returned (%d)\n", + ret); + exit(ret); + } + + /* + * Signal the other process to commit the transaction + */ + ret = write(to_parent[1], "GO", 2); + if (ret != 2) { + print_error(__location__": write returned (%d)\n", + ret); + exit(LDB_ERR_OPERATIONS_ERROR); + } + + /* + * Wait for the transaction to be committed + */ + ret = read(to_child[0], buf, 2); + if (ret != 2) { + print_error(__location__": read returned (%d)\n", + ret); + exit(LDB_ERR_OPERATIONS_ERROR); + } + + /* + * Check that KEY1 is there + */ + ret = ldb_kv->kv_ops->lock_read(ldb->modules); + if (ret != LDB_SUCCESS) { + print_error(__location__": unlock_read returned (%d)\n", + ret); + exit(ret); + } + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1); + key.length = strlen(KEY1) + 1; + + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val); + if (ret != LDB_SUCCESS) { + print_error(__location__": fetch_and_parse returned " + "(%d)\n", + ret); + exit(ret); + } + + if ((strlen(VAL1) + 1) != val.length) { + print_error(__location__": KEY1 value lengths different" + ", expected (%d) actual(%d)\n", + (int)(strlen(VAL1) + 1), (int)val.length); + exit(LDB_ERR_OPERATIONS_ERROR); + } + if (memcmp(VAL1, val.data, strlen(VAL1)) != 0) { + print_error(__location__": KEY1 values different, " + "expected (%s) actual(%s)\n", + VAL1, + val.data); + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_kv->kv_ops->unlock_read(ldb->modules); + if (ret != LDB_SUCCESS) { + print_error(__location__": unlock_read returned (%d)\n", + ret); + exit(ret); + } + + + /* + * Check that KEY2 is there + */ + ret = ldb_kv->kv_ops->lock_read(ldb->modules); + if (ret != LDB_SUCCESS) { + print_error(__location__": unlock_read returned (%d)\n", + ret); + exit(ret); + } + + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2); + key.length = strlen(KEY2) + 1; + + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val); + if (ret != LDB_SUCCESS) { + print_error(__location__": fetch_and_parse returned " + "(%d)\n", + ret); + exit(ret); + } + + if ((strlen(VAL2) + 1) != val.length) { + print_error(__location__": KEY2 value lengths different" + ", expected (%d) actual(%d)\n", + (int)(strlen(VAL2) + 1), (int)val.length); + exit(LDB_ERR_OPERATIONS_ERROR); + } + if (memcmp(VAL2, val.data, strlen(VAL2)) != 0) { + print_error(__location__": KEY2 values different, " + "expected (%s) actual(%s)\n", + VAL2, + val.data); + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_kv->kv_ops->unlock_read(ldb->modules); + if (ret != LDB_SUCCESS) { + print_error(__location__": unlock_read returned (%d)\n", + ret); + exit(ret); + } + + exit(0); + } + close(to_child[0]); + close(to_parent[1]); + + /* + * Begin a transaction and add a record to the database + * but leave the transaction open + */ + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, 0); + + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2); + key.length = strlen(KEY2) + 1; + + val.data = (uint8_t *)talloc_strdup(tmp_ctx, VAL2); + val.length = strlen(VAL2) + 1; + + ret = ldb_kv->kv_ops->store(ldb_kv, key, val, 0); + assert_int_equal(ret, 0); + + /* + * Signal the child process + */ + ret = write(to_child[1], "GO", 2); + assert_int_equal(2, ret); + + /* + * Wait for the child process to check the DB state while the + * transaction is active + */ + ret = read(to_parent[0], buf, 2); + assert_int_equal(2, ret); + + /* + * commit the transaction + */ + ret = ldb_kv->kv_ops->finish_write(ldb_kv); + assert_int_equal(0, ret); + + /* + * Signal the child process + */ + ret = write(to_child[1], "GO", 2); + assert_int_equal(2, ret); + + w_pid = waitpid(pid, &wstatus, 0); + assert_int_equal(pid, w_pid); + + assert_true(WIFEXITED(wstatus)); + + assert_int_equal(WEXITSTATUS(wstatus), 0); + + + TALLOC_FREE(tmp_ctx); +} + +/* + * Ensure that deletes are not visible until the transaction has been + * committed. + */ +static void test_delete_transaction_isolation(void **state) +{ + int ret; + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb); + struct ldb_val key; + struct ldb_val val; + + const char *KEY1 = "KEY01"; + const char *VAL1 = "VALUE01"; + + const char *KEY2 = "KEY02"; + const char *VAL2 = "VALUE02"; + + /* + * Pipes etc to coordinate the processes + */ + int to_child[2]; + int to_parent[2]; + char buf[2]; + pid_t pid, w_pid; + int wstatus; + + TALLOC_CTX *tmp_ctx; + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + + /* + * Add records to the database + */ + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, 0); + + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1); + key.length = strlen(KEY1) + 1; + + val.data = (uint8_t *)talloc_strdup(tmp_ctx, VAL1); + val.length = strlen(VAL1) + 1; + + ret = ldb_kv->kv_ops->store(ldb_kv, key, val, 0); + assert_int_equal(ret, 0); + + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2); + key.length = strlen(KEY2) + 1; + + val.data = (uint8_t *)talloc_strdup(tmp_ctx, VAL2); + val.length = strlen(VAL2) + 1; + + ret = ldb_kv->kv_ops->store(ldb_kv, key, val, 0); + assert_int_equal(ret, 0); + + ret = ldb_kv->kv_ops->finish_write(ldb_kv); + assert_int_equal(ret, 0); + + + ret = pipe(to_child); + assert_int_equal(ret, 0); + ret = pipe(to_parent); + assert_int_equal(ret, 0); + /* + * Now fork a new process + */ + + pid = fork(); + if (pid == 0) { + + struct ldb_context *ldb = NULL; + close(to_child[1]); + close(to_parent[0]); + + /* + * Wait for the transaction to be started + */ + ret = read(to_child[0], buf, 2); + if (ret != 2) { + print_error(__location__": read returned (%d)\n", + ret); + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ldb = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb, test_ctx->dbpath, 0, NULL); + if (ret != LDB_SUCCESS) { + print_error(__location__": ldb_connect returned (%d)\n", + ret); + exit(ret); + } + + ldb_kv = get_ldb_kv(ldb); + + ret = ldb_kv->kv_ops->lock_read(ldb->modules); + if (ret != LDB_SUCCESS) { + print_error(__location__": lock_read returned (%d)\n", + ret); + exit(ret); + } + + /* + * Check that KEY1 is there + */ + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1); + key.length = strlen(KEY1) + 1; + + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val); + if (ret != LDB_SUCCESS) { + print_error(__location__": fetch_and_parse returned " + "(%d)\n", + ret); + exit(ret); + } + + if ((strlen(VAL1) + 1) != val.length) { + print_error(__location__": KEY1 value lengths different" + ", expected (%d) actual(%d)\n", + (int)(strlen(VAL1) + 1), (int)val.length); + exit(LDB_ERR_OPERATIONS_ERROR); + } + if (memcmp(VAL1, val.data, strlen(VAL1)) != 0) { + print_error(__location__": KEY1 values different, " + "expected (%s) actual(%s)\n", + VAL1, + val.data); + exit(LDB_ERR_OPERATIONS_ERROR); + } + + /* + * Check that KEY2 is there + */ + + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2); + key.length = strlen(KEY2) + 1; + + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val); + if (ret != LDB_SUCCESS) { + print_error(__location__": fetch_and_parse returned " + "(%d)\n", + ret); + exit(ret); + } + + if ((strlen(VAL2) + 1) != val.length) { + print_error(__location__": KEY2 value lengths different" + ", expected (%d) actual(%d)\n", + (int)(strlen(VAL2) + 1), (int)val.length); + exit(LDB_ERR_OPERATIONS_ERROR); + } + if (memcmp(VAL2, val.data, strlen(VAL2)) != 0) { + print_error(__location__": KEY2 values different, " + "expected (%s) actual(%s)\n", + VAL2, + val.data); + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_kv->kv_ops->unlock_read(ldb->modules); + if (ret != LDB_SUCCESS) { + print_error(__location__": unlock_read returned (%d)\n", + ret); + exit(ret); + } + + /* + * Signal the other process to commit the transaction + */ + ret = write(to_parent[1], "GO", 2); + if (ret != 2) { + print_error(__location__": write returned (%d)\n", + ret); + exit(LDB_ERR_OPERATIONS_ERROR); + } + + /* + * Wait for the transaction to be committed + */ + ret = read(to_child[0], buf, 2); + if (ret != 2) { + print_error(__location__": read returned (%d)\n", + ret); + exit(LDB_ERR_OPERATIONS_ERROR); + } + + /* + * Check that KEY1 is there + */ + ret = ldb_kv->kv_ops->lock_read(ldb->modules); + if (ret != LDB_SUCCESS) { + print_error(__location__": unlock_read returned (%d)\n", + ret); + exit(ret); + } + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1); + key.length = strlen(KEY1) + 1; + + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val); + if (ret != LDB_SUCCESS) { + print_error(__location__": fetch_and_parse returned " + "(%d)\n", + ret); + exit(ret); + } + + if ((strlen(VAL1) + 1) != val.length) { + print_error(__location__": KEY1 value lengths different" + ", expected (%d) actual(%d)\n", + (int)(strlen(VAL1) + 1), (int)val.length); + exit(LDB_ERR_OPERATIONS_ERROR); + } + if (memcmp(VAL1, val.data, strlen(VAL1)) != 0) { + print_error(__location__": KEY1 values different, " + "expected (%s) actual(%s)\n", + VAL1, + val.data); + exit(LDB_ERR_OPERATIONS_ERROR); + } + ret = ldb_kv->kv_ops->unlock_read(ldb->modules); + if (ret != LDB_SUCCESS) { + print_error(__location__": unlock_read returned (%d)\n", + ret); + exit(ret); + } + + /* + * Check that KEY2 is not there + */ + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2); + key.length = strlen(KEY2 + 1); + + ret = ldb_kv->kv_ops->lock_read(ldb->modules); + if (ret != LDB_SUCCESS) { + print_error(__location__": lock_read returned (%d)\n", + ret); + exit(ret); + } + + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val); + if (ret != LDB_ERR_NO_SUCH_OBJECT) { + print_error(__location__": fetch_and_parse returned " + "(%d)\n", + ret); + exit(ret); + } + + ret = ldb_kv->kv_ops->unlock_read(ldb->modules); + if (ret != LDB_SUCCESS) { + print_error(__location__": unlock_read returned (%d)\n", + ret); + exit(ret); + } + TALLOC_FREE(tmp_ctx); + exit(0); + } + close(to_child[0]); + close(to_parent[1]); + + /* + * Begin a transaction and delete a record from the database + * but leave the transaction open + */ + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, 0); + + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2); + key.length = strlen(KEY2) + 1; + + ret = ldb_kv->kv_ops->delete (ldb_kv, key); + assert_int_equal(ret, 0); + /* + * Signal the child process + */ + ret = write(to_child[1], "GO", 2); + assert_int_equal(2, ret); + + /* + * Wait for the child process to check the DB state while the + * transaction is active + */ + ret = read(to_parent[0], buf, 2); + assert_int_equal(2, ret); + + /* + * commit the transaction + */ + ret = ldb_kv->kv_ops->finish_write(ldb_kv); + assert_int_equal(0, ret); + + /* + * Signal the child process + */ + ret = write(to_child[1], "GO", 2); + assert_int_equal(2, ret); + + w_pid = waitpid(pid, &wstatus, 0); + assert_int_equal(pid, w_pid); + + assert_true(WIFEXITED(wstatus)); + + assert_int_equal(WEXITSTATUS(wstatus), 0); + + + TALLOC_FREE(tmp_ctx); +} + + +/* + * Test that get_size returns a sensible estimate of the number of records + * in the database. + */ +static void test_get_size(void **state) +{ + int ret; + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb); + uint8_t key_val[] = "TheKey"; + struct ldb_val key = { + .data = key_val, + .length = sizeof(key_val) + }; + + uint8_t value[] = "The record contents"; + struct ldb_val data = { + .data = value, + .length = sizeof(value) + }; + size_t size = 0; + + int flags = 0; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + size = ldb_kv->kv_ops->get_size(ldb_kv); +#if defined(TEST_LMDB) + assert_int_equal(2, size); +#else + /* + * The tdb implementation of get_size over estimates for sparse files + * which is perfectly acceptable for it's intended use. + * mipsel, ia64: 9994 + * ppc64el, powerpc, ppc64: 13369 + * sparc64: 5046 + */ + assert_in_range(size, 2500, 15000); +#endif + + /* + * Begin a transaction + */ + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, 0); + + /* + * Write the record + */ + ret = ldb_kv->kv_ops->store(ldb_kv, key, data, flags); + assert_int_equal(ret, 0); + + /* + * Commit the transaction + */ + ret = ldb_kv->kv_ops->finish_write(ldb_kv); + assert_int_equal(ret, 0); + + size = ldb_kv->kv_ops->get_size(ldb_kv); +#ifdef TEST_LMDB + assert_int_equal(3, size); +#else + /* + * The tdb implementation of get_size over estimates for sparse files + * which is perfectly acceptable for it's intended use. + * mipsel, ia64: 9994 + * ppc64el, powerpc, ppc64: 13369 + * sparc64: 5046 + */ + assert_in_range(size, 2500, 15000); +#endif + talloc_free(tmp_ctx); +} + +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + test_add_get, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_delete, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_transaction_abort_write, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_transaction_abort_delete, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_read_outside_transaction, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_write_outside_transaction, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_delete_outside_transaction, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_iterate, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_iterate_range, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_update_in_iterate, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_write_transaction_isolation, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_delete_transaction_isolation, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_get_size, + setup, + teardown), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/ldb/tests/ldb_kv_ops_test.valgrind b/lib/ldb/tests/ldb_kv_ops_test.valgrind new file mode 100644 index 0000000..1747076 --- /dev/null +++ b/lib/ldb/tests/ldb_kv_ops_test.valgrind @@ -0,0 +1,97 @@ +{ + Memory allocated by setup + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:setup +} +{ + Memory allocated by setup + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + ... + fun:setup +} +{ + Memory allocated by ldb_init + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:ldb_init +} +{ + Memory allocated by ldb_init + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + ... + fun:ldb_init +} +{ + Memory allocated by noconn_setup + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:noconn_setup +} +{ + Memory allocated by parse, which allocates on the NULL context + Memcheck:Leak + match-leak-kinds: all + fun:malloc + ... + fun:parse +} +{ + Memory allocated by tdb_parse_data + Memcheck:Leak + match-leak-kinds: all + fun:malloc + ... + fun:tdb_parse_data +} +{ + Memory allocated by ldb_connect + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:ldb_connect +} +{ + Memory allocated by ldb_connect + Memcheck:Leak + match-leak-kinds: possible + fun:calloc + ... + fun:ldb_connect +} +{ + Memory allocated by ldb_kv_cache_load + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:ldb_kv_cache_load +} +{ + Memory allocated by ldb_kv_index_load + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:ldb_kv_index_load +} +{ + Memory allocated by ldb_asprintf_errstring + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:ldb_asprintf_errstring +} + diff --git a/lib/ldb/tests/ldb_lmdb_free_list_test.c b/lib/ldb/tests/ldb_lmdb_free_list_test.c new file mode 100644 index 0000000..246fdc7 --- /dev/null +++ b/lib/ldb/tests/ldb_lmdb_free_list_test.c @@ -0,0 +1,661 @@ +/* + * Copyright (C) Catalyst.Net Ltd 2020 + * + * 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 . + * + */ + +/* + * Tests confirming lmdb's handling of the free space list in the presence + * of active and stale readers. A stale reader is a process that opens a + * read lock and then exits without releasing the lock. + * + * lmdb uses MVCC to maintain databased consistency, new copies of updated + * records are written to the database. The old entries are only + * reused when they are no longer referenced in a read transaction. + * + * The tests all update a single record multiple times + * + * If there is a read transaction or a stale reader lmdb will report + * out of space. + * + * If no read transaction and no stale reader, lmdb reclaims space from the + * free list. + */ + +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ldb_tdb/ldb_tdb.h" +#include "ldb_key_value/ldb_kv.h" + +#define DEFAULT_BE "mdb" + +#ifndef TEST_BE +#define TEST_BE DEFAULT_BE +#endif /* TEST_BE */ + +const int RECORD_SIZE = 6144; +const int ITERATIONS = 192; + +struct test_ctx { + struct tevent_context *ev; + struct ldb_context *ldb; + + const char *dbfile; + const char *lockfile; /* lockfile is separate */ + + const char *dbpath; +}; + +static void unlink_old_db(struct test_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(); + } +} + +static int noconn_setup(void **state) +{ + struct test_ctx *test_ctx; + + test_ctx = talloc_zero(NULL, struct test_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->dbfile = talloc_strdup(test_ctx, "lmdb_free_list_test.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); + *state = test_ctx; + return 0; +} + +static int noconn_teardown(void **state) +{ + struct test_ctx *test_ctx = + talloc_get_type_abort(*state, struct test_ctx); + + unlink_old_db(test_ctx); + talloc_free(test_ctx); + return 0; +} + +static int setup(void **state) +{ + struct test_ctx *test_ctx; + int ret; + struct ldb_ldif *ldif; + const char *index_ldif = "dn: @INDEXLIST\n" + "@IDXGUID: objectUUID\n" + "@IDX_DN_GUID: GUID\n" + "\n"; + /* + * Use a 1MiB DB for this test + */ + const char *options[] = {"lmdb_env_size:1048576", NULL}; + + noconn_setup((void **)&test_ctx); + + ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, options); + assert_int_equal(ret, 0); + + while ((ldif = ldb_ldif_read_string(test_ctx->ldb, &index_ldif))) { + ret = ldb_add(test_ctx->ldb, ldif->msg); + assert_int_equal(ret, LDB_SUCCESS); + } + *state = test_ctx; + return 0; +} + +static int teardown(void **state) +{ + struct test_ctx *test_ctx = + talloc_get_type_abort(*state, struct test_ctx); + noconn_teardown((void **)&test_ctx); + return 0; +} + +static struct ldb_kv_private *get_ldb_kv(struct ldb_context *ldb) +{ + void *data = NULL; + struct ldb_kv_private *ldb_kv = NULL; + + data = ldb_module_get_private(ldb->modules); + assert_non_null(data); + + ldb_kv = talloc_get_type(data, struct ldb_kv_private); + assert_non_null(ldb_kv); + + return ldb_kv; +} + +static int parse(struct ldb_val key, struct ldb_val data, void *private_data) +{ + struct ldb_val *read = private_data; + + /* Yes, we leak this. That is OK */ + read->data = talloc_size(NULL, data.length); + assert_non_null(read->data); + + memcpy(read->data, data.data, data.length); + read->length = data.length; + return LDB_SUCCESS; +} + +/* + * This test has the same structure as the test_free_list_read_lock + * except the parent process does not keep the read lock open while the + * child process is performing an update. + */ +static void test_free_list_no_read_lock(void **state) +{ + int ret; + struct test_ctx *test_ctx = + talloc_get_type_abort(*state, struct test_ctx); + struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb); + struct ldb_val key; + struct ldb_val val; + + const char *KEY1 = "KEY01"; + + /* + * Pipes etc to coordinate the processes + */ + int to_child[2]; + int to_parent[2]; + char buf[2]; + pid_t pid; + size_t i; + + TALLOC_CTX *tmp_ctx; + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + ret = pipe(to_child); + assert_int_equal(ret, 0); + ret = pipe(to_parent); + assert_int_equal(ret, 0); + /* + * Now fork a new process + */ + + pid = fork(); + if (pid == 0) { + /* + * Child process + */ + + struct ldb_context *ldb = NULL; + close(to_child[1]); + close(to_parent[0]); + + /* + * Wait for the parent to get ready. + */ + ret = read(to_child[0], buf, 2); + assert_int_equal(ret, 2); + + ldb = ldb_init(test_ctx, test_ctx->ev); + assert_non_null(ldb); + + ret = ldb_connect(ldb, test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, LDB_SUCCESS); + + ldb_kv = get_ldb_kv(ldb); + assert_non_null(ldb_kv); + /* + * Add a record to the database + */ + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1); + key.length = strlen(KEY1) + 1; + val.data = talloc_zero_size(tmp_ctx, RECORD_SIZE); + assert_non_null(val.data); + memset(val.data, 'x', RECORD_SIZE); + val.length = RECORD_SIZE; + /* + * Do more iterations than when a read lock, stale reader + * active to confirm that the space is being re-used. + */ + for (i = 0; i < ITERATIONS * 10; i++) { + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_kv->kv_ops->store(ldb_kv, key, val, 0); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_kv->kv_ops->finish_write(ldb_kv); + assert_int_equal(ret, LDB_SUCCESS); + } + + /* + * Signal the parent that we've done the updates + */ + ret = write(to_parent[1], "GO", 2); + assert_int_equal(ret, 2); + exit(0); + } + + close(to_child[0]); + close(to_parent[1]); + + /* + * Begin a read transaction + */ + ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules); + assert_int_equal(ret, LDB_SUCCESS); + + /* + * Now close it + */ + ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules); + assert_int_equal(ret, LDB_SUCCESS); + + /* + * Signal the child process + */ + ret = write(to_child[1], "GO", 2); + assert_int_equal(2, ret); + + /* + * Wait for the child process to update the record + */ + ret = read(to_parent[0], buf, 2); + assert_int_equal(2, ret); + + /* + * Begin a read transaction + */ + ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules); + assert_int_equal(ret, LDB_SUCCESS); + /* + * read the record + * and close the transaction + */ + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1); + key.length = strlen(KEY1) + 1; + + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules); + assert_int_equal(ret, LDB_SUCCESS); + + close(to_child[1]); + close(to_parent[0]); + TALLOC_FREE(tmp_ctx); +} + +/* + * This test has the same structure as the test_free_list_read_lock + * except the parent process keeps the read lock open while the + * child process is performing an update. + */ +static void test_free_list_read_lock(void **state) +{ + int ret; + struct test_ctx *test_ctx = + talloc_get_type_abort(*state, struct test_ctx); + struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb); + struct ldb_val key; + struct ldb_val val; + + const char *KEY1 = "KEY01"; + + /* + * Pipes etc to coordinate the processes + */ + int to_child[2]; + int to_parent[2]; + char buf[2]; + pid_t pid; + size_t i; + + TALLOC_CTX *tmp_ctx; + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + ret = pipe(to_child); + assert_int_equal(ret, 0); + ret = pipe(to_parent); + assert_int_equal(ret, 0); + /* + * Now fork a new process + */ + + pid = fork(); + if (pid == 0) { + /* + * Child process + */ + + struct ldb_context *ldb = NULL; + close(to_child[1]); + close(to_parent[0]); + + /* + * Wait for the transaction to start + */ + ret = read(to_child[0], buf, 2); + assert_int_equal(ret, 2); + + ldb = ldb_init(test_ctx, test_ctx->ev); + assert_non_null(ldb); + + ret = ldb_connect(ldb, test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, LDB_SUCCESS); + + ldb_kv = get_ldb_kv(ldb); + assert_non_null(ldb_kv); + /* + * Add a record to the database + */ + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1); + key.length = strlen(KEY1) + 1; + val.data = talloc_zero_size(tmp_ctx, RECORD_SIZE); + assert_non_null(val.data); + memset(val.data, 'x', RECORD_SIZE); + val.length = RECORD_SIZE; + for (i = 0; i < ITERATIONS; i++) { + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, 0); + ret = ldb_kv->kv_ops->store(ldb_kv, key, val, 0); + if (ret == LDB_ERR_BUSY && i > 0) { + int rc = ldb_kv->kv_ops->abort_write(ldb_kv); + assert_int_equal(rc, LDB_SUCCESS); + break; + } + assert_int_equal(ret, LDB_SUCCESS); + ret = ldb_kv->kv_ops->finish_write(ldb_kv); + assert_int_equal(ret, LDB_SUCCESS); + } + assert_int_equal(ret, LDB_ERR_BUSY); + assert_int_not_equal(i, 0); + + /* + * Begin a read transaction + */ + ret = ldb_kv->kv_ops->lock_read(ldb->modules); + assert_int_equal(ret, LDB_SUCCESS); + /* + * read the record + * and close the transaction + */ + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1); + key.length = strlen(KEY1) + 1; + + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_kv->kv_ops->unlock_read(ldb->modules); + assert_int_equal(ret, LDB_SUCCESS); + + /* + * Signal the the parent that we've done the update + */ + ret = write(to_parent[1], "GO", 2); + assert_int_equal(ret, 2); + exit(0); + } + + close(to_child[0]); + close(to_parent[1]); + + /* + * Begin a read transaction + */ + ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules); + assert_int_equal(ret, LDB_SUCCESS); + + /* + * Signal the child process + */ + ret = write(to_child[1], "GO", 2); + assert_int_equal(ret, 2); + + /* + * Wait for the child process to update the record + */ + ret = read(to_parent[0], buf, 2); + assert_int_equal(ret, 2); + + /* + * read the record + * and close the transaction + */ + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1); + key.length = strlen(KEY1) + 1; + + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val); + assert_int_equal(ret, LDB_ERR_NO_SUCH_OBJECT); + ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules); + assert_int_equal(ret, 0); + + close(to_child[1]); + close(to_parent[0]); + TALLOC_FREE(tmp_ctx); +} + +/* + * This tests forks a child process that opens a read lock and then + * exits. This results in a stale reader entry in the lmdb lock file. + */ +static void test_free_list_stale_reader(void **state) +{ + int ret; + struct test_ctx *test_ctx = + talloc_get_type_abort(*state, struct test_ctx); + struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb); + struct ldb_val key; + struct ldb_val val; + + const char *KEY1 = "KEY01"; + + /* + * Pipes etc to coordinate the processes + */ + int to_child[2]; + int to_parent[2]; + char buf[2]; + pid_t pid; + size_t i; + + TALLOC_CTX *tmp_ctx; + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + ret = pipe(to_child); + assert_int_equal(ret, 0); + ret = pipe(to_parent); + assert_int_equal(ret, 0); + /* + * Now fork a new process + */ + + pid = fork(); + if (pid == 0) { + /* + * Child process + */ + + struct ldb_context *ldb = NULL; + close(to_child[1]); + close(to_parent[0]); + + /* + * Wait for the parent to get ready + */ + ret = read(to_child[0], buf, 2); + assert_int_equal(ret, 2); + + ldb = ldb_init(test_ctx, test_ctx->ev); + assert_non_null(ldb); + + ret = ldb_connect(ldb, test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, LDB_SUCCESS); + + ldb_kv = get_ldb_kv(ldb); + assert_non_null(ldb_kv); + + /* + * Begin a read transaction + */ + ret = ldb_kv->kv_ops->lock_read(ldb->modules); + assert_int_equal(ret, LDB_SUCCESS); + + /* + * Now exit with out releasing the read lock + * this will result in a stale entry in the + * read lock table. + */ + + exit(0); + } + + close(to_child[0]); + close(to_parent[1]); + + /* + * Tell the child to start + */ + ret = write(to_child[1], "GO", 2); + assert_int_equal(ret, 2); + + close(to_child[1]); + close(to_parent[0]); + + /* + * Now wait for the child process to complete + */ + waitpid(pid, NULL, 0); + + /* + * Add a record to the database + */ + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1); + key.length = strlen(KEY1) + 1; + val.data = talloc_zero_size(tmp_ctx, RECORD_SIZE); + assert_non_null(val.data); + memset(val.data, 'x', RECORD_SIZE); + val.length = RECORD_SIZE; + for (i = 0; i < ITERATIONS; i++) { + ret = ldb_kv->kv_ops->begin_write(ldb_kv); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_kv->kv_ops->store(ldb_kv, key, val, 0); + if (ret == LDB_ERR_BUSY && i > 0) { + int rc = ldb_kv->kv_ops->abort_write(ldb_kv); + assert_int_equal(rc, LDB_SUCCESS); + break; + } + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_kv->kv_ops->finish_write(ldb_kv); + assert_int_equal(ret, LDB_SUCCESS); + } + /* + * We now do an explicit clear of stale readers at the start of a + * write transaction so should not get LDB_ERR_BUSY any more + * assert_int_equal(ret, LDB_ERR_BUSY); + */ + assert_int_equal(ret, LDB_SUCCESS); + assert_int_not_equal(i, 0); + + /* + * Begin a read transaction + */ + ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules); + assert_int_equal(ret, LDB_SUCCESS); + /* + * read the record + * and close the transaction + */ + key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1); + key.length = strlen(KEY1) + 1; + + ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules); + assert_int_equal(ret, LDB_SUCCESS); + + TALLOC_FREE(tmp_ctx); +} + +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + test_free_list_no_read_lock, setup, teardown), + cmocka_unit_test_setup_teardown( + test_free_list_read_lock, setup, teardown), + cmocka_unit_test_setup_teardown( + test_free_list_stale_reader, setup, teardown), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/ldb/tests/ldb_lmdb_size_test.c b/lib/ldb/tests/ldb_lmdb_size_test.c new file mode 100644 index 0000000..95eba87 --- /dev/null +++ b/lib/ldb/tests/ldb_lmdb_size_test.c @@ -0,0 +1,249 @@ +/* + * lmdb backend specific tests for ldb + * Tests for truncated index keys + * + * Copyright (C) Andrew Bartlett 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 . + * + */ + +/* + * These tests confirm that database sizes of > 4GB are supported + * Due to the disk space requirement they are not run as part of the normal + * self test runs. + * + * Setup and tear down code copied from ldb_mod_op_test.c + */ + +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + * + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + + +#define TEST_BE "mdb" + +struct ldbtest_ctx { + struct tevent_context *ev; + struct ldb_context *ldb; + + const char *dbfile; + const char *lockfile; /* lockfile is separate */ + + const char *dbpath; +}; + +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(); + } +} + +static int ldbtest_noconn_setup(void **state) +{ + struct ldbtest_ctx *test_ctx; + + 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->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->dbpath = talloc_asprintf(test_ctx, + TEST_BE"://%s", test_ctx->dbfile); + assert_non_null(test_ctx->dbpath); + + unlink_old_db(test_ctx); + *state = test_ctx; + return 0; +} + +static int ldbtest_noconn_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; +} + +static int ldbtest_setup(void **state) +{ + struct ldbtest_ctx *test_ctx; + int ret; + /* + * We need to to set GUID index mode as it's required now required + * by LDB + */ + struct ldb_ldif *ldif; + const char *index_ldif = + "dn: @INDEXLIST\n" + "@IDXGUID: objectUUID\n" + "@IDX_DN_GUID: GUID\n" + "\n"; + /* + * Set the lmdb map size to 8Gb + */ + const char *options[] = {"lmdb_env_size:8589934592", NULL}; + + ldbtest_noconn_setup((void **) &test_ctx); + + + ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, options); + assert_int_equal(ret, 0); + + while ((ldif = ldb_ldif_read_string(test_ctx->ldb, &index_ldif))) { + ret = ldb_add(test_ctx->ldb, ldif->msg); + assert_int_equal(ret, LDB_SUCCESS); + } + + *state = test_ctx; + return 0; +} + +static int ldbtest_teardown(void **state) +{ + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + ldbtest_noconn_teardown((void **) &test_ctx); + return 0; +} + +static void test_db_size_gt_4GB(void **state) +{ + int ret, x; + struct ldb_message *msg; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + const int MB = 1024 * 1024; + char *blob = NULL; + + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + + blob = talloc_zero_size(tmp_ctx, (MB + 1)); + assert_non_null(blob); + memset(blob, 'x', MB); + + + /* + * Write 6144 1Mb records to the database, this will require more than + * 4GiB of disk space + */ + for (x = 0; x < 6144; x++) { + char uuid[24]; + msg = ldb_msg_new(tmp_ctx); + assert_non_null(msg); + + /* + * Generate a unique dn for each record + */ + msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=test%d", x); + assert_non_null(msg->dn); + + /* + * Generate a unique uuid for each added record + */ + sprintf(uuid, "000000000000%04d", x); + ret = ldb_msg_add_string(msg, "objectUUID", uuid); + assert_int_equal(ret, 0); + + ldb_transaction_start(test_ctx->ldb); + ret = ldb_msg_add_string(msg, "blob", blob); + assert_int_equal(ret, 0); + + ret = ldb_add(test_ctx->ldb, msg); + assert_int_equal(ret, 0); + ldb_transaction_commit(test_ctx->ldb); + + TALLOC_FREE(msg); + } + talloc_free(tmp_ctx); + { + struct stat s; + ret = stat(test_ctx->dbfile, &s); + assert_int_equal(ret, 0); + /* + * There should have been at least 6GiB written to disk + */ + assert_true(s.st_size > (6144LL * MB)); + } +} + +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + test_db_size_gt_4GB, + ldbtest_setup, + ldbtest_teardown), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/ldb/tests/ldb_lmdb_test.c b/lib/ldb/tests/ldb_lmdb_test.c new file mode 100644 index 0000000..798a191 --- /dev/null +++ b/lib/ldb/tests/ldb_lmdb_test.c @@ -0,0 +1,590 @@ +/* + * lmdb backend specific tests for ldb + * + * Copyright (C) Andrew Bartlett 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 . + * + */ + +/* + * lmdb backend specific tests for ldb + * + * Setup and tear down code copied from ldb_mod_op_test.c + */ + +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + * + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../ldb_tdb/ldb_tdb.h" +#include "../ldb_mdb/ldb_mdb.h" +#include "../ldb_key_value/ldb_kv.h" + +#define TEST_BE "mdb" + +#define LMDB_MAX_KEY_SIZE 511 + +struct ldbtest_ctx { + struct tevent_context *ev; + struct ldb_context *ldb; + + const char *dbfile; + const char *lockfile; /* lockfile is separate */ + + const char *dbpath; +}; + +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(); + } +} + +static int ldbtest_noconn_setup(void **state) +{ + struct ldbtest_ctx *test_ctx; + + 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->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->dbpath = talloc_asprintf(test_ctx, + TEST_BE"://%s", test_ctx->dbfile); + assert_non_null(test_ctx->dbpath); + + unlink_old_db(test_ctx); + *state = test_ctx; + return 0; +} + +static int ldbtest_noconn_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; +} + +static int ldbtest_setup(void **state) +{ + struct ldbtest_ctx *test_ctx; + int ret; + struct ldb_ldif *ldif; + const char *index_ldif = \ + "dn: @INDEXLIST\n" + "@IDXGUID: objectUUID\n" + "@IDX_DN_GUID: GUID\n" + "\n"; + + ldbtest_noconn_setup((void **) &test_ctx); + + ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, 0); + + while ((ldif = ldb_ldif_read_string(test_ctx->ldb, &index_ldif))) { + ret = ldb_add(test_ctx->ldb, ldif->msg); + assert_int_equal(ret, LDB_SUCCESS); + } + *state = test_ctx; + return 0; +} + +static int ldbtest_teardown(void **state) +{ + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + ldbtest_noconn_teardown((void **) &test_ctx); + return 0; +} + +static void test_ldb_add_key_len_gt_max(void **state) +{ + int ret; + int xs_size = 0; + struct ldb_message *msg; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + char *xs = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + msg = ldb_msg_new(tmp_ctx); + assert_non_null(msg); + + /* + * The zero terminator is part of the key if we were not in + * GUID mode + */ + + xs_size = LMDB_MAX_KEY_SIZE - 7; /* "dn=dc=" and the zero terminator */ + xs_size += 1; /* want key on char too long */ + xs = talloc_zero_size(tmp_ctx, (xs_size + 1)); + memset(xs, 'x', xs_size); + + msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=%s", xs); + assert_non_null(msg->dn); + + ret = ldb_msg_add_string(msg, "cn", "test_cn_val"); + assert_int_equal(ret, 0); + + ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcdef"); + assert_int_equal(ret, 0); + + ret = ldb_add(test_ctx->ldb, msg); + assert_int_equal(ret, LDB_SUCCESS); + + talloc_free(tmp_ctx); +} + +static void test_ldb_add_key_len_2x_gt_max(void **state) +{ + int ret; + int xs_size = 0; + struct ldb_message *msg; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + char *xs = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + msg = ldb_msg_new(tmp_ctx); + assert_non_null(msg); + + /* + * The zero terminator is part of the key if we were not in + * GUID mode + */ + + xs_size = 2 * LMDB_MAX_KEY_SIZE; + xs = talloc_zero_size(tmp_ctx, (xs_size + 1)); + memset(xs, 'x', xs_size); + + msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=%s", xs); + assert_non_null(msg->dn); + + ret = ldb_msg_add_string(msg, "cn", "test_cn_val"); + assert_int_equal(ret, 0); + + ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcdef"); + assert_int_equal(ret, 0); + + ret = ldb_add(test_ctx->ldb, msg); + assert_int_equal(ret, LDB_SUCCESS); + + talloc_free(tmp_ctx); +} + +static void test_ldb_add_key_len_eq_max(void **state) +{ + int ret; + int xs_size = 0; + struct ldb_message *msg; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + char *xs = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + msg = ldb_msg_new(tmp_ctx); + assert_non_null(msg); + + /* + * The zero terminator is part of the key if we were not in + * GUID mode + */ + + xs_size = LMDB_MAX_KEY_SIZE - 7; /* "dn=dc=" and the zero terminator */ + xs = talloc_zero_size(tmp_ctx, (xs_size + 1)); + memset(xs, 'x', xs_size); + + msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=%s", xs); + assert_non_null(msg->dn); + + ret = ldb_msg_add_string(msg, "cn", "test_cn_val"); + assert_int_equal(ret, 0); + + ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcdef"); + assert_int_equal(ret, 0); + + ret = ldb_add(test_ctx->ldb, msg); + assert_int_equal(ret, 0); + + talloc_free(tmp_ctx); +} + +static int ldbtest_setup_noguid(void **state) +{ + struct ldbtest_ctx *test_ctx; + int ret; + + ldbtest_noconn_setup((void **) &test_ctx); + + ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, 0); + + *state = test_ctx; + return 0; +} + +static void test_ldb_add_special_key_len_gt_max(void **state) +{ + int ret; + int xs_size = 0; + struct ldb_message *msg; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + char *xs = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + msg = ldb_msg_new(tmp_ctx); + assert_non_null(msg); + + /* + * The zero terminator is part of the key if we were not in + * GUID mode + */ + + xs_size = LMDB_MAX_KEY_SIZE - 5; /* "dn=@" and the zero terminator */ + xs_size += 1; /* want key on char too long */ + xs = talloc_zero_size(tmp_ctx, (xs_size + 1)); + memset(xs, 'x', xs_size); + + msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "@%s", xs); + assert_non_null(msg->dn); + + ret = ldb_msg_add_string(msg, "cn", "test_cn_val"); + assert_int_equal(ret, 0); + + ret = ldb_add(test_ctx->ldb, msg); + assert_int_equal(ret, LDB_ERR_PROTOCOL_ERROR); + + talloc_free(tmp_ctx); +} + +static void test_ldb_add_special_key_len_eq_max(void **state) +{ + int ret; + int xs_size = 0; + struct ldb_message *msg; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + char *xs = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + msg = ldb_msg_new(tmp_ctx); + assert_non_null(msg); + + /* + * The zero terminator is part of the key if we were not in + * GUID mode + */ + + xs_size = LMDB_MAX_KEY_SIZE - 5; /* "dn=@" and the zero terminator */ + xs = talloc_zero_size(tmp_ctx, (xs_size + 1)); + memset(xs, 'x', xs_size); + + msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "@%s", xs); + assert_non_null(msg->dn); + + ret = ldb_msg_add_string(msg, "cn", "test_cn_val"); + assert_int_equal(ret, 0); + + ret = ldb_add(test_ctx->ldb, msg); + assert_int_equal(ret, LDB_SUCCESS); + + talloc_free(tmp_ctx); +} + +static void test_ldb_add_dn_no_guid_mode(void **state) +{ + int ret; + int xs_size = 0; + struct ldb_message *msg; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + char *xs = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + msg = ldb_msg_new(tmp_ctx); + assert_non_null(msg); + + /* + * The zero terminator is part of the key if we were not in + * GUID mode + */ + + xs_size = LMDB_MAX_KEY_SIZE - 7; /* "dn=dc=" and the zero terminator */ + xs_size += 1; /* want key on char too long */ + xs = talloc_zero_size(tmp_ctx, (xs_size + 1)); + memset(xs, 'x', xs_size); + + msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=%s", xs); + assert_non_null(msg->dn); + + ret = ldb_msg_add_string(msg, "cn", "test_cn_val"); + assert_int_equal(ret, 0); + + ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcdef"); + assert_int_equal(ret, 0); + + ret = ldb_add(test_ctx->ldb, msg); + assert_int_equal(ret, LDB_ERR_UNWILLING_TO_PERFORM); + + talloc_free(tmp_ctx); +} + +static struct MDB_env *get_mdb_env(struct ldb_context *ldb) +{ + void *data = NULL; + struct ldb_kv_private *ldb_kv = NULL; + struct lmdb_private *lmdb = NULL; + struct MDB_env *env = NULL; + + data = ldb_module_get_private(ldb->modules); + assert_non_null(data); + + ldb_kv = talloc_get_type(data, struct ldb_kv_private); + assert_non_null(ldb_kv); + + lmdb = ldb_kv->lmdb_private; + assert_non_null(lmdb); + + env = lmdb->env; + assert_non_null(env); + + return env; +} + +static void test_multiple_opens(void **state) +{ + struct ldb_context *ldb1 = NULL; + struct ldb_context *ldb2 = NULL; + struct ldb_context *ldb3 = NULL; + struct MDB_env *env1 = NULL; + struct MDB_env *env2 = NULL; + struct MDB_env *env3 = NULL; + int ret; + struct ldbtest_ctx *test_ctx = NULL; + + test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx); + + /* + * Open the database again + */ + ldb1 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb1, test_ctx->dbpath, LDB_FLG_RDONLY, NULL); + assert_int_equal(ret, 0); + + ldb2 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb2, test_ctx->dbpath, LDB_FLG_RDONLY, NULL); + assert_int_equal(ret, 0); + + ldb3 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb3, test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, 0); + /* + * We now have 3 ldb's open pointing to the same on disk database + * they should all share the same MDB_env + */ + env1 = get_mdb_env(ldb1); + env2 = get_mdb_env(ldb2); + env3 = get_mdb_env(ldb3); + + assert_ptr_equal(env1, env2); + assert_ptr_equal(env1, env3); +} + +static void test_multiple_opens_across_fork(void **state) +{ + struct ldb_context *ldb1 = NULL; + struct ldb_context *ldb2 = NULL; + struct MDB_env *env1 = NULL; + struct MDB_env *env2 = NULL; + int ret; + struct ldbtest_ctx *test_ctx = NULL; + int pipes[2]; + char buf[2]; + int wstatus; + pid_t pid, child_pid; + + test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx); + + /* + * Open the database again + */ + ldb1 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb1, test_ctx->dbpath, LDB_FLG_RDONLY, NULL); + assert_int_equal(ret, 0); + + ldb2 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb2, test_ctx->dbpath, LDB_FLG_RDONLY, NULL); + assert_int_equal(ret, 0); + + env1 = get_mdb_env(ldb1); + env2 = get_mdb_env(ldb2); + + ret = pipe(pipes); + assert_int_equal(ret, 0); + + child_pid = fork(); + if (child_pid == 0) { + struct ldb_context *ldb3 = NULL; + struct MDB_env *env3 = NULL; + + close(pipes[0]); + ldb3 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb3, test_ctx->dbpath, 0, NULL); + if (ret != 0) { + print_error(__location__": ldb_connect returned (%d)\n", + ret); + exit(ret); + } + env3 = get_mdb_env(ldb3); + if (env1 != env2) { + print_error(__location__": env1 != env2\n"); + exit(LDB_ERR_OPERATIONS_ERROR); + } + if (env1 == env3) { + print_error(__location__": env1 == env3\n"); + exit(LDB_ERR_OPERATIONS_ERROR); + } + ret = write(pipes[1], "GO", 2); + if (ret != 2) { + print_error(__location__ + " write returned (%d)", + ret); + exit(LDB_ERR_OPERATIONS_ERROR); + } + exit(LDB_SUCCESS); + } + close(pipes[1]); + ret = read(pipes[0], buf, 2); + assert_int_equal(ret, 2); + + pid = waitpid(child_pid, &wstatus, 0); + assert_int_equal(pid, child_pid); + + assert_true(WIFEXITED(wstatus)); + + assert_int_equal(WEXITSTATUS(wstatus), 0); +} + +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + test_ldb_add_key_len_eq_max, + ldbtest_setup, + ldbtest_teardown), + cmocka_unit_test_setup_teardown( + test_ldb_add_key_len_gt_max, + ldbtest_setup, + ldbtest_teardown), + cmocka_unit_test_setup_teardown( + test_ldb_add_key_len_2x_gt_max, + ldbtest_setup, + ldbtest_teardown), + cmocka_unit_test_setup_teardown( + test_ldb_add_special_key_len_eq_max, + ldbtest_setup_noguid, + ldbtest_teardown), + cmocka_unit_test_setup_teardown( + test_ldb_add_special_key_len_gt_max, + ldbtest_setup_noguid, + ldbtest_teardown), + cmocka_unit_test_setup_teardown( + test_ldb_add_dn_no_guid_mode, + ldbtest_setup_noguid, + ldbtest_teardown), + cmocka_unit_test_setup_teardown( + test_multiple_opens, + ldbtest_setup, + ldbtest_teardown), + cmocka_unit_test_setup_teardown( + test_multiple_opens_across_fork, + ldbtest_setup, + ldbtest_teardown), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/ldb/tests/ldb_match_test.c b/lib/ldb/tests/ldb_match_test.c new file mode 100644 index 0000000..1bb56d0 --- /dev/null +++ b/lib/ldb/tests/ldb_match_test.c @@ -0,0 +1,313 @@ +/* + * Tests exercising the ldb match operations. + * + * + * Copyright (C) Catalyst.NET Ltd 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 . + * + */ + +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + */ +#include +#include +#include +#include +#include + +#include "../common/ldb_match.c" + +#include "../include/ldb.h" + +struct ldbtest_ctx { + struct tevent_context *ev; + struct ldb_context *ldb; +}; + +static int ldb_test_canonicalise( + struct ldb_context *ldb, + void *mem_ctx, + const struct ldb_val *in, + struct ldb_val *out) +{ + out->length = in->length; + out->data = in->data; + return 0; +} + +static int setup(void **state) +{ + struct ldbtest_ctx *test_ctx; + struct ldb_schema_syntax *syntax = NULL; + int ret; + + 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); + + syntax = talloc_zero(test_ctx, struct ldb_schema_syntax); + assert_non_null(syntax); + syntax->canonicalise_fn = ldb_test_canonicalise; + + ret = ldb_schema_attribute_add_with_syntax( + test_ctx->ldb, "a", LDB_ATTR_FLAG_FIXED, syntax); + assert_int_equal(LDB_SUCCESS, ret); + + *state = test_ctx; + return 0; +} + +static int teardown(void **state) +{ + talloc_free(*state); + return 0; +} + +static void escape_string(uint8_t *buf, size_t buflen, + const uint8_t *s, size_t len) +{ + size_t i; + size_t j = 0; + for (i = 0; i < len; i++) { + if (j == buflen - 1) { + goto fin; + } + if (s[i] >= 0x20) { + buf[j] = s[i]; + j++; + } else { + if (j >= buflen - 4) { + goto fin; + } + /* utf-8 control char representation */ + buf[j] = 0xE2; + buf[j + 1] = 0x90; + buf[j + 2] = 0x80 + s[i]; + j+= 3; + } + } +fin: + buf[j] = 0; +} + + +/* + * The wild card pattern "attribute=*" is parsed as an LDB_OP_PRESENT operation + * rather than a LDB_OP_???? + * + * This test serves to document that behaviour, and to confirm that + * ldb_wildcard_compare handles this case appropriately. + */ +static void test_wildcard_match_star(void **state) +{ + struct ldbtest_ctx *ctx = *state; + bool matched = false; + int ret; + + uint8_t value[] = "The value.......end"; + struct ldb_val val = { + .data = value, + .length = (sizeof(value)) + }; + struct ldb_parse_tree *tree = ldb_parse_tree(ctx, "a=*"); + assert_non_null(tree); + + ret = ldb_wildcard_compare(ctx->ldb, tree, val, &matched); + assert_false(matched); + assert_int_equal(LDB_ERR_INAPPROPRIATE_MATCHING, ret); +} + +/* + * Test basic wild card matching + * + */ +struct wildcard_test { + uint8_t *val; + size_t val_size; + const char *search; + bool should_match; + bool fold; +}; + +/* + * Q: Why this macro rather than plain struct values? + * A: So we can get the size of the const char[] value while it is still a + * true array, not a pointer. + * + * Q: but why not just use strlen? + * A: so values can contain '\0', which we supposedly allow. + */ + +#define TEST_ENTRY(val, search, should_match, fold) \ + { \ + (uint8_t*)discard_const(val), \ + sizeof(val) - 1, \ + search, \ + should_match, \ + fold \ + } + +static void test_wildcard_match(void **state) +{ + struct ldbtest_ctx *ctx = *state; + size_t failed = 0; + size_t i; + struct wildcard_test tests[] = { + TEST_ENTRY(" 1 0", "1*0*", true, true), + TEST_ENTRY(" 1 0", "1 *0", true, true), + TEST_ENTRY(" 1 0", "*1 0", true, true), + TEST_ENTRY("1 0", "*1 0", true, true), + TEST_ENTRY("The value.......end", "*end", true, true), + TEST_ENTRY("The value.......end", "*fend", false, true), + TEST_ENTRY("The value.......end", "*eel", false, true), + TEST_ENTRY("The value.......end", "*d", true, true), + TEST_ENTRY("The value.......end", "*D*", true, true), + TEST_ENTRY("The value.......end", "*e*d*", true, true), + TEST_ENTRY("end", "*e*d*", true, true), + TEST_ENTRY("end", " *e*d*", true, true), + TEST_ENTRY("1.0.0.0.0.0.0.0aaaaaaaaaaaa", "*aaaaa", true, true), + TEST_ENTRY("1.0..0.0.0.0.0.0.0aAaaaAAAAAAA", "*a", true, true), + TEST_ENTRY("1.0.0.0.0.0.0.0.0.0.0aaaa", "*aaaaa", false, true), + TEST_ENTRY("1.0.0.0.0.0.0.0.0.0.0", "*0.0", true, true), + TEST_ENTRY("1.0.0.0.0.0.0.0.0.0.0", "*0.0.0", true, true), + TEST_ENTRY("1.0.0.0.0.0.0.0.0.0", "1*0*0*0*0*0*0*0*0*0", true, + true), + TEST_ENTRY("1.0.0.0.0.0.0.0.0", "1*0*0*0*0*0*0*0*0*0", false, + true), + TEST_ENTRY("1.0.0.0.000.0.0.0.0", "1*0*0*0*0*0*0*0*0*0", true, + true), + TEST_ENTRY("1\n0\r0\t000.0.0.0.0", "1*0*0*0*0*0*0*0*0", true, + true), + /* + * We allow NUL bytes and redundant spaces in non-casefolding + * syntaxes. + */ + TEST_ENTRY(" 1 0", "*1 0", true, false), + TEST_ENTRY(" 1 0", "*1 0", true, false), + TEST_ENTRY("1 0", "*1 0", false, false), + TEST_ENTRY("1\x00 x", "1*x", true, false), + TEST_ENTRY("1\x00 x", "*x", true, false), + TEST_ENTRY("1\x00 x", "*x*", true, false), + TEST_ENTRY("1\x00 x", "* *", true, false), + TEST_ENTRY("1\x00 x", "1*", true, false), + TEST_ENTRY("1\x00 b* x", "1*b*", true, false), + TEST_ENTRY("1.0..0.0.0.0.0.0.0aAaaaAAAAAAA", "*a", false, false), + }; + + for (i = 0; i < ARRAY_SIZE(tests); i++) { + bool matched; + int ret; + struct ldb_val val = { + .data = (uint8_t *)tests[i].val, + .length = tests[i].val_size + }; + const char *attr = tests[i].fold ? "objectclass" : "birthLocation"; + const char *s = talloc_asprintf(ctx, "%s=%s", + attr, tests[i].search); + struct ldb_parse_tree *tree = ldb_parse_tree(ctx, s); + assert_non_null(tree); + ret = ldb_wildcard_compare(ctx->ldb, tree, val, &matched); + if (ret != LDB_SUCCESS) { + uint8_t buf[100]; + escape_string(buf, sizeof(buf), + tests[i].val, tests[i].val_size); + print_error("%zu val: «%s», search «%s» FAILED with %d\n", + i, buf, tests[i].search, ret); + failed++; + } + if (matched != tests[i].should_match) { + uint8_t buf[100]; + escape_string(buf, sizeof(buf), + tests[i].val, tests[i].val_size); + print_error("%zu val: «%s», search «%s» should %s\n", + i, buf, tests[i].search, + matched ? "not match" : "match"); + failed++; + } + } + if (failed != 0) { + fail_msg("wrong results for %zu/%zu wildcard searches\n", + failed, ARRAY_SIZE(tests)); + } +} + +#undef TEST_ENTRY + + +/* + * ldb_handler_copy and ldb_val_dup over allocate by one and add a trailing '\0' + * to the data, to make them safe to use the C string functions on. + * + * However testing for the trailing '\0' is not the correct way to test for + * the end of a value, the length should be checked instead. + */ +static void test_wildcard_match_end_condition(void **state) +{ + struct ldbtest_ctx *ctx = *state; + bool matched = false; + + uint8_t value[] = "hellomynameisbobx"; + struct ldb_val val = { + .data = talloc_memdup(NULL, value, sizeof(value)), + .length = (sizeof(value) - 2) + }; + struct ldb_parse_tree *tree = ldb_parse_tree(ctx, "a=*hello*mynameis*bob"); + assert_non_null(tree); + + ldb_wildcard_compare(ctx->ldb, tree, val, &matched); + assert_true(matched); +} + +/* + * Note: to run under valgrind use: + * valgrind \ + * --suppressions=lib/ldb/tests/ldb_match_test.valgrind \ + * bin/ldb_match_test + */ +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + test_wildcard_match_star, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_wildcard_match, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_wildcard_match_end_condition, + setup, + teardown), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/ldb/tests/ldb_match_test.valgrind b/lib/ldb/tests/ldb_match_test.valgrind new file mode 100644 index 0000000..660bd5a --- /dev/null +++ b/lib/ldb/tests/ldb_match_test.valgrind @@ -0,0 +1,16 @@ +{ + Memory allocated in set-up + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:setup +} +{ + Memory allocated by ldb_init + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:ldb_init +} diff --git a/lib/ldb/tests/ldb_mod_op_test.c b/lib/ldb/tests/ldb_mod_op_test.c new file mode 100644 index 0000000..f620fc1 --- /dev/null +++ b/lib/ldb/tests/ldb_mod_op_test.c @@ -0,0 +1,4724 @@ +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + */ +#include +#include +#include +#include +#include + +#include +#include +#include + +#define TEVENT_DEPRECATED 1 +#include + +#include +#include +#include +#include +#include + +#include + + +#define DEFAULT_BE "tdb" + +#ifndef TEST_BE +#define TEST_BE DEFAULT_BE +#endif /* TEST_BE */ + +#ifdef TEST_LMDB +#include "lmdb.h" +#include "../ldb_tdb/ldb_tdb.h" +#include "../ldb_mdb/ldb_mdb.h" +#endif + +struct ldbtest_ctx { + struct tevent_context *ev; + struct ldb_context *ldb; + + const char *dbfile; + const char *lockfile; /* lockfile is separate */ + + const char *dbpath; + char *debug_string; +}; + +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(); + } +} + +static int ldbtest_noconn_setup(void **state) +{ + struct ldbtest_ctx *test_ctx; + + 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->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->dbpath = talloc_asprintf(test_ctx, + TEST_BE"://%s", test_ctx->dbfile); + assert_non_null(test_ctx->dbpath); + + unlink_old_db(test_ctx); + *state = test_ctx; + return 0; +} + +static int ldbtest_noconn_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; +} + +static void test_connect(void **state) +{ + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + int ret; + + ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, 0); +} + +static struct ldb_message *get_test_ldb_message(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb) +{ + struct ldb_message *msg = ldb_msg_new(mem_ctx); + int ret; + assert_non_null(msg); + + msg->dn = ldb_dn_new(msg, ldb, "dc=samba,dc=org"); + assert_non_null(msg->dn); + ret = ldb_msg_add_string(msg, "public", "key"); + assert_int_equal(ret, LDB_SUCCESS); + ret = ldb_msg_add_string(msg, "supersecret", "password"); + assert_int_equal(ret, LDB_SUCCESS); + ret = ldb_msg_add_string(msg, "binary", "\xff\xff\0"); + assert_int_equal(ret, LDB_SUCCESS); + return msg; +} + +static void test_ldif_message(void **state) +{ + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + char *got_ldif; + const char *expected_ldif = + "dn: dc=samba,dc=org\n" + "changetype: add\n" + "public: key\n" + "supersecret: password\n" + "binary:: //8=\n" + "\n"; + + struct ldb_message *msg = get_test_ldb_message(test_ctx, + test_ctx->ldb); + + got_ldif = ldb_ldif_message_string(test_ctx->ldb, + test_ctx, + LDB_CHANGETYPE_ADD, + msg); + assert_string_equal(got_ldif, expected_ldif); + TALLOC_FREE(got_ldif); +} + +static void test_ldif_message_redacted(void **state) +{ + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + int ret; + char *got_ldif; + const char *expected_ldif = + "dn: dc=samba,dc=org\n" + "changetype: add\n" + "public: key\n" + "# supersecret::: REDACTED SECRET ATTRIBUTE\n" + "binary:: //8=\n" + "\n"; + + const char *secret_attrs[] = { + "supersecret", + NULL + }; + + struct ldb_message *msg = ldb_msg_new(test_ctx); + + ldb_set_opaque(test_ctx->ldb, + LDB_SECRET_ATTRIBUTE_LIST_OPAQUE, + secret_attrs); + + assert_non_null(msg); + + msg->dn = ldb_dn_new(msg, test_ctx->ldb, "dc=samba,dc=org"); + ret = ldb_msg_add_string(msg, "public", "key"); + assert_int_equal(ret, LDB_SUCCESS); + ret = ldb_msg_add_string(msg, "supersecret", "password"); + assert_int_equal(ret, LDB_SUCCESS); + ret = ldb_msg_add_string(msg, "binary", "\xff\xff\0"); + assert_int_equal(ret, LDB_SUCCESS); + got_ldif = ldb_ldif_message_redacted_string(test_ctx->ldb, + test_ctx, + LDB_CHANGETYPE_ADD, + msg); + assert_string_equal(got_ldif, expected_ldif); + TALLOC_FREE(got_ldif); + assert_int_equal(ret, 0); +} + +static int ldbtest_setup(void **state) +{ + struct ldbtest_ctx *test_ctx; + struct ldb_ldif *ldif; +#ifdef GUID_IDX + const char *index_ldif = \ + "dn: @INDEXLIST\n" + "@IDXGUID: objectUUID\n" + "@IDX_DN_GUID: GUID\n" + "\n"; +#else + const char *index_ldif = "\n"; +#endif + int ret; + + ldbtest_noconn_setup((void **) &test_ctx); + + ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, 0); + + while ((ldif = ldb_ldif_read_string(test_ctx->ldb, &index_ldif))) { + ret = ldb_add(test_ctx->ldb, ldif->msg); + assert_int_equal(ret, LDB_SUCCESS); + } + *state = test_ctx; + return 0; +} + +static int ldbtest_teardown(void **state) +{ + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + ldbtest_noconn_teardown((void **) &test_ctx); + return 0; +} + +static void test_ldb_add(void **state) +{ + int ret; + struct ldb_message *msg; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + msg = ldb_msg_new(tmp_ctx); + assert_non_null(msg); + + msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=test"); + assert_non_null(msg->dn); + + ret = ldb_msg_add_string(msg, "cn", "test_cn_val"); + assert_int_equal(ret, 0); + + ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcdef"); + assert_int_equal(ret, 0); + + ret = ldb_add(test_ctx->ldb, msg); + assert_int_equal(ret, 0); + + talloc_free(tmp_ctx); +} + +static void test_ldb_search(void **state) +{ + int ret; + struct ldb_message *msg; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + TALLOC_CTX *tmp_ctx; + struct ldb_dn *basedn; + struct ldb_dn *basedn2; + struct ldb_result *result = NULL; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + basedn = ldb_dn_new_fmt(tmp_ctx, test_ctx->ldb, "dc=test"); + assert_non_null(basedn); + + ret = ldb_search(test_ctx->ldb, tmp_ctx, &result, basedn, + LDB_SCOPE_BASE, NULL, NULL); + assert_int_equal(ret, 0); + assert_non_null(result); + assert_int_equal(result->count, 0); + + msg = ldb_msg_new(tmp_ctx); + assert_non_null(msg); + + msg->dn = basedn; + assert_non_null(msg->dn); + + ret = ldb_msg_add_string(msg, "cn", "test_cn_val1"); + assert_int_equal(ret, 0); + + ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcde1"); + assert_int_equal(ret, 0); + + ret = ldb_add(test_ctx->ldb, msg); + assert_int_equal(ret, 0); + + basedn2 = ldb_dn_new_fmt(tmp_ctx, test_ctx->ldb, "dc=test2"); + assert_non_null(basedn2); + + msg = ldb_msg_new(tmp_ctx); + assert_non_null(msg); + + msg->dn = basedn2; + assert_non_null(msg->dn); + + ret = ldb_msg_add_string(msg, "cn", "test_cn_val2"); + assert_int_equal(ret, 0); + + ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcde2"); + assert_int_equal(ret, 0); + + ret = ldb_add(test_ctx->ldb, msg); + assert_int_equal(ret, 0); + + ret = ldb_search(test_ctx->ldb, tmp_ctx, &result, basedn, + LDB_SCOPE_BASE, NULL, NULL); + assert_int_equal(ret, 0); + assert_non_null(result); + assert_int_equal(result->count, 1); + assert_string_equal(ldb_dn_get_linearized(result->msgs[0]->dn), + ldb_dn_get_linearized(basedn)); + + ret = ldb_search(test_ctx->ldb, tmp_ctx, &result, basedn2, + LDB_SCOPE_BASE, NULL, NULL); + assert_int_equal(ret, 0); + assert_non_null(result); + assert_int_equal(result->count, 1); + assert_string_equal(ldb_dn_get_linearized(result->msgs[0]->dn), + ldb_dn_get_linearized(basedn2)); + + talloc_free(tmp_ctx); +} + +static int base_search_count(struct ldbtest_ctx *test_ctx, const char *entry_dn) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *basedn; + struct ldb_result *result = NULL; + int ret; + int count; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + basedn = ldb_dn_new_fmt(tmp_ctx, test_ctx->ldb, "%s", entry_dn); + assert_non_null(basedn); + + ret = ldb_search(test_ctx->ldb, tmp_ctx, &result, basedn, + LDB_SCOPE_BASE, NULL, NULL); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(result); + + count = result->count; + talloc_free(tmp_ctx); + return count; +} + +static int sub_search_count(struct ldbtest_ctx *test_ctx, + const char *base_dn, + const char *filter) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *basedn; + struct ldb_result *result = NULL; + int ret; + int count; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + basedn = ldb_dn_new_fmt(tmp_ctx, test_ctx->ldb, "%s", base_dn); + assert_non_null(basedn); + + ret = ldb_search(test_ctx->ldb, tmp_ctx, &result, basedn, + LDB_SCOPE_SUBTREE, NULL, "%s", filter); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(result); + + count = result->count; + talloc_free(tmp_ctx); + return count; +} + +/* In general it would be better if utility test functions didn't assert + * but only returned a value, then assert in the test shows correct + * line + */ +static void assert_dn_exists(struct ldbtest_ctx *test_ctx, + const char *entry_dn) +{ + int count; + + count = base_search_count(test_ctx, entry_dn); + assert_int_equal(count, 1); +} + +static void assert_dn_doesnt_exist(struct ldbtest_ctx *test_ctx, + const char *entry_dn) +{ + int count; + + count = base_search_count(test_ctx, entry_dn); + assert_int_equal(count, 0); +} + +static void add_dn_with_cn(struct ldbtest_ctx *test_ctx, + struct ldb_dn *dn, + const char *cn_value, + const char *uuid_value) +{ + int ret; + TALLOC_CTX *tmp_ctx; + struct ldb_message *msg; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + assert_dn_doesnt_exist(test_ctx, + ldb_dn_get_linearized(dn)); + + msg = ldb_msg_new(tmp_ctx); + assert_non_null(msg); + msg->dn = dn; + + ret = ldb_msg_add_string(msg, "cn", cn_value); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_msg_add_string(msg, "objectUUID", uuid_value); + assert_int_equal(ret, 0); + + ret = ldb_add(test_ctx->ldb, msg); + assert_int_equal(ret, LDB_SUCCESS); + + assert_dn_exists(test_ctx, + ldb_dn_get_linearized(dn)); + talloc_free(tmp_ctx); +} + +static void test_ldb_del(void **state) +{ + int ret; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + const char *basedn = "dc=ldb_del_test"; + struct ldb_dn *dn; + + dn = ldb_dn_new_fmt(test_ctx, test_ctx->ldb, "%s", basedn); + assert_non_null(dn); + + add_dn_with_cn(test_ctx, dn, + "test_del_cn_val", + "0123456789abcdef"); + + ret = ldb_delete(test_ctx->ldb, dn); + assert_int_equal(ret, LDB_SUCCESS); + + assert_dn_doesnt_exist(test_ctx, basedn); +} + +static void test_ldb_del_noexist(void **state) +{ + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + struct ldb_dn *basedn; + int ret; + + basedn = ldb_dn_new(test_ctx, test_ctx->ldb, "dc=nosuchplace"); + assert_non_null(basedn); + + ret = ldb_delete(test_ctx->ldb, basedn); + assert_int_equal(ret, LDB_ERR_NO_SUCH_OBJECT); +} + +static void test_ldb_handle(void **state) +{ + int ret; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + TALLOC_CTX *tmp_ctx; + struct ldb_dn *basedn; + struct ldb_request *request = NULL; + struct ldb_request *request2 = NULL; + struct ldb_result *res = NULL; + const char *attrs[] = { "cn", NULL }; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + basedn = ldb_dn_new_fmt(tmp_ctx, test_ctx->ldb, "dc=test"); + assert_non_null(basedn); + + res = talloc_zero(tmp_ctx, struct ldb_result); + assert_non_null(res); + + ret = ldb_build_search_req(&request, test_ctx->ldb, tmp_ctx, + basedn, LDB_SCOPE_BASE, + NULL, attrs, NULL, res, + ldb_search_default_callback, + NULL); + assert_int_equal(ret, 0); + + /* We are against ldb_tdb, so expect private event contexts */ + assert_ptr_not_equal(ldb_handle_get_event_context(request->handle), + ldb_get_event_context(test_ctx->ldb)); + + ret = ldb_build_search_req(&request2, test_ctx->ldb, tmp_ctx, + basedn, LDB_SCOPE_BASE, + NULL, attrs, NULL, res, + ldb_search_default_callback, + request); + assert_int_equal(ret, 0); + + /* Expect that same event context will be chained */ + assert_ptr_equal(ldb_handle_get_event_context(request->handle), + ldb_handle_get_event_context(request2->handle)); + + /* Now force this to use the global context */ + ldb_handle_use_global_event_context(request2->handle); + assert_ptr_equal(ldb_handle_get_event_context(request2->handle), + ldb_get_event_context(test_ctx->ldb)); + + talloc_free(tmp_ctx); +} + +static void test_ldb_build_search_req(void **state) +{ + int ret; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + TALLOC_CTX *tmp_ctx; + struct ldb_dn *basedn; + struct ldb_request *request = NULL; + struct ldb_request *request2 = NULL; + struct ldb_result *res = NULL; + const char *attrs[] = { "cn", NULL }; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + basedn = ldb_dn_new_fmt(tmp_ctx, test_ctx->ldb, "dc=test"); + assert_non_null(basedn); + + res = talloc_zero(tmp_ctx, struct ldb_result); + assert_non_null(res); + + ret = ldb_build_search_req(&request, test_ctx->ldb, tmp_ctx, + basedn, LDB_SCOPE_BASE, + NULL, attrs, NULL, res, + ldb_search_default_callback, + NULL); + assert_int_equal(ret, 0); + + assert_int_equal(request->operation, LDB_SEARCH); + assert_ptr_equal(request->op.search.base, basedn); + assert_int_equal(request->op.search.scope, LDB_SCOPE_BASE); + assert_non_null(request->op.search.tree); + assert_ptr_equal(request->op.search.attrs, attrs); + assert_ptr_equal(request->context, res); + assert_ptr_equal(request->callback, ldb_search_default_callback); + + ret = ldb_build_search_req(&request2, test_ctx->ldb, tmp_ctx, + basedn, LDB_SCOPE_BASE, + NULL, attrs, NULL, res, + ldb_search_default_callback, + request); + assert_int_equal(ret, 0); + assert_ptr_equal(request, request2->handle->parent); + assert_int_equal(request->starttime, request2->starttime); + assert_int_equal(request->timeout, request2->timeout); + + talloc_free(tmp_ctx); +} + +static void add_keyval(struct ldbtest_ctx *test_ctx, + const char *key, + const char *val, + const char *uuid) +{ + int ret; + struct ldb_message *msg; + + msg = ldb_msg_new(test_ctx); + assert_non_null(msg); + + msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "%s=%s", key, val); + assert_non_null(msg->dn); + + ret = ldb_msg_add_string(msg, key, val); + assert_int_equal(ret, 0); + + ret = ldb_msg_add_string(msg, "objectUUID", uuid); + assert_int_equal(ret, 0); + + ret = ldb_add(test_ctx->ldb, msg); + assert_int_equal(ret, 0); + + talloc_free(msg); +} + +static struct ldb_result *get_keyval(struct ldbtest_ctx *test_ctx, + const char *key, + const char *val) +{ + int ret; + struct ldb_result *result; + struct ldb_dn *basedn; + + basedn = ldb_dn_new_fmt(test_ctx, test_ctx->ldb, "%s=%s", key, val); + assert_non_null(basedn); + + ret = ldb_search(test_ctx->ldb, test_ctx, &result, basedn, + LDB_SCOPE_BASE, NULL, NULL); + assert_int_equal(ret, 0); + + return result; +} + +static void test_transactions(void **state) +{ + int ret; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + struct ldb_result *res; + + /* start lev-0 transaction */ + ret = ldb_transaction_start(test_ctx->ldb); + assert_int_equal(ret, 0); + + add_keyval(test_ctx, "vegetable", "carrot", + "0123456789abcde0"); + + /* commit lev-0 transaction */ + ret = ldb_transaction_commit(test_ctx->ldb); + assert_int_equal(ret, 0); + + /* start another lev-1 nested transaction */ + ret = ldb_transaction_start(test_ctx->ldb); + assert_int_equal(ret, 0); + + add_keyval(test_ctx, "fruit", "apple", + "0123456789abcde1"); + + /* abort lev-1 nested transaction */ + ret = ldb_transaction_cancel(test_ctx->ldb); + assert_int_equal(ret, 0); + + res = get_keyval(test_ctx, "vegetable", "carrot"); + assert_non_null(res); + assert_int_equal(res->count, 1); + + res = get_keyval(test_ctx, "fruit", "apple"); + assert_non_null(res); + assert_int_equal(res->count, 0); +} + +static void test_nested_transactions(void **state) +{ + int ret; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + struct ldb_result *res; + + /* start lev-0 transaction */ + ret = ldb_transaction_start(test_ctx->ldb); + assert_int_equal(ret, 0); + + add_keyval(test_ctx, "vegetable", "carrot", + "0123456789abcde0"); + + + /* start another lev-1 nested transaction */ + ret = ldb_transaction_start(test_ctx->ldb); + assert_int_equal(ret, 0); + + add_keyval(test_ctx, "fruit", "apple", + "0123456789abcde1"); + + /* abort lev-1 nested transaction */ + ret = ldb_transaction_cancel(test_ctx->ldb); + assert_int_equal(ret, 0); + + /* commit lev-0 transaction */ + ret = ldb_transaction_commit(test_ctx->ldb); + assert_int_equal(ret, 0); + + res = get_keyval(test_ctx, "vegetable", "carrot"); + assert_non_null(res); + assert_int_equal(res->count, 1); + + /* This documents the current ldb behaviour, i.e. nested + * transactions are not supported. And the cancellation of the nested + * transaction has no effect. + */ + res = get_keyval(test_ctx, "fruit", "apple"); + assert_non_null(res); + assert_int_equal(res->count, 1); +} +struct ldb_mod_test_ctx { + struct ldbtest_ctx *ldb_test_ctx; + const char *entry_dn; +}; + +struct keyval { + const char *key; + const char *val; +}; + +static struct ldb_message *build_mod_msg(TALLOC_CTX *mem_ctx, + struct ldbtest_ctx *test_ctx, + const char *dn, + int modify_flags, + struct keyval *kvs) +{ + struct ldb_message *msg; + int ret; + int i; + + msg = ldb_msg_new(mem_ctx); + assert_non_null(msg); + + msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "%s", dn); + assert_non_null(msg->dn); + + for (i = 0; kvs[i].key != NULL; i++) { + if (modify_flags) { + ret = ldb_msg_add_empty(msg, kvs[i].key, + modify_flags, NULL); + assert_int_equal(ret, 0); + } + + if (kvs[i].val) { + ret = ldb_msg_add_string(msg, kvs[i].key, kvs[i].val); + assert_int_equal(ret, LDB_SUCCESS); + } + } + + return msg; +} + +static void ldb_test_add_data(TALLOC_CTX *mem_ctx, + struct ldbtest_ctx *ldb_test_ctx, + const char *basedn, + struct keyval *kvs) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message *msg; + struct ldb_result *result = NULL; + int ret; + + tmp_ctx = talloc_new(mem_ctx); + assert_non_null(tmp_ctx); + + msg = build_mod_msg(tmp_ctx, ldb_test_ctx, + basedn, 0, kvs); + assert_non_null(msg); + + ret = ldb_add(ldb_test_ctx->ldb, msg); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_search(ldb_test_ctx->ldb, tmp_ctx, &result, msg->dn, + LDB_SCOPE_BASE, NULL, NULL); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(result); + assert_int_equal(result->count, 1); + assert_string_equal(ldb_dn_get_linearized(result->msgs[0]->dn), + ldb_dn_get_linearized(msg->dn)); + + talloc_free(tmp_ctx); +} + +static void ldb_test_remove_data(TALLOC_CTX *mem_ctx, + struct ldbtest_ctx *ldb_test_ctx, + const char *strdn) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *basedn; + int ret; + size_t count; + + tmp_ctx = talloc_new(mem_ctx); + assert_non_null(tmp_ctx); + + basedn = ldb_dn_new_fmt(tmp_ctx, ldb_test_ctx->ldb, + "%s", strdn); + assert_non_null(basedn); + + ret = ldb_delete(ldb_test_ctx->ldb, basedn); + assert_true(ret == LDB_SUCCESS || ret == LDB_ERR_NO_SUCH_OBJECT); + + count = base_search_count(ldb_test_ctx, ldb_dn_get_linearized(basedn)); + assert_int_equal(count, 0); + + talloc_free(tmp_ctx); +} + +static void mod_test_add_data(struct ldb_mod_test_ctx *mod_test_ctx, + struct keyval *kvs) +{ + ldb_test_add_data(mod_test_ctx, + mod_test_ctx->ldb_test_ctx, + mod_test_ctx->entry_dn, + kvs); +} + +static void mod_test_remove_data(struct ldb_mod_test_ctx *mod_test_ctx) +{ + ldb_test_remove_data(mod_test_ctx, + mod_test_ctx->ldb_test_ctx, + mod_test_ctx->entry_dn); +} + +static struct ldb_result *run_mod_test(struct ldb_mod_test_ctx *mod_test_ctx, + int modify_flags, + struct keyval *kvs) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_result *res; + struct ldb_message *mod_msg; + struct ldb_dn *basedn; + struct ldbtest_ctx *ldb_test_ctx; + int ret; + + ldb_test_ctx = mod_test_ctx->ldb_test_ctx; + + tmp_ctx = talloc_new(mod_test_ctx); + assert_non_null(tmp_ctx); + + mod_msg = build_mod_msg(tmp_ctx, ldb_test_ctx, mod_test_ctx->entry_dn, + modify_flags, kvs); + assert_non_null(mod_msg); + + ret = ldb_modify(ldb_test_ctx->ldb, mod_msg); + assert_int_equal(ret, LDB_SUCCESS); + + basedn = ldb_dn_new_fmt(tmp_ctx, ldb_test_ctx->ldb, + "%s", mod_test_ctx->entry_dn); + assert_non_null(basedn); + + ret = ldb_search(ldb_test_ctx->ldb, mod_test_ctx, &res, basedn, + LDB_SCOPE_BASE, NULL, NULL); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(res); + assert_int_equal(res->count, 1); + assert_string_equal(ldb_dn_get_linearized(res->msgs[0]->dn), + ldb_dn_get_linearized(mod_msg->dn)); + + talloc_free(tmp_ctx); + return res; +} + +static int ldb_modify_test_setup(void **state) +{ + struct ldbtest_ctx *ldb_test_ctx; + struct ldb_mod_test_ctx *mod_test_ctx; + struct keyval kvs[] = { + { "cn", "test_mod_cn" }, + { "objectUUID", "0123456789abcdef"}, + { NULL, NULL }, + }; + + ldbtest_setup((void **) &ldb_test_ctx); + + mod_test_ctx = talloc(ldb_test_ctx, struct ldb_mod_test_ctx); + assert_non_null(mod_test_ctx); + + mod_test_ctx->entry_dn = "dc=mod_test_entry"; + mod_test_ctx->ldb_test_ctx = ldb_test_ctx; + + mod_test_remove_data(mod_test_ctx); + mod_test_add_data(mod_test_ctx, kvs); + *state = mod_test_ctx; + return 0; +} + +static int ldb_modify_test_teardown(void **state) +{ + struct ldb_mod_test_ctx *mod_test_ctx = \ + talloc_get_type_abort(*state, + struct ldb_mod_test_ctx); + struct ldbtest_ctx *ldb_test_ctx; + + ldb_test_ctx = mod_test_ctx->ldb_test_ctx; + + mod_test_remove_data(mod_test_ctx); + talloc_free(mod_test_ctx); + + ldbtest_teardown((void **) &ldb_test_ctx); + return 0; +} + +static void test_ldb_modify_add_key(void **state) +{ + struct ldb_mod_test_ctx *mod_test_ctx = \ + talloc_get_type_abort(*state, + struct ldb_mod_test_ctx); + struct keyval mod_kvs[] = { + { "name", "test_mod_name" }, + { NULL, NULL }, + }; + struct ldb_result *res; + struct ldb_message_element *el; + + res = run_mod_test(mod_test_ctx, LDB_FLAG_MOD_ADD, mod_kvs); + assert_non_null(res); + + /* Check cn is intact and name was added */ + assert_int_equal(res->count, 1); + el = ldb_msg_find_element(res->msgs[0], "cn"); + assert_non_null(el); + assert_int_equal(el->num_values, 1); + assert_string_equal((const char *)el->values[0].data, "test_mod_cn"); + + el = ldb_msg_find_element(res->msgs[0], "name"); + assert_non_null(el); + assert_int_equal(el->num_values, 1); + assert_string_equal((const char *)el->values[0].data, "test_mod_name"); +} + +static void test_ldb_modify_extend_key(void **state) +{ + struct ldb_mod_test_ctx *mod_test_ctx = \ + talloc_get_type_abort(*state, + struct ldb_mod_test_ctx); + struct keyval mod_kvs[] = { + { "cn", "test_mod_cn2" }, + { NULL, NULL }, + }; + struct ldb_result *res; + struct ldb_message_element *el; + + res = run_mod_test(mod_test_ctx, LDB_FLAG_MOD_ADD, mod_kvs); + assert_non_null(res); + + /* Check cn was extended with another value */ + assert_int_equal(res->count, 1); + el = ldb_msg_find_element(res->msgs[0], "cn"); + assert_non_null(el); + assert_int_equal(el->num_values, 2); + assert_string_equal((const char *)el->values[0].data, "test_mod_cn"); + assert_string_equal((const char *)el->values[1].data, "test_mod_cn2"); +} + +static void test_ldb_modify_add_key_noval(void **state) +{ + struct ldb_mod_test_ctx *mod_test_ctx = \ + talloc_get_type_abort(*state, + struct ldb_mod_test_ctx); + struct ldb_message *mod_msg; + struct ldbtest_ctx *ldb_test_ctx; + struct ldb_message_element *el; + int ret; + + ldb_test_ctx = mod_test_ctx->ldb_test_ctx; + + mod_msg = ldb_msg_new(mod_test_ctx); + assert_non_null(mod_msg); + + mod_msg->dn = ldb_dn_new_fmt(mod_msg, ldb_test_ctx->ldb, + "%s", mod_test_ctx->entry_dn); + assert_non_null(mod_msg->dn); + + el = talloc_zero(mod_msg, struct ldb_message_element); + el->flags = LDB_FLAG_MOD_ADD; + assert_non_null(el); + el->name = talloc_strdup(el, "cn"); + assert_non_null(el->name); + + mod_msg->elements = el; + mod_msg->num_elements = 1; + + ret = ldb_modify(ldb_test_ctx->ldb, mod_msg); + assert_int_equal(ret, LDB_ERR_CONSTRAINT_VIOLATION); +} + +static void test_ldb_modify_replace_key(void **state) +{ + struct ldb_mod_test_ctx *mod_test_ctx = \ + talloc_get_type_abort(*state, + struct ldb_mod_test_ctx); + const char *new_cn = "new_cn"; + struct keyval mod_kvs[] = { + { "cn", new_cn }, + { NULL, NULL }, + }; + struct ldb_result *res; + struct ldb_message_element *el; + + res = run_mod_test(mod_test_ctx, LDB_FLAG_MOD_REPLACE, mod_kvs); + assert_non_null(res); + + /* Check cn was replaced */ + assert_int_equal(res->count, 1); + el = ldb_msg_find_element(res->msgs[0], "cn"); + assert_non_null(el); + assert_int_equal(el->num_values, 1); + assert_string_equal((const char *)el->values[0].data, new_cn); +} + +static void test_ldb_modify_replace_noexist_key(void **state) +{ + struct ldb_mod_test_ctx *mod_test_ctx = \ + talloc_get_type_abort(*state, + struct ldb_mod_test_ctx); + struct keyval mod_kvs[] = { + { "name", "name_val" }, + { NULL, NULL }, + }; + struct ldb_result *res; + struct ldb_message_element *el; + + res = run_mod_test(mod_test_ctx, LDB_FLAG_MOD_REPLACE, mod_kvs); + assert_non_null(res); + + /* Check cn is intact and name was added */ + assert_int_equal(res->count, 1); + el = ldb_msg_find_element(res->msgs[0], "cn"); + assert_non_null(el); + assert_int_equal(el->num_values, 1); + assert_string_equal((const char *)el->values[0].data, "test_mod_cn"); + + el = ldb_msg_find_element(res->msgs[0], mod_kvs[0].key); + assert_non_null(el); + assert_int_equal(el->num_values, 1); + assert_string_equal((const char *)el->values[0].data, mod_kvs[0].val); +} + +static void test_ldb_modify_replace_zero_vals(void **state) +{ + struct ldb_mod_test_ctx *mod_test_ctx = \ + talloc_get_type_abort(*state, + struct ldb_mod_test_ctx); + struct ldb_message_element *el; + struct ldb_result *res; + struct keyval kvs[] = { + { "cn", NULL }, + { NULL, NULL }, + }; + + /* cn must be gone */ + res = run_mod_test(mod_test_ctx, LDB_FLAG_MOD_REPLACE, kvs); + assert_non_null(res); + el = ldb_msg_find_element(res->msgs[0], "cn"); + assert_null(el); +} + +static void test_ldb_modify_replace_noexist_key_zero_vals(void **state) +{ + struct ldb_mod_test_ctx *mod_test_ctx = \ + talloc_get_type_abort(*state, + struct ldb_mod_test_ctx); + struct ldb_message_element *el; + struct ldb_result *res; + struct keyval kvs[] = { + { "noexist_key", NULL }, + { NULL, NULL }, + }; + + /* cn must be gone */ + res = run_mod_test(mod_test_ctx, LDB_FLAG_MOD_REPLACE, kvs); + assert_non_null(res); + + /* cn should be intact */ + el = ldb_msg_find_element(res->msgs[0], "cn"); + assert_non_null(el); +} + +static void test_ldb_modify_del_key(void **state) +{ + struct ldb_mod_test_ctx *mod_test_ctx = \ + talloc_get_type_abort(*state, + struct ldb_mod_test_ctx); + struct ldb_message_element *el; + struct ldb_result *res; + struct keyval kvs[] = { + { "cn", NULL }, + { NULL, NULL }, + }; + + /* cn must be gone */ + res = run_mod_test(mod_test_ctx, LDB_FLAG_MOD_DELETE, kvs); + assert_non_null(res); + + el = ldb_msg_find_element(res->msgs[0], "cn"); + assert_null(el); +} + +static void test_ldb_modify_del_keyval(void **state) +{ + struct ldb_mod_test_ctx *mod_test_ctx = \ + talloc_get_type_abort(*state, + struct ldb_mod_test_ctx); + struct ldb_message_element *el; + struct ldb_result *res; + struct keyval kvs[] = { + { "cn", "test_mod_cn" }, + { NULL, NULL }, + }; + + /* cn must be gone */ + res = run_mod_test(mod_test_ctx, LDB_FLAG_MOD_DELETE, kvs); + assert_non_null(res); + + el = ldb_msg_find_element(res->msgs[0], "cn"); + assert_null(el); +} + +struct search_test_ctx { + struct ldbtest_ctx *ldb_test_ctx; + const char *base_dn; +}; + +static char *get_full_dn(TALLOC_CTX *mem_ctx, + struct search_test_ctx *search_test_ctx, + const char *rdn) +{ + char *full_dn; + + full_dn = talloc_asprintf(mem_ctx, + "%s,%s", rdn, search_test_ctx->base_dn); + assert_non_null(full_dn); + + return full_dn; +} + +static void search_test_add_data(struct search_test_ctx *search_test_ctx, + const char *rdn, + struct keyval *kvs) +{ + char *full_dn; + + full_dn = get_full_dn(search_test_ctx, search_test_ctx, rdn); + + ldb_test_add_data(search_test_ctx, + search_test_ctx->ldb_test_ctx, + full_dn, + kvs); +} + +static void search_test_remove_data(struct search_test_ctx *search_test_ctx, + const char *rdn) +{ + char *full_dn; + + full_dn = talloc_asprintf(search_test_ctx, + "%s,%s", rdn, search_test_ctx->base_dn); + assert_non_null(full_dn); + + ldb_test_remove_data(search_test_ctx, + search_test_ctx->ldb_test_ctx, + full_dn); +} + +static int ldb_search_test_setup(void **state) +{ + struct ldbtest_ctx *ldb_test_ctx; + struct search_test_ctx *search_test_ctx; + struct keyval kvs[] = { + { "cn", "test_search_cn" }, + { "cn", "test_search_cn2" }, + { "uid", "test_search_uid" }, + { "uid", "test_search_uid2" }, + { "objectUUID", "0123456789abcde0"}, + { NULL, NULL }, + }; + struct keyval kvs2[] = { + { "cn", "test_search_2_cn" }, + { "cn", "test_search_2_cn2" }, + { "uid", "test_search_2_uid" }, + { "uid", "test_search_2_uid2" }, + { "objectUUID", "0123456789abcde1"}, + { NULL, NULL }, + }; + + ldbtest_setup((void **) &ldb_test_ctx); + + search_test_ctx = talloc(ldb_test_ctx, struct search_test_ctx); + assert_non_null(search_test_ctx); + + search_test_ctx->base_dn = "dc=search_test_entry"; + search_test_ctx->ldb_test_ctx = ldb_test_ctx; + + search_test_remove_data(search_test_ctx, "cn=test_search_cn"); + search_test_add_data(search_test_ctx, "cn=test_search_cn", kvs); + + search_test_remove_data(search_test_ctx, "cn=test_search_2_cn"); + search_test_add_data(search_test_ctx, "cn=test_search_2_cn", kvs2); + + *state = search_test_ctx; + return 0; +} + +static int ldb_search_test_teardown(void **state) +{ + struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state, + struct search_test_ctx); + struct ldbtest_ctx *ldb_test_ctx; + + ldb_test_ctx = search_test_ctx->ldb_test_ctx; + + search_test_remove_data(search_test_ctx, "cn=test_search_cn"); + search_test_remove_data(search_test_ctx, "cn=test_search_2_cn"); + ldbtest_teardown((void **) &ldb_test_ctx); + return 0; +} + +static void assert_attr_has_vals(struct ldb_message *msg, + const char *attr, + const char *vals[], + const size_t nvals) +{ + struct ldb_message_element *el; + size_t i; + + el = ldb_msg_find_element(msg, attr); + assert_non_null(el); + + assert_int_equal(el->num_values, nvals); + for (i = 0; i < nvals; i++) { + assert_string_equal((const char *)el->values[i].data, + vals[i]); + } +} + +static void assert_has_no_attr(struct ldb_message *msg, + const char *attr) +{ + struct ldb_message_element *el; + + el = ldb_msg_find_element(msg, attr); + assert_null(el); +} + +static bool has_dn(struct ldb_message *msg, const char *dn) +{ + const char *msgdn; + + msgdn = ldb_dn_get_linearized(msg->dn); + if (strcmp(dn, msgdn) == 0) { + return true; + } + + return false; +} + +static void test_search_match_none(void **state) +{ + struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state, + struct search_test_ctx); + size_t count; + + count = base_search_count(search_test_ctx->ldb_test_ctx, + "dc=no_such_entry"); + assert_int_equal(count, 0); +} + +static void test_search_match_one(void **state) +{ + struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state, + struct search_test_ctx); + int ret; + struct ldb_dn *basedn; + struct ldb_result *result = NULL; + const char *cn_vals[] = { "test_search_cn", + "test_search_cn2" }; + const char *uid_vals[] = { "test_search_uid", + "test_search_uid2" }; + + basedn = ldb_dn_new_fmt(search_test_ctx, + search_test_ctx->ldb_test_ctx->ldb, + "%s", + search_test_ctx->base_dn); + assert_non_null(basedn); + + ret = ldb_search(search_test_ctx->ldb_test_ctx->ldb, + search_test_ctx, + &result, + basedn, + LDB_SCOPE_SUBTREE, NULL, + "cn=test_search_cn"); + assert_int_equal(ret, 0); + assert_non_null(result); + assert_int_equal(result->count, 1); + + assert_attr_has_vals(result->msgs[0], "cn", cn_vals, 2); + assert_attr_has_vals(result->msgs[0], "uid", uid_vals, 2); +} + +static void test_search_match_filter(void **state) +{ + struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state, + struct search_test_ctx); + int ret; + struct ldb_dn *basedn; + struct ldb_result *result = NULL; + const char *cn_vals[] = { "test_search_cn", + "test_search_cn2" }; + const char *attrs[] = { "cn", NULL }; + + basedn = ldb_dn_new_fmt(search_test_ctx, + search_test_ctx->ldb_test_ctx->ldb, + "%s", + search_test_ctx->base_dn); + assert_non_null(basedn); + + ret = ldb_search(search_test_ctx->ldb_test_ctx->ldb, + search_test_ctx, + &result, + basedn, + LDB_SCOPE_SUBTREE, + attrs, + "cn=test_search_cn"); + assert_int_equal(ret, 0); + assert_non_null(result); + assert_int_equal(result->count, 1); + + assert_attr_has_vals(result->msgs[0], "cn", cn_vals, 2); + assert_has_no_attr(result->msgs[0], "uid"); +} + +static void assert_expected(struct search_test_ctx *search_test_ctx, + struct ldb_message *msg) +{ + char *full_dn1; + char *full_dn2; + const char *cn_vals[] = { "test_search_cn", + "test_search_cn2" }; + const char *uid_vals[] = { "test_search_uid", + "test_search_uid2" }; + const char *cn2_vals[] = { "test_search_2_cn", + "test_search_2_cn2" }; + const char *uid2_vals[] = { "test_search_2_uid", + "test_search_2_uid2" }; + + full_dn1 = get_full_dn(search_test_ctx, + search_test_ctx, + "cn=test_search_cn"); + + full_dn2 = get_full_dn(search_test_ctx, + search_test_ctx, + "cn=test_search_2_cn"); + + if (has_dn(msg, full_dn1) == true) { + assert_attr_has_vals(msg, "cn", cn_vals, 2); + assert_attr_has_vals(msg, "uid", uid_vals, 2); + } else if (has_dn(msg, full_dn2) == true) { + assert_attr_has_vals(msg, "cn", cn2_vals, 2); + assert_attr_has_vals(msg, "uid", uid2_vals, 2); + } else { + fail(); + } +} + +static void test_search_match_both(void **state) +{ + struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state, + struct search_test_ctx); + int ret; + struct ldb_dn *basedn; + struct ldb_result *result = NULL; + + basedn = ldb_dn_new_fmt(search_test_ctx, + search_test_ctx->ldb_test_ctx->ldb, + "%s", + search_test_ctx->base_dn); + assert_non_null(basedn); + + ret = ldb_search(search_test_ctx->ldb_test_ctx->ldb, + search_test_ctx, + &result, + basedn, + LDB_SCOPE_SUBTREE, NULL, + "cn=test_search_*"); + assert_int_equal(ret, 0); + assert_non_null(result); + assert_int_equal(result->count, 2); + + assert_expected(search_test_ctx, result->msgs[0]); + assert_expected(search_test_ctx, result->msgs[1]); +} + +static void test_search_match_basedn(void **state) +{ + struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state, + struct search_test_ctx); + int ret; + struct ldb_dn *basedn; + struct ldb_result *result = NULL; + struct ldb_message *msg; + + basedn = ldb_dn_new_fmt(search_test_ctx, + search_test_ctx->ldb_test_ctx->ldb, + "dc=nosuchdn"); + assert_non_null(basedn); + + ret = ldb_search(search_test_ctx->ldb_test_ctx->ldb, + search_test_ctx, + &result, + basedn, + LDB_SCOPE_SUBTREE, NULL, + "cn=*"); + assert_int_equal(ret, 0); + + /* Add 'checkBaseOnSearch' to @OPTIONS */ + msg = ldb_msg_new(search_test_ctx); + assert_non_null(msg); + + msg->dn = ldb_dn_new_fmt(msg, + search_test_ctx->ldb_test_ctx->ldb, + "@OPTIONS"); + assert_non_null(msg->dn); + + ret = ldb_msg_add_string(msg, "checkBaseOnSearch", "TRUE"); + assert_int_equal(ret, 0); + + ret = ldb_add(search_test_ctx->ldb_test_ctx->ldb, msg); + assert_int_equal(ret, 0); + + /* Search again */ + /* The search should return LDB_ERR_NO_SUCH_OBJECT */ + ret = ldb_search(search_test_ctx->ldb_test_ctx->ldb, + search_test_ctx, + &result, + basedn, + LDB_SCOPE_SUBTREE, NULL, + "cn=*"); + assert_int_equal(ret, LDB_ERR_NO_SUCH_OBJECT); + + ret = ldb_delete(search_test_ctx->ldb_test_ctx->ldb, msg->dn); + assert_int_equal(ret, 0); +} + + +/* + * This test is complex. + * The purpose is to test for a deadlock detected between ldb_search() + * and ldb_transaction_commit(). The deadlock happens if in process + * (1) and (2): + * - (1) the all-record lock is taken in ltdb_search() + * - (2) the ldb_transaction_start() call is made + * - (1) an un-indexed search starts (forced here by doing it in + * the callback + * - (2) the ldb_transaction_commit() is called. + * This returns LDB_ERR_BUSY if the deadlock is detected + * + * With ldb 1.1.31 and tdb 1.3.12 we avoid this only due to a missing + * lock call in ltdb_search() due to a refcounting bug in + * ltdb_lock_read() + */ + +struct search_against_transaction_ctx { + struct ldbtest_ctx *test_ctx; + int res_count; + pid_t child_pid; + struct ldb_dn *basedn; +}; + +static int test_ldb_search_against_transaction_callback2(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct search_against_transaction_ctx *ctx = req->context; + switch (ares->type) { + case LDB_REPLY_ENTRY: + ctx->res_count++; + if (ctx->res_count != 1) { + return LDB_SUCCESS; + } + + break; + + case LDB_REPLY_REFERRAL: + break; + + case LDB_REPLY_DONE: + return ldb_request_done(req, LDB_SUCCESS); + } + + return 0; + +} + +/* + * This purpose of this callback is to trigger a transaction in + * the child process while the all-record lock is held, but before + * we take any locks in the tdb_traverse_read() handler. + * + * In tdb 1.3.12 tdb_traverse_read() take the read transaction lock + * however in ldb 1.1.31 ltdb_search() forgets to take the all-record + * lock (except the very first time) due to a ref-counting bug. + * + */ + +static int test_ldb_search_against_transaction_callback1(struct ldb_request *req, + struct ldb_reply *ares) +{ + int ret, ret2; + int pipes[2]; + char buf[2]; + struct search_against_transaction_ctx *ctx = req->context; + switch (ares->type) { + case LDB_REPLY_ENTRY: + break; + + case LDB_REPLY_REFERRAL: + return LDB_SUCCESS; + + case LDB_REPLY_DONE: + return ldb_request_done(req, LDB_SUCCESS); + } + + ret = pipe(pipes); + assert_int_equal(ret, 0); + + ctx->child_pid = fork(); + if (ctx->child_pid == 0) { + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_message *msg; + TALLOC_FREE(ctx->test_ctx->ldb); + TALLOC_FREE(ctx->test_ctx->ev); + close(pipes[0]); + ctx->test_ctx->ev = tevent_context_init(ctx->test_ctx); + if (ctx->test_ctx->ev == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ctx->test_ctx->ldb = ldb_init(ctx->test_ctx, + ctx->test_ctx->ev); + if (ctx->test_ctx->ldb == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_connect(ctx->test_ctx->ldb, + ctx->test_ctx->dbpath, 0, NULL); + if (ret != LDB_SUCCESS) { + exit(ret); + } + + tmp_ctx = talloc_new(ctx->test_ctx); + if (tmp_ctx == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + msg = ldb_msg_new(tmp_ctx); + if (msg == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + msg->dn = ldb_dn_new_fmt(msg, ctx->test_ctx->ldb, + "dc=test"); + if (msg->dn == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_msg_add_string(msg, "cn", "test_cn_val"); + if (ret != 0) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_transaction_start(ctx->test_ctx->ldb); + if (ret != 0) { + exit(ret); + } + + ret = write(pipes[1], "GO", 2); + if (ret != 2) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_msg_add_string(msg, "objectUUID", + "0123456789abcdef"); + if (ret != 0) { + exit(ret); + } + + ret = ldb_add(ctx->test_ctx->ldb, msg); + if (ret != 0) { + exit(ret); + } + + ret = ldb_transaction_commit(ctx->test_ctx->ldb); + exit(ret); + } + close(pipes[1]); + ret = read(pipes[0], buf, 2); + assert_int_equal(ret, 2); + + /* This search must be unindexed (ie traverse in tdb) */ + ret = ldb_build_search_req(&req, + ctx->test_ctx->ldb, + ctx->test_ctx, + ctx->basedn, + LDB_SCOPE_SUBTREE, + "cn=*", NULL, + NULL, + ctx, + test_ldb_search_against_transaction_callback2, + NULL); + /* + * we don't assert on these return codes until after the search is + * finished, or the clean up will fail because we hold locks. + */ + + ret2 = ldb_request(ctx->test_ctx->ldb, req); + + if (ret2 == LDB_SUCCESS) { + ret2 = ldb_wait(req->handle, LDB_WAIT_ALL); + } + assert_int_equal(ret, 0); + assert_int_equal(ret2, 0); + assert_int_equal(ctx->res_count, 2); + + return LDB_SUCCESS; +} + +static void test_ldb_search_against_transaction(void **state) +{ + struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state, + struct search_test_ctx); + struct search_against_transaction_ctx + ctx = + { .res_count = 0, + .test_ctx = search_test_ctx->ldb_test_ctx + }; + + int ret; + struct ldb_request *req; + pid_t pid; + int wstatus; + struct ldb_dn *base_search_dn; + + tevent_loop_allow_nesting(search_test_ctx->ldb_test_ctx->ev); + + base_search_dn + = ldb_dn_new_fmt(search_test_ctx, + search_test_ctx->ldb_test_ctx->ldb, + "cn=test_search_cn,%s", + search_test_ctx->base_dn); + assert_non_null(base_search_dn); + + ctx.basedn + = ldb_dn_new_fmt(search_test_ctx, + search_test_ctx->ldb_test_ctx->ldb, + "%s", + search_test_ctx->base_dn); + assert_non_null(ctx.basedn); + + + /* This search must be indexed (ie no traverse in tdb) */ + ret = ldb_build_search_req(&req, + search_test_ctx->ldb_test_ctx->ldb, + search_test_ctx, + base_search_dn, + LDB_SCOPE_BASE, + "cn=*", NULL, + NULL, + &ctx, + test_ldb_search_against_transaction_callback1, + NULL); + assert_int_equal(ret, 0); + ret = ldb_request(search_test_ctx->ldb_test_ctx->ldb, req); + + if (ret == LDB_SUCCESS) { + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + } + assert_int_equal(ret, 0); + assert_int_equal(ctx.res_count, 2); + + pid = waitpid(ctx.child_pid, &wstatus, 0); + assert_int_equal(pid, ctx.child_pid); + + assert_true(WIFEXITED(wstatus)); + + assert_int_equal(WEXITSTATUS(wstatus), 0); + + +} + +/* + * This test is also complex. + * The purpose is to test if a modify can occur during an ldb_search() + * This would be a failure if if in process + * (1) and (2): + * - (1) ltdb_search() starts and calls back for one entry + * - (2) one of the entries to be matched is modified + * - (1) the indexed search tries to return the modified entry, but + * it is no longer found, either: + * - despite it still matching (dn changed) + * - it no longer matching (attrs changed) + * + * We also try un-indexed to show that the behaviour differs on this + * point, which it should not (an index should only impact search + * speed). + */ + +struct modify_during_search_test_ctx { + struct ldbtest_ctx *test_ctx; + int res_count; + pid_t child_pid; + struct ldb_dn *basedn; + bool got_cn; + bool got_2_cn; + bool rename; +}; + +/* + * This purpose of this callback is to trigger a write in + * the child process while a search is in progress. + * + * In tdb 1.3.12 tdb_traverse_read() take the read transaction lock + * however in ldb 1.1.31 ltdb_search() forgets to take the all-record + * lock (except the very first time) due to a ref-counting bug. + * + * We assume that if the write will proceed, it will proceed in a 3 + * second window after the function is called. + */ + +static int test_ldb_modify_during_search_callback1(struct ldb_request *req, + struct ldb_reply *ares) +{ + int ret; + int pipes[2]; + char buf[2]; + struct modify_during_search_test_ctx *ctx = req->context; + switch (ares->type) { + case LDB_REPLY_ENTRY: + { + const struct ldb_val *cn_val + = ldb_dn_get_component_val(ares->message->dn, 0); + const char *cn = (char *)cn_val->data; + ctx->res_count++; + if (strcmp(cn, "test_search_cn") == 0) { + ctx->got_cn = true; + } else if (strcmp(cn, "test_search_2_cn") == 0) { + ctx->got_2_cn = true; + } + if (ctx->res_count == 2) { + return LDB_SUCCESS; + } + break; + } + case LDB_REPLY_REFERRAL: + return LDB_SUCCESS; + + case LDB_REPLY_DONE: + return ldb_request_done(req, LDB_SUCCESS); + } + + ret = pipe(pipes); + assert_int_equal(ret, 0); + + ctx->child_pid = fork(); + if (ctx->child_pid == 0 && ctx->rename) { + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_dn *dn, *new_dn; + TALLOC_FREE(ctx->test_ctx->ldb); + TALLOC_FREE(ctx->test_ctx->ev); + close(pipes[0]); + ctx->test_ctx->ev = tevent_context_init(ctx->test_ctx); + if (ctx->test_ctx->ev == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ctx->test_ctx->ldb = ldb_init(ctx->test_ctx, + ctx->test_ctx->ev); + if (ctx->test_ctx->ldb == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_connect(ctx->test_ctx->ldb, + ctx->test_ctx->dbpath, 0, NULL); + if (ret != LDB_SUCCESS) { + exit(ret); + } + + tmp_ctx = talloc_new(ctx->test_ctx); + if (tmp_ctx == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + if (ctx->got_cn) { + /* Modify the other one */ + dn = ldb_dn_new_fmt(tmp_ctx, ctx->test_ctx->ldb, + "cn=test_search_2_cn," + "dc=search_test_entry"); + } else { + dn = ldb_dn_new_fmt(tmp_ctx, ctx->test_ctx->ldb, + "cn=test_search_cn," + "dc=search_test_entry"); + } + if (dn == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + new_dn = ldb_dn_new_fmt(tmp_ctx, ctx->test_ctx->ldb, + "cn=test_search_cn_renamed," + "dc=search_test_entry"); + if (new_dn == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_transaction_start(ctx->test_ctx->ldb); + if (ret != 0) { + exit(ret); + } + + if (write(pipes[1], "GO", 2) != 2) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_rename(ctx->test_ctx->ldb, dn, new_dn); + if (ret != 0) { + exit(ret); + } + + ret = ldb_transaction_commit(ctx->test_ctx->ldb); + exit(ret); + + } else if (ctx->child_pid == 0) { + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_message *msg; + struct ldb_message_element *el; + TALLOC_FREE(ctx->test_ctx->ldb); + TALLOC_FREE(ctx->test_ctx->ev); + close(pipes[0]); + ctx->test_ctx->ev = tevent_context_init(ctx->test_ctx); + if (ctx->test_ctx->ev == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ctx->test_ctx->ldb = ldb_init(ctx->test_ctx, + ctx->test_ctx->ev); + if (ctx->test_ctx->ldb == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_connect(ctx->test_ctx->ldb, + ctx->test_ctx->dbpath, 0, NULL); + if (ret != LDB_SUCCESS) { + exit(ret); + } + + tmp_ctx = talloc_new(ctx->test_ctx); + if (tmp_ctx == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + msg = ldb_msg_new(tmp_ctx); + if (msg == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + if (ctx->got_cn) { + /* Modify the other one */ + msg->dn = ldb_dn_new_fmt(msg, ctx->test_ctx->ldb, + "cn=test_search_2_cn," + "dc=search_test_entry"); + } else { + msg->dn = ldb_dn_new_fmt(msg, ctx->test_ctx->ldb, + "cn=test_search_cn," + "dc=search_test_entry"); + } + if (msg->dn == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_msg_add_string(msg, "filterAttr", "TRUE"); + if (ret != 0) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + el = ldb_msg_find_element(msg, "filterAttr"); + if (el == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + el->flags = LDB_FLAG_MOD_REPLACE; + + ret = ldb_transaction_start(ctx->test_ctx->ldb); + if (ret != 0) { + exit(ret); + } + + if (write(pipes[1], "GO", 2) != 2) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_modify(ctx->test_ctx->ldb, msg); + if (ret != 0) { + exit(ret); + } + + ret = ldb_transaction_commit(ctx->test_ctx->ldb); + exit(ret); + } + + /* + * With TDB 1.3.13 and before "tdb: Remove locking from tdb_traverse_read()" + * we will hang here because the child process can not proceed to + * sending the "GO" as it is blocked at ldb_transaction_start(). + */ + + close(pipes[1]); + ret = read(pipes[0], buf, 2); + assert_int_equal(ret, 2); + + sleep(3); + + return LDB_SUCCESS; +} + +static void test_ldb_modify_during_search(void **state, bool add_index, + bool rename) +{ + struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state, + struct search_test_ctx); + struct modify_during_search_test_ctx + ctx = + { .res_count = 0, + .test_ctx = search_test_ctx->ldb_test_ctx, + .rename = rename + }; + + int ret; + struct ldb_request *req; + pid_t pid; + int wstatus; + + if (add_index) { + struct ldb_message *msg; + struct ldb_dn *indexlist = ldb_dn_new(search_test_ctx, + search_test_ctx->ldb_test_ctx->ldb, + "@INDEXLIST"); + assert_non_null(indexlist); + + msg = ldb_msg_new(search_test_ctx); + assert_non_null(msg); + + msg->dn = indexlist; + + ret = ldb_msg_add_string(msg, "@IDXATTR", "cn"); + assert_int_equal(ret, LDB_SUCCESS); + ret = ldb_add(search_test_ctx->ldb_test_ctx->ldb, + msg); + if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) { + msg->elements[0].flags = LDB_FLAG_MOD_ADD; + ret = ldb_modify(search_test_ctx->ldb_test_ctx->ldb, + msg); + } + assert_int_equal(ret, LDB_SUCCESS); + } + + tevent_loop_allow_nesting(search_test_ctx->ldb_test_ctx->ev); + + ctx.basedn + = ldb_dn_new_fmt(search_test_ctx, + search_test_ctx->ldb_test_ctx->ldb, + "%s", + search_test_ctx->base_dn); + assert_non_null(ctx.basedn); + + + /* + * This search must be over multiple items, and should include + * the new name after a rename, to show that it would match + * both before and after that modify + */ + ret = ldb_build_search_req(&req, + search_test_ctx->ldb_test_ctx->ldb, + search_test_ctx, + ctx.basedn, + LDB_SCOPE_SUBTREE, + "(&(!(filterAttr=*))" + "(|(cn=test_search_cn_renamed)" + "(cn=test_search_cn)" + "(cn=test_search_2_cn)" + "))", + NULL, + NULL, + &ctx, + test_ldb_modify_during_search_callback1, + NULL); + assert_int_equal(ret, 0); + ret = ldb_request(search_test_ctx->ldb_test_ctx->ldb, req); + + if (ret == LDB_SUCCESS) { + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + } + assert_int_equal(ret, 0); + assert_int_equal(ctx.res_count, 2); + assert_int_equal(ctx.got_cn, true); + assert_int_equal(ctx.got_2_cn, true); + + pid = waitpid(ctx.child_pid, &wstatus, 0); + assert_int_equal(pid, ctx.child_pid); + + assert_true(WIFEXITED(wstatus)); + + assert_int_equal(WEXITSTATUS(wstatus), 0); + + +} + +static void test_ldb_modify_during_indexed_search(void **state) +{ + test_ldb_modify_during_search(state, true, false); +} + +static void test_ldb_modify_during_unindexed_search(void **state) +{ + test_ldb_modify_during_search(state, false, false); +} + +static void test_ldb_rename_during_indexed_search(void **state) +{ + test_ldb_modify_during_search(state, true, true); +} + +static void test_ldb_rename_during_unindexed_search(void **state) +{ + test_ldb_modify_during_search(state, false, true); +} + +/* + * This test is also complex. + * + * The purpose is to test if a modify can occur during an ldb_search() + * before the end of the callback + * + * This would be a failure if if in process + * (1) and (2): + * - (1) ldb_search() starts and calls back for a number of entries + * - (2) an entry in the DB is allowed to change before the callback returns + * - (1) the callback can see the modification + * + */ + +/* + * This purpose of this callback is to trigger a write in + * the child process while a search DONE callback is in progress. + * + * In ldb 1.1.31 ldb_search() omitted to take a all-record + * lock for the full duration of the search and callbacks + * + * We assume that if the write will proceed, it will proceed in a 3 + * second window after the function is called. + */ + +static int test_ldb_modify_during_whole_search_callback1(struct ldb_request *req, + struct ldb_reply *ares) +{ + int ret; + int pipes[2]; + char buf[2]; + struct modify_during_search_test_ctx *ctx = req->context; + struct ldb_dn *search_dn; + struct ldb_result *res2; + unsigned res_count; + switch (ares->type) { + case LDB_REPLY_ENTRY: + case LDB_REPLY_REFERRAL: + return LDB_SUCCESS; + + case LDB_REPLY_DONE: + break; + } + + ret = pipe(pipes); + assert_int_equal(ret, 0); + + ctx->child_pid = fork(); + if (ctx->child_pid == 0) { + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_message *msg; + struct ldb_message_element *el; + TALLOC_FREE(ctx->test_ctx->ldb); + TALLOC_FREE(ctx->test_ctx->ev); + close(pipes[0]); + ctx->test_ctx->ev = tevent_context_init(ctx->test_ctx); + if (ctx->test_ctx->ev == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ctx->test_ctx->ldb = ldb_init(ctx->test_ctx, + ctx->test_ctx->ev); + if (ctx->test_ctx->ldb == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_connect(ctx->test_ctx->ldb, + ctx->test_ctx->dbpath, 0, NULL); + if (ret != LDB_SUCCESS) { + exit(ret); + } + + tmp_ctx = talloc_new(ctx->test_ctx); + if (tmp_ctx == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + msg = ldb_msg_new(tmp_ctx); + if (msg == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + msg->dn = ldb_dn_new_fmt(msg, ctx->test_ctx->ldb, + "cn=test_search_cn," + "dc=search_test_entry"); + if (msg->dn == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_msg_add_string(msg, "filterAttr", "TRUE"); + if (ret != 0) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + el = ldb_msg_find_element(msg, "filterAttr"); + if (el == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + el->flags = LDB_FLAG_MOD_REPLACE; + + ret = ldb_transaction_start(ctx->test_ctx->ldb); + if (ret != 0) { + exit(ret); + } + + if (write(pipes[1], "GO", 2) != 2) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_modify(ctx->test_ctx->ldb, msg); + if (ret != 0) { + exit(ret); + } + + ret = ldb_transaction_commit(ctx->test_ctx->ldb); + exit(ret); + } + + close(pipes[1]); + ret = read(pipes[0], buf, 2); + assert_int_equal(ret, 2); + + sleep(3); + + /* + * If writes are not blocked until after this function, we + * will be able to successfully search for this modification + * here + */ + + search_dn = ldb_dn_new_fmt(ares, ctx->test_ctx->ldb, + "cn=test_search_cn," + "dc=search_test_entry"); + + ret = ldb_search(ctx->test_ctx->ldb, ares, + &res2, search_dn, LDB_SCOPE_BASE, NULL, + "filterAttr=TRUE"); + + /* + * We do this in an unusual order, because if we fail an assert before + * ldb_request_done(), we will also fail to clean up as we hold locks. + */ + + res_count = res2->count; + ldb_request_done(req, LDB_SUCCESS); + assert_int_equal(ret, 0); + + /* We should not have got the result */ + assert_int_equal(res_count, 0); + + return ret; +} + +static void test_ldb_modify_during_whole_search(void **state) +{ + struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state, + struct search_test_ctx); + struct modify_during_search_test_ctx + ctx = + { + .test_ctx = search_test_ctx->ldb_test_ctx, + }; + + int ret; + struct ldb_request *req; + pid_t pid; + int wstatus; + struct ldb_dn *search_dn; + struct ldb_result *res2; + + tevent_loop_allow_nesting(search_test_ctx->ldb_test_ctx->ev); + + ctx.basedn + = ldb_dn_new_fmt(search_test_ctx, + search_test_ctx->ldb_test_ctx->ldb, + "%s", + search_test_ctx->base_dn); + assert_non_null(ctx.basedn); + + + /* + * The search just needs to call DONE, we don't care about the + * contents of the search for this test + */ + ret = ldb_build_search_req(&req, + search_test_ctx->ldb_test_ctx->ldb, + search_test_ctx, + ctx.basedn, + LDB_SCOPE_SUBTREE, + "(&(!(filterAttr=*))" + "(cn=test_search_cn))", + NULL, + NULL, + &ctx, + test_ldb_modify_during_whole_search_callback1, + NULL); + assert_int_equal(ret, 0); + ret = ldb_request(search_test_ctx->ldb_test_ctx->ldb, req); + + if (ret == LDB_SUCCESS) { + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + } + assert_int_equal(ret, 0); + + pid = waitpid(ctx.child_pid, &wstatus, 0); + assert_int_equal(pid, ctx.child_pid); + + assert_true(WIFEXITED(wstatus)); + + assert_int_equal(WEXITSTATUS(wstatus), 0); + + /* + * If writes are blocked until after the search function, we + * will be able to successfully search for this modification + * now + */ + + search_dn = ldb_dn_new_fmt(search_test_ctx, + search_test_ctx->ldb_test_ctx->ldb, + "cn=test_search_cn," + "dc=search_test_entry"); + + ret = ldb_search(search_test_ctx->ldb_test_ctx->ldb, + search_test_ctx, + &res2, search_dn, LDB_SCOPE_BASE, NULL, + "filterAttr=TRUE"); + assert_int_equal(ret, 0); + + /* We got the result */ + assert_int_equal(res2->count, 1); +} + +/* + * This test is also complex. + * + * The purpose is to test if a modify can occur during an ldb_search() + * before the request is destroyed with TALLOC_FREE() + * + * This would be a failure if in process + * (1) and (2): + * - (1) ldb_search() starts and waits + * - (2) an entry in the DB is allowed to change before the ldb_wait() is called + * - (1) the original process can see the modification before the TALLOC_FREE() + * also we check that + * - (1) the original process can see the modification after the TALLOC_FREE() + * + */ + +/* + * This purpose of this callback is to trigger a write in + * the child process before the ldb_wait() is called + * + * In ldb 1.1.31 ldb_search() omitted to take a all-record + * lock for the full duration of the search and callbacks + * + * We assume that if the write will proceed, it will proceed in a 3 + * second window after the function is called. + */ + +static int test_ldb_modify_before_ldb_wait_callback1(struct ldb_request *req, + struct ldb_reply *ares) +{ + switch (ares->type) { + case LDB_REPLY_ENTRY: + case LDB_REPLY_REFERRAL: + return LDB_SUCCESS; + + case LDB_REPLY_DONE: + break; + } + + return ldb_request_done(req, LDB_SUCCESS); +} + +static void test_ldb_modify_before_ldb_wait(void **state) +{ + struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state, + struct search_test_ctx); + int ret; + struct ldb_request *req; + pid_t pid; + int wstatus; + struct ldb_dn *search_dn; + struct ldb_dn *basedn; + struct ldb_result *res2; + int pipes[2]; + char buf[2]; + pid_t child_pid; + unsigned res_count; + + search_dn = ldb_dn_new_fmt(search_test_ctx, + search_test_ctx->ldb_test_ctx->ldb, + "cn=test_search_cn," + "dc=search_test_entry"); + assert_non_null(search_dn); + + basedn = ldb_dn_new_fmt(search_test_ctx, + search_test_ctx->ldb_test_ctx->ldb, + "%s", + search_test_ctx->base_dn); + assert_non_null(basedn); + + /* + * The search just needs to call DONE, we don't care about the + * contents of the search for this test + */ + ret = ldb_build_search_req(&req, + search_test_ctx->ldb_test_ctx->ldb, + search_test_ctx, + basedn, + LDB_SCOPE_SUBTREE, + "(&(!(filterAttr=*))" + "(cn=test_search_cn))", + NULL, + NULL, + NULL, + test_ldb_modify_before_ldb_wait_callback1, + NULL); + assert_int_equal(ret, 0); + ret = ldb_request(search_test_ctx->ldb_test_ctx->ldb, req); + + ret = pipe(pipes); + assert_int_equal(ret, 0); + + child_pid = fork(); + if (child_pid == 0) { + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_message *msg; + struct ldb_message_element *el; + TALLOC_FREE(search_test_ctx->ldb_test_ctx->ldb); + TALLOC_FREE(search_test_ctx->ldb_test_ctx->ev); + close(pipes[0]); + search_test_ctx->ldb_test_ctx->ev = tevent_context_init(search_test_ctx->ldb_test_ctx); + if (search_test_ctx->ldb_test_ctx->ev == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + search_test_ctx->ldb_test_ctx->ldb = ldb_init(search_test_ctx->ldb_test_ctx, + search_test_ctx->ldb_test_ctx->ev); + if (search_test_ctx->ldb_test_ctx->ldb == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_connect(search_test_ctx->ldb_test_ctx->ldb, + search_test_ctx->ldb_test_ctx->dbpath, 0, NULL); + if (ret != LDB_SUCCESS) { + exit(ret); + } + + tmp_ctx = talloc_new(search_test_ctx->ldb_test_ctx); + if (tmp_ctx == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + msg = ldb_msg_new(tmp_ctx); + if (msg == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + /* + * We must re-create this DN from a string to ensure + * it does not reference the now-gone LDB context of + * the parent + */ + msg->dn = ldb_dn_new_fmt(search_test_ctx, + search_test_ctx->ldb_test_ctx->ldb, + "cn=test_search_cn," + "dc=search_test_entry"); + + if (msg->dn == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_msg_add_string(msg, "filterAttr", "TRUE"); + if (ret != 0) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + el = ldb_msg_find_element(msg, "filterAttr"); + if (el == NULL) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + el->flags = LDB_FLAG_MOD_REPLACE; + + ret = ldb_transaction_start(search_test_ctx->ldb_test_ctx->ldb); + if (ret != 0) { + exit(ret); + } + + if (write(pipes[1], "GO", 2) != 2) { + exit(LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_modify(search_test_ctx->ldb_test_ctx->ldb, msg); + if (ret != 0) { + exit(ret); + } + + ret = ldb_transaction_commit(search_test_ctx->ldb_test_ctx->ldb); + exit(ret); + } + close(pipes[1]); + + ret = read(pipes[0], buf, 2); + assert_int_equal(ret, 2); + + sleep(3); + + /* + * If writes are not blocked until after the (never called) ldb_wait(), we + * will be able to successfully search for this modification + * here + */ + + ret = ldb_search(search_test_ctx->ldb_test_ctx->ldb, search_test_ctx, + &res2, search_dn, LDB_SCOPE_BASE, NULL, + "filterAttr=TRUE"); + + /* + * We avoid making assertions before TALLOC_FREE()ing the request, + * lest the assert fail and mess with the clean-up because we still + * have locks. + */ + res_count = res2->count; + TALLOC_FREE(req); + + /* We should not have got the result */ + assert_int_equal(res_count, 0); + assert_int_equal(ret, 0); + + pid = waitpid(child_pid, &wstatus, 0); + assert_int_equal(pid, child_pid); + + assert_true(WIFEXITED(wstatus)); + + assert_int_equal(WEXITSTATUS(wstatus), 0); + + /* + * If writes are blocked until after the search request was freed, we + * will be able to successfully search for this modification + * now + */ + + search_dn = ldb_dn_new_fmt(search_test_ctx, + search_test_ctx->ldb_test_ctx->ldb, + "cn=test_search_cn," + "dc=search_test_entry"); + + ret = ldb_search(search_test_ctx->ldb_test_ctx->ldb, + search_test_ctx, + &res2, search_dn, LDB_SCOPE_BASE, NULL, + "filterAttr=TRUE"); + assert_int_equal(ret, 0); + + /* We got the result */ + assert_int_equal(res2->count, 1); +} + +/* + * This test is also complex. + * The purpose is to test if a modify can occur during an ldb_search() + * This would be a failure if if in process + * (1) and (2): + * - (1) ltdb_search() starts and calls back for one entry + * - (2) one of the entries to be matched is modified + * - (1) the indexed search tries to return the modified entry, but + * it is no longer found, either: + * - despite it still matching (dn changed) + * - it no longer matching (attrs changed) + * + * We also try un-indexed to show that the behaviour differs on this + * point, which it should not (an index should only impact search + * speed). + */ + +/* + * This purpose of this callback is to trigger a write in the callback + * so as to change in in-memory index code while looping over the + * index result. + */ + +static int test_ldb_callback_modify_during_search_callback1(struct ldb_request *req, + struct ldb_reply *ares) +{ + int ret; + struct modify_during_search_test_ctx *ctx = req->context; + struct ldb_dn *dn = NULL, *new_dn = NULL; + TALLOC_CTX *tmp_ctx = talloc_new(ctx->test_ctx); + struct ldb_message *msg = NULL; + + assert_non_null(tmp_ctx); + + switch (ares->type) { + case LDB_REPLY_ENTRY: + { + const struct ldb_val *cn_val + = ldb_dn_get_component_val(ares->message->dn, 0); + const char *cn = (char *)cn_val->data; + ctx->res_count++; + if (strcmp(cn, "test_search_cn") == 0) { + ctx->got_cn = true; + } else if (strcmp(cn, "test_search_2_cn") == 0) { + ctx->got_2_cn = true; + } + if (ctx->res_count == 2) { + return LDB_SUCCESS; + } + break; + } + case LDB_REPLY_REFERRAL: + return LDB_SUCCESS; + + case LDB_REPLY_DONE: + return ldb_request_done(req, LDB_SUCCESS); + } + + if (ctx->rename) { + if (ctx->got_2_cn) { + /* Modify this one */ + dn = ldb_dn_new_fmt(tmp_ctx, + ctx->test_ctx->ldb, + "cn=test_search_2_cn,%s", + ldb_dn_get_linearized(ctx->basedn)); + } else { + dn = ldb_dn_new_fmt(tmp_ctx, + ctx->test_ctx->ldb, + "cn=test_search_cn,%s", + ldb_dn_get_linearized(ctx->basedn)); + } + assert_non_null(dn); + + new_dn = ldb_dn_new_fmt(tmp_ctx, + ctx->test_ctx->ldb, + "cn=test_search_cn_renamed," + "dc=not_search_test_entry"); + assert_non_null(new_dn); + + ret = ldb_rename(ctx->test_ctx->ldb, dn, new_dn); + assert_int_equal(ret, 0); + + } else { + if (ctx->got_2_cn) { + /* Delete this one */ + dn = ldb_dn_new_fmt(tmp_ctx, + ctx->test_ctx->ldb, + "cn=test_search_2_cn,%s", + ldb_dn_get_linearized(ctx->basedn)); + } else { + dn = ldb_dn_new_fmt(tmp_ctx, + ctx->test_ctx->ldb, + "cn=test_search_cn,%s", + ldb_dn_get_linearized(ctx->basedn)); + } + assert_non_null(dn); + + ret = ldb_delete(ctx->test_ctx->ldb, dn); + assert_int_equal(ret, 0); + } + + /* + * Now fill in the position we just removed from the + * index to ensure we fail the test (otherwise we just read + * past the end of the array and find the value we wanted to + * skip) + */ + msg = ldb_msg_new(tmp_ctx); + assert_non_null(msg); + + /* We deliberately use ou= not cn= here */ + msg->dn = ldb_dn_new_fmt(msg, + ctx->test_ctx->ldb, + "ou=test_search_cn_extra,%s", + ldb_dn_get_linearized(ctx->basedn)); + + ret = ldb_msg_add_string(msg, + "objectUUID", + "0123456789abcde3"); + + ret = ldb_add(ctx->test_ctx->ldb, + msg); + assert_int_equal(ret, LDB_SUCCESS); + + TALLOC_FREE(tmp_ctx); + return LDB_SUCCESS; +} + +static void test_ldb_callback_modify_during_search(void **state, bool add_index, + bool rename) +{ + struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state, + struct search_test_ctx); + struct modify_during_search_test_ctx + ctx = + { .res_count = 0, + .test_ctx = search_test_ctx->ldb_test_ctx, + .rename = rename + }; + + int ret; + struct ldb_request *req; + + ret = ldb_transaction_start(search_test_ctx->ldb_test_ctx->ldb); + assert_int_equal(ret, 0); + + if (add_index) { + struct ldb_message *msg; + struct ldb_dn *indexlist = ldb_dn_new(search_test_ctx, + search_test_ctx->ldb_test_ctx->ldb, + "@INDEXLIST"); + assert_non_null(indexlist); + + msg = ldb_msg_new(search_test_ctx); + assert_non_null(msg); + + msg->dn = indexlist; + + ret = ldb_msg_add_string(msg, "@IDXONE", "1"); + assert_int_equal(ret, LDB_SUCCESS); + ret = ldb_msg_add_string(msg, "@IDXATTR", "cn"); + assert_int_equal(ret, LDB_SUCCESS); + ret = ldb_add(search_test_ctx->ldb_test_ctx->ldb, + msg); + if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) { + msg->elements[0].flags = LDB_FLAG_MOD_ADD; + msg->elements[1].flags = LDB_FLAG_MOD_ADD; + ret = ldb_modify(search_test_ctx->ldb_test_ctx->ldb, + msg); + } + assert_int_equal(ret, LDB_SUCCESS); + + /* + * Now bring the IDXONE index into memory by modifying + * it. This exposes an issue in ldb_tdb + */ + msg = ldb_msg_new(search_test_ctx); + assert_non_null(msg); + + msg->dn = ldb_dn_new_fmt(search_test_ctx, + search_test_ctx->ldb_test_ctx->ldb, + "cn=test_search_cn_extra,%s", + search_test_ctx->base_dn); + + ret = ldb_msg_add_string(msg, + "objectUUID", + "0123456789abcde2"); + + ret = ldb_add(search_test_ctx->ldb_test_ctx->ldb, + msg); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_delete(search_test_ctx->ldb_test_ctx->ldb, + msg->dn); + assert_int_equal(ret, LDB_SUCCESS); + } + + tevent_loop_allow_nesting(search_test_ctx->ldb_test_ctx->ev); + + ctx.basedn + = ldb_dn_new_fmt(search_test_ctx, + search_test_ctx->ldb_test_ctx->ldb, + "%s", + search_test_ctx->base_dn); + assert_non_null(ctx.basedn); + + + /* + * This search must be over multiple items, and should include + * the new name after a rename, to show that it would match + * both before and after that modify + * + * This needs to be a search that isn't matched by an index so + * that we just use the one-level index. + */ + ret = ldb_build_search_req(&req, + search_test_ctx->ldb_test_ctx->ldb, + search_test_ctx, + ctx.basedn, + LDB_SCOPE_ONELEVEL, + "(cn=*)", + NULL, + NULL, + &ctx, + test_ldb_callback_modify_during_search_callback1, + NULL); + assert_int_equal(ret, 0); + + ret = ldb_request(search_test_ctx->ldb_test_ctx->ldb, req); + + if (ret == LDB_SUCCESS) { + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + } + assert_int_equal(ret, 0); + + ret = ldb_transaction_commit(search_test_ctx->ldb_test_ctx->ldb); + assert_int_equal(ret, 0); + + assert_int_equal(ctx.res_count, 2); + assert_int_equal(ctx.got_cn, true); + assert_int_equal(ctx.got_2_cn, true); +} + +static void test_ldb_callback_delete_during_indexed_search(void **state) +{ + test_ldb_callback_modify_during_search(state, true, false); +} + +static void test_ldb_callback_delete_during_unindexed_search(void **state) +{ + test_ldb_callback_modify_during_search(state, false, false); +} + +static void test_ldb_callback_rename_during_indexed_search(void **state) +{ + test_ldb_callback_modify_during_search(state, true, true); +} + +static void test_ldb_callback_rename_during_unindexed_search(void **state) +{ + test_ldb_callback_modify_during_search(state, false, true); +} + +static int ldb_case_test_setup(void **state) +{ + int ret; + struct ldb_ldif *ldif; + struct ldbtest_ctx *ldb_test_ctx; + const char *attrs_ldif = \ + "dn: @ATTRIBUTES\n" + "cn: CASE_INSENSITIVE\n" + "\n"; + struct keyval kvs[] = { + { "cn", "CaseInsensitiveValue" }, + { "uid", "CaseSensitiveValue" }, + { "objectUUID", "0123456789abcdef" }, + { NULL, NULL }, + }; + + + ldbtest_setup((void **) &ldb_test_ctx); + + while ((ldif = ldb_ldif_read_string(ldb_test_ctx->ldb, &attrs_ldif))) { + ret = ldb_add(ldb_test_ctx->ldb, ldif->msg); + assert_int_equal(ret, LDB_SUCCESS); + } + + ldb_test_add_data(ldb_test_ctx, + ldb_test_ctx, + "cn=CaseInsensitiveValue", + kvs); + + *state = ldb_test_ctx; + return 0; +} + +static int ldb_case_test_teardown(void **state) +{ + int ret; + struct ldbtest_ctx *ldb_test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + + struct ldb_dn *del_dn; + + del_dn = ldb_dn_new_fmt(ldb_test_ctx, + ldb_test_ctx->ldb, + "@ATTRIBUTES"); + assert_non_null(del_dn); + + ret = ldb_delete(ldb_test_ctx->ldb, del_dn); + assert_int_equal(ret, LDB_SUCCESS); + + assert_dn_doesnt_exist(ldb_test_ctx, + "@ATTRIBUTES"); + + ldb_test_remove_data(ldb_test_ctx, ldb_test_ctx, + "cn=CaseInsensitiveValue"); + + ldbtest_teardown((void **) &ldb_test_ctx); + return 0; +} + +static void test_ldb_attrs_case_insensitive(void **state) +{ + int cnt; + struct ldbtest_ctx *ldb_test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + + /* cn matches exact case */ + cnt = sub_search_count(ldb_test_ctx, "", "cn=CaseInsensitiveValue"); + assert_int_equal(cnt, 1); + + /* cn matches lower case */ + cnt = sub_search_count(ldb_test_ctx, "", "cn=caseinsensitivevalue"); + assert_int_equal(cnt, 1); + + /* uid matches exact case */ + cnt = sub_search_count(ldb_test_ctx, "", "uid=CaseSensitiveValue"); + assert_int_equal(cnt, 1); + + /* uid does not match lower case */ + cnt = sub_search_count(ldb_test_ctx, "", "uid=casesensitivevalue"); + assert_int_equal(cnt, 0); +} + +static struct ldb_schema_attribute cn_attr_1; +static struct ldb_schema_attribute cn_attr_2; +static struct ldb_schema_attribute default_attr; + +/* + override the name to attribute handler function + */ +static const struct ldb_schema_attribute *ldb_test_attribute_handler_override(struct ldb_context *ldb, + void *private_data, + const char *name) +{ + if (private_data != NULL && ldb_attr_cmp(name, "cn") == 0) { + return &cn_attr_1; + } else if (private_data == NULL && ldb_attr_cmp(name, "cn") == 0) { + return &cn_attr_2; + } else if (ldb_attr_cmp(name, "uid") == 0) { + return &cn_attr_2; + } + return &default_attr; +} + +static void test_ldb_attrs_case_handler(void **state) +{ + int cnt; + int ret; + const struct ldb_schema_syntax *syntax; + + struct ldbtest_ctx *ldb_test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + struct ldb_context *ldb = ldb_test_ctx->ldb; + + /* cn matches lower case */ + cnt = sub_search_count(ldb_test_ctx, "", "cn=caseinsensitivevalue"); + assert_int_equal(cnt, 1); + + syntax = ldb_standard_syntax_by_name(ldb, LDB_SYNTAX_OCTET_STRING); + assert_non_null(syntax); + + ret = ldb_schema_attribute_fill_with_syntax(ldb, ldb, + "*", 0, + syntax, &default_attr); + assert_int_equal(ret, LDB_SUCCESS); + + syntax = ldb_standard_syntax_by_name(ldb, LDB_SYNTAX_OCTET_STRING); + assert_non_null(syntax); + + ret = ldb_schema_attribute_fill_with_syntax(ldb, ldb, + "cn", 0, + syntax, &cn_attr_1); + assert_int_equal(ret, LDB_SUCCESS); + + /* + * Set an attribute handler, which will fail to match as we + * force case sensitive + */ + ldb_schema_attribute_set_override_handler(ldb, + ldb_test_attribute_handler_override, + (void *)1); + + /* cn does not matche lower case */ + cnt = sub_search_count(ldb_test_ctx, "", "cn=caseinsensitivevalue"); + assert_int_equal(cnt, 0); + + syntax = ldb_standard_syntax_by_name(ldb, LDB_SYNTAX_DIRECTORY_STRING); + assert_non_null(syntax); + + ret = ldb_schema_attribute_fill_with_syntax(ldb, ldb, + "cn", 0, + syntax, &cn_attr_2); + assert_int_equal(ret, LDB_SUCCESS); + + /* + * Set an attribute handler, which will match as we + * force case insensitive + */ + ldb_schema_attribute_set_override_handler(ldb, + ldb_test_attribute_handler_override, + NULL); + + /* cn matches lower case */ + cnt = sub_search_count(ldb_test_ctx, "", "cn=caseinsensitivevalue"); + assert_int_equal(cnt, 1); + +} + + +static void test_ldb_attrs_index_handler(void **state) +{ + int cnt; + int ret; + const struct ldb_schema_syntax *syntax; + struct ldb_ldif *ldif; + + const char *index_ldif = \ + "dn: @INDEXLIST\n" + "@IDXATTR: cn\n" + "\n"; + + struct ldbtest_ctx *ldb_test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + struct ldb_context *ldb = ldb_test_ctx->ldb; + + /* cn matches lower case */ + cnt = sub_search_count(ldb_test_ctx, "", "cn=caseinsensitivevalue"); + assert_int_equal(cnt, 1); + + syntax = ldb_standard_syntax_by_name(ldb, LDB_SYNTAX_OCTET_STRING); + assert_non_null(syntax); + + ret = ldb_schema_attribute_fill_with_syntax(ldb, ldb, + "cn", 0, + syntax, &cn_attr_1); + assert_int_equal(ret, LDB_SUCCESS); + + syntax = ldb_standard_syntax_by_name(ldb, LDB_SYNTAX_DIRECTORY_STRING); + assert_non_null(syntax); + + ret = ldb_schema_attribute_fill_with_syntax(ldb, ldb, + "cn", LDB_ATTR_FLAG_INDEXED, + syntax, &cn_attr_2); + assert_int_equal(ret, LDB_SUCCESS); + + syntax = ldb_standard_syntax_by_name(ldb, LDB_SYNTAX_OCTET_STRING); + assert_non_null(syntax); + + ret = ldb_schema_attribute_fill_with_syntax(ldb, ldb, + "", 0, + syntax, &default_attr); + assert_int_equal(ret, LDB_SUCCESS); + + /* + * Set an attribute handler + */ + ldb_schema_attribute_set_override_handler(ldb, + ldb_test_attribute_handler_override, + NULL); + + /* cn matches lower case */ + cnt = sub_search_count(ldb_test_ctx, "", "cn=caseinsensitivevalue"); + assert_int_equal(cnt, 1); + + /* Add the index (actually any modify will do) */ + while ((ldif = ldb_ldif_read_string(ldb_test_ctx->ldb, &index_ldif))) { + ret = ldb_add(ldb_test_ctx->ldb, ldif->msg); + if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) { + ldif->msg->elements[0].flags = LDB_FLAG_MOD_ADD; + ret = ldb_modify(ldb_test_ctx->ldb, + ldif->msg); + } + assert_int_equal(ret, LDB_SUCCESS); + } + + ldb_schema_set_override_indexlist(ldb, false); + + /* cn does match as there is an index now */ + cnt = sub_search_count(ldb_test_ctx, "", "cn=caseinsensitivevalue"); + assert_int_equal(cnt, 1); + + /* + * Set an attribute handler, which will later fail to match as we + * didn't re-index the DB + */ + ldb_schema_attribute_set_override_handler(ldb, + ldb_test_attribute_handler_override, + (void *)1); + + /* + * cn does not match as we changed the case sensitivity, but + * didn't re-index + * + * This shows that the override is in control + */ + cnt = sub_search_count(ldb_test_ctx, "", "cn=caseinsensitivevalue"); + assert_int_equal(cnt, 0); + +} + +static int ldb_case_attrs_index_test_teardown(void **state) +{ + int ret; + struct ldbtest_ctx *ldb_test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + struct ldb_dn *del_dn; + + del_dn = ldb_dn_new_fmt(ldb_test_ctx, + ldb_test_ctx->ldb, + "@INDEXLIST"); + assert_non_null(del_dn); + + ret = ldb_delete(ldb_test_ctx->ldb, del_dn); + if (ret != LDB_ERR_NO_SUCH_OBJECT) { + assert_int_equal(ret, LDB_SUCCESS); + } + + assert_dn_doesnt_exist(ldb_test_ctx, + "@INDEXLIST"); + + ldb_case_test_teardown(state); + return 0; +} + + +struct rename_test_ctx { + struct ldbtest_ctx *ldb_test_ctx; + + struct ldb_dn *basedn; + const char *str_basedn; + + const char *teardown_dn; +}; + +static int ldb_rename_test_setup(void **state) +{ + struct ldbtest_ctx *ldb_test_ctx; + struct rename_test_ctx *rename_test_ctx; + const char *strdn = "dc=rename_test_entry_from"; + + ldbtest_setup((void **) &ldb_test_ctx); + + rename_test_ctx = talloc(ldb_test_ctx, struct rename_test_ctx); + assert_non_null(rename_test_ctx); + rename_test_ctx->ldb_test_ctx = ldb_test_ctx; + assert_non_null(rename_test_ctx->ldb_test_ctx); + + rename_test_ctx->basedn = ldb_dn_new_fmt(rename_test_ctx, + rename_test_ctx->ldb_test_ctx->ldb, + "%s", strdn); + assert_non_null(rename_test_ctx->basedn); + + rename_test_ctx->str_basedn = strdn; + rename_test_ctx->teardown_dn = strdn; + + add_dn_with_cn(ldb_test_ctx, + rename_test_ctx->basedn, + "test_rename_cn_val", + "0123456789abcde0"); + + *state = rename_test_ctx; + return 0; +} + +static int ldb_rename_test_teardown(void **state) +{ + int ret; + struct rename_test_ctx *rename_test_ctx = talloc_get_type_abort(*state, + struct rename_test_ctx); + struct ldbtest_ctx *ldb_test_ctx; + struct ldb_dn *del_dn; + + ldb_test_ctx = rename_test_ctx->ldb_test_ctx; + + del_dn = ldb_dn_new_fmt(rename_test_ctx, + rename_test_ctx->ldb_test_ctx->ldb, + "%s", rename_test_ctx->teardown_dn); + assert_non_null(del_dn); + + ret = ldb_delete(ldb_test_ctx->ldb, del_dn); + assert_int_equal(ret, LDB_SUCCESS); + + assert_dn_doesnt_exist(ldb_test_ctx, + rename_test_ctx->teardown_dn); + + ldbtest_teardown((void **) &ldb_test_ctx); + return 0; +} + +static void test_ldb_rename(void **state) +{ + struct rename_test_ctx *rename_test_ctx = + talloc_get_type_abort(*state, struct rename_test_ctx); + int ret; + const char *str_new_dn = "dc=rename_test_entry_to"; + struct ldb_dn *new_dn; + + new_dn = ldb_dn_new_fmt(rename_test_ctx, + rename_test_ctx->ldb_test_ctx->ldb, + "%s", str_new_dn); + assert_non_null(new_dn); + + ret = ldb_rename(rename_test_ctx->ldb_test_ctx->ldb, + rename_test_ctx->basedn, + new_dn); + assert_int_equal(ret, LDB_SUCCESS); + + assert_dn_exists(rename_test_ctx->ldb_test_ctx, str_new_dn); + assert_dn_doesnt_exist(rename_test_ctx->ldb_test_ctx, + rename_test_ctx->str_basedn); + rename_test_ctx->teardown_dn = str_new_dn; + + /* FIXME - test the values which didn't change */ +} + +static void test_ldb_rename_from_doesnt_exist(void **state) +{ + struct rename_test_ctx *rename_test_ctx = talloc_get_type_abort( + *state, + struct rename_test_ctx); + int ret; + const char *str_new_dn = "dc=rename_test_entry_to"; + const char *str_bad_old_dn = "dc=rename_test_no_such_entry"; + struct ldb_dn *new_dn; + struct ldb_dn *bad_old_dn; + + new_dn = ldb_dn_new_fmt(rename_test_ctx, + rename_test_ctx->ldb_test_ctx->ldb, + "%s", str_new_dn); + assert_non_null(new_dn); + + bad_old_dn = ldb_dn_new_fmt(rename_test_ctx, + rename_test_ctx->ldb_test_ctx->ldb, + "%s", str_bad_old_dn); + assert_non_null(bad_old_dn); + + assert_dn_doesnt_exist(rename_test_ctx->ldb_test_ctx, + str_bad_old_dn); + + ret = ldb_rename(rename_test_ctx->ldb_test_ctx->ldb, + bad_old_dn, new_dn); + assert_int_equal(ret, LDB_ERR_NO_SUCH_OBJECT); + + assert_dn_doesnt_exist(rename_test_ctx->ldb_test_ctx, + str_new_dn); +} + +static void test_ldb_rename_to_exists(void **state) +{ + struct rename_test_ctx *rename_test_ctx = talloc_get_type_abort( + *state, + struct rename_test_ctx); + int ret; + const char *str_new_dn = "dc=rename_test_already_exists"; + struct ldb_dn *new_dn; + + new_dn = ldb_dn_new_fmt(rename_test_ctx, + rename_test_ctx->ldb_test_ctx->ldb, + "%s", str_new_dn); + assert_non_null(new_dn); + + add_dn_with_cn(rename_test_ctx->ldb_test_ctx, + new_dn, + "test_rename_cn_val", + "0123456789abcde1"); + + ret = ldb_rename(rename_test_ctx->ldb_test_ctx->ldb, + rename_test_ctx->basedn, + new_dn); + assert_int_equal(ret, LDB_ERR_ENTRY_ALREADY_EXISTS); + + /* Old object must still exist */ + assert_dn_exists(rename_test_ctx->ldb_test_ctx, + rename_test_ctx->str_basedn); + + ret = ldb_delete(rename_test_ctx->ldb_test_ctx->ldb, + new_dn); + assert_int_equal(ret, LDB_SUCCESS); + + assert_dn_exists(rename_test_ctx->ldb_test_ctx, + rename_test_ctx->teardown_dn); +} + +static void test_ldb_rename_self(void **state) +{ + struct rename_test_ctx *rename_test_ctx = talloc_get_type_abort( + *state, + struct rename_test_ctx); + int ret; + + /* Oddly enough, this is a success in ldb.. */ + ret = ldb_rename(rename_test_ctx->ldb_test_ctx->ldb, + rename_test_ctx->basedn, + rename_test_ctx->basedn); + assert_int_equal(ret, LDB_SUCCESS); + + /* Old object must still exist */ + assert_dn_exists(rename_test_ctx->ldb_test_ctx, + rename_test_ctx->str_basedn); +} + +static void test_ldb_rename_dn_case_change(void **state) +{ + struct rename_test_ctx *rename_test_ctx = talloc_get_type_abort( + *state, + struct rename_test_ctx); + int ret; + char *str_new_dn; + struct ldb_dn *new_dn; + unsigned i; + + str_new_dn = talloc_strdup(rename_test_ctx, rename_test_ctx->str_basedn); + assert_non_null(str_new_dn); + for (i = 0; str_new_dn[i]; i++) { + str_new_dn[i] = toupper(str_new_dn[i]); + } + + new_dn = ldb_dn_new_fmt(rename_test_ctx, + rename_test_ctx->ldb_test_ctx->ldb, + "%s", str_new_dn); + assert_non_null(new_dn); + + ret = ldb_rename(rename_test_ctx->ldb_test_ctx->ldb, + rename_test_ctx->basedn, + new_dn); + assert_int_equal(ret, LDB_SUCCESS); + + /* DNs are case insensitive, so both searches will match */ + assert_dn_exists(rename_test_ctx->ldb_test_ctx, str_new_dn); + assert_dn_exists(rename_test_ctx->ldb_test_ctx, + rename_test_ctx->str_basedn); + /* FIXME - test the values didn't change */ +} + +static int ldb_read_only_setup(void **state) +{ + struct ldbtest_ctx *test_ctx; + + ldbtest_setup((void **) &test_ctx); + + *state = test_ctx; + return 0; +} + +static int ldb_read_only_teardown(void **state) +{ + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + ldbtest_teardown((void **) &test_ctx); + return 0; +} + +static void test_read_only(void **state) +{ + struct ldb_context *ro_ldb = NULL; + struct ldb_context *rw_ldb = NULL; + int ret; + TALLOC_CTX *tmp_ctx = NULL; + + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + /* + * Close the ldb context freeing it this will ensure it exists on + * disk and can be opened in read only mode + */ + TALLOC_FREE(test_ctx->ldb); + + /* + * Open the database in read only and read write mode, + * ensure it's opened in read only mode first + */ + ro_ldb = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ro_ldb, test_ctx->dbpath, LDB_FLG_RDONLY, NULL); + assert_int_equal(ret, 0); + + rw_ldb = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(rw_ldb, test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, 0); + + + /* + * Set up a context for the temporary variables + */ + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* + * Ensure that we can search the read write database + */ + { + struct ldb_result *result = NULL; + struct ldb_dn *dn = ldb_dn_new_fmt(tmp_ctx, rw_ldb, + "dc=test"); + assert_non_null(dn); + + ret = ldb_search(rw_ldb, tmp_ctx, &result, dn, + LDB_SCOPE_BASE, NULL, NULL); + assert_int_equal(ret, LDB_SUCCESS); + TALLOC_FREE(result); + TALLOC_FREE(dn); + } + + /* + * Ensure that we can search the read only database + */ + { + struct ldb_result *result = NULL; + struct ldb_dn *dn = ldb_dn_new_fmt(tmp_ctx, ro_ldb, + "dc=test"); + assert_non_null(dn); + + ret = ldb_search(ro_ldb, tmp_ctx, &result, dn, + LDB_SCOPE_BASE, NULL, NULL); + assert_int_equal(ret, LDB_SUCCESS); + TALLOC_FREE(result); + TALLOC_FREE(dn); + } + /* + * Ensure that a write to the read only database fails + */ + { + struct ldb_message *msg = NULL; + msg = ldb_msg_new(tmp_ctx); + assert_non_null(msg); + + msg->dn = ldb_dn_new_fmt(msg, ro_ldb, "dc=test"); + assert_non_null(msg->dn); + + ret = ldb_msg_add_string(msg, "cn", "test_cn_val"); + assert_int_equal(ret, 0); + + ret = ldb_msg_add_string(msg, "objectUUID", + "0123456789abcde1"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_add(ro_ldb, msg); + assert_int_equal(ret, LDB_ERR_UNWILLING_TO_PERFORM); + TALLOC_FREE(msg); + } + + /* + * Ensure that a write to the read write database succeeds + */ + { + struct ldb_message *msg = NULL; + msg = ldb_msg_new(tmp_ctx); + assert_non_null(msg); + + msg->dn = ldb_dn_new_fmt(msg, rw_ldb, "dc=test"); + assert_non_null(msg->dn); + + ret = ldb_msg_add_string(msg, "cn", "test_cn_val"); + assert_int_equal(ret, 0); + + ret = ldb_msg_add_string(msg, "objectUUID", + "0123456789abcde2"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_add(rw_ldb, msg); + assert_int_equal(ret, LDB_SUCCESS); + TALLOC_FREE(msg); + } + + /* + * Ensure that a delete from a read only database fails + */ + { + struct ldb_dn *dn = ldb_dn_new_fmt(tmp_ctx, ro_ldb, "dc=test"); + assert_non_null(dn); + + ret = ldb_delete(ro_ldb, dn); + assert_int_equal(ret, LDB_ERR_UNWILLING_TO_PERFORM); + TALLOC_FREE(dn); + } + + + /* + * Ensure that a delete from a read write succeeds + */ + { + struct ldb_dn *dn = ldb_dn_new_fmt(tmp_ctx, rw_ldb, "dc=test"); + assert_non_null(dn); + + ret = ldb_delete(rw_ldb, dn); + assert_int_equal(ret, LDB_SUCCESS); + TALLOC_FREE(dn); + } + TALLOC_FREE(tmp_ctx); +} + +static bool unique_values = false; + +static int unique_index_test_module_add( + struct ldb_module *module, + struct ldb_request *req) +{ + if (unique_values) { + struct ldb_message *msg = discard_const(req->op.add.message); + struct ldb_message_element *el = NULL; + el = ldb_msg_find_element(msg, "cn"); + if (el != NULL) { + el->flags |= LDB_FLAG_INTERNAL_FORCE_UNIQUE_INDEX; + } + } + + return ldb_next_request(module, req); +} + +static int unique_index_test_module_init(struct ldb_module *module) +{ + return ldb_next_init(module); +} + +static const struct ldb_module_ops ldb_unique_index_test_module_ops = { + .name = "unique_index_test", + .init_context = unique_index_test_module_init, + .add = unique_index_test_module_add, +}; + +static int ldb_unique_index_test_setup(void **state) +{ + int ret; + struct ldb_ldif *ldif; + struct ldbtest_ctx *ldb_test_ctx; + const char *attrs_ldif = \ + "dn: @ATTRIBUTES\n" + "cn: UNIQUE_INDEX\n" + "\n"; + const char *index_ldif = \ + "dn: @INDEXLIST\n" + "@IDXATTR: cn\n" +#ifdef GUID_IDX + "@IDXGUID: objectUUID\n" + "@IDX_DN_GUID: GUID\n" +#endif + "\n"; + const char *options[] = {"modules:unique_index_test", NULL}; + + + ret = ldb_register_module(&ldb_unique_index_test_module_ops); + assert_true(ret == LDB_SUCCESS || ret == LDB_ERR_ENTRY_ALREADY_EXISTS); + ldbtest_noconn_setup((void **) &ldb_test_ctx); + + + ret = ldb_connect(ldb_test_ctx->ldb, ldb_test_ctx->dbpath, 0, options); + assert_int_equal(ret, 0); + + while ((ldif = ldb_ldif_read_string(ldb_test_ctx->ldb, &attrs_ldif))) { + ret = ldb_add(ldb_test_ctx->ldb, ldif->msg); + assert_int_equal(ret, LDB_SUCCESS); + } + + while ((ldif = ldb_ldif_read_string(ldb_test_ctx->ldb, &index_ldif))) { + ret = ldb_add(ldb_test_ctx->ldb, ldif->msg); + assert_int_equal(ret, LDB_SUCCESS); + } + + unique_values = true; + + *state = ldb_test_ctx; + return 0; +} + +static int ldb_unique_index_test_teardown(void **state) +{ + int ret; + struct ldbtest_ctx *ldb_test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + struct ldb_dn *del_dn; + + del_dn = ldb_dn_new_fmt(ldb_test_ctx, + ldb_test_ctx->ldb, + "@INDEXLIST"); + assert_non_null(del_dn); + + ret = ldb_delete(ldb_test_ctx->ldb, del_dn); + if (ret != LDB_ERR_NO_SUCH_OBJECT) { + assert_int_equal(ret, LDB_SUCCESS); + } + + assert_dn_doesnt_exist(ldb_test_ctx, + "@INDEXLIST"); + + TALLOC_FREE(del_dn); + + del_dn = ldb_dn_new_fmt(ldb_test_ctx, + ldb_test_ctx->ldb, + "@ATTRIBUTES"); + assert_non_null(del_dn); + + ret = ldb_delete(ldb_test_ctx->ldb, del_dn); + if (ret != LDB_ERR_NO_SUCH_OBJECT) { + assert_int_equal(ret, LDB_SUCCESS); + } + + assert_dn_doesnt_exist(ldb_test_ctx, + "@ATTRIBUTES"); + + ldbtest_teardown((void **) &ldb_test_ctx); + return 0; +} + + +static void test_ldb_add_unique_value_to_unique_index(void **state) +{ + int ret; + struct ldb_message *msg; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + msg = ldb_msg_new(tmp_ctx); + assert_non_null(msg); + + msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=test"); + assert_non_null(msg->dn); + + ret = ldb_msg_add_string(msg, "cn", "test_unique_index"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_msg_add_string(msg, "objectUUID", + "0123456789abcde1"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_add(test_ctx->ldb, msg); + assert_int_equal(ret, LDB_SUCCESS); + + talloc_free(tmp_ctx); +} + +static int ldb_non_unique_index_test_setup(void **state) +{ + int ret; + struct ldb_ldif *ldif; + struct ldbtest_ctx *ldb_test_ctx; + const char *index_ldif = \ + "dn: @INDEXLIST\n" + "@IDXATTR: cn\n" +#ifdef GUID_IDX + "@IDXGUID: objectUUID\n" + "@IDX_DN_GUID: GUID\n" +#endif + "\n"; + const char *options[] = {"modules:unique_index_test", NULL}; + + + ret = ldb_register_module(&ldb_unique_index_test_module_ops); + assert_true(ret == LDB_SUCCESS || ret == LDB_ERR_ENTRY_ALREADY_EXISTS); + ldbtest_noconn_setup((void **) &ldb_test_ctx); + + + ret = ldb_connect(ldb_test_ctx->ldb, ldb_test_ctx->dbpath, 0, options); + assert_int_equal(ret, 0); + + while ((ldif = ldb_ldif_read_string(ldb_test_ctx->ldb, &index_ldif))) { + ret = ldb_add(ldb_test_ctx->ldb, ldif->msg); + assert_int_equal(ret, LDB_SUCCESS); + } + + unique_values = true; + + *state = ldb_test_ctx; + return 0; +} + +static int ldb_non_unique_index_test_teardown(void **state) +{ + int ret; + struct ldbtest_ctx *ldb_test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + struct ldb_dn *del_dn; + + del_dn = ldb_dn_new_fmt(ldb_test_ctx, + ldb_test_ctx->ldb, + "@INDEXLIST"); + assert_non_null(del_dn); + + ret = ldb_delete(ldb_test_ctx->ldb, del_dn); + if (ret != LDB_ERR_NO_SUCH_OBJECT) { + assert_int_equal(ret, LDB_SUCCESS); + } + + assert_dn_doesnt_exist(ldb_test_ctx, + "@INDEXLIST"); + + TALLOC_FREE(del_dn); + + ldbtest_teardown((void **) &ldb_test_ctx); + return 0; +} + +static void test_ldb_add_duplicate_value_to_unique_index(void **state) +{ + int ret; + struct ldb_message *msg01; + struct ldb_message *msg02; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + msg01 = ldb_msg_new(tmp_ctx); + assert_non_null(msg01); + + msg01->dn = ldb_dn_new_fmt(msg01, test_ctx->ldb, "dc=test01"); + assert_non_null(msg01->dn); + + ret = ldb_msg_add_string(msg01, "cn", "test_unique_index"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_msg_add_string(msg01, "objectUUID", + "0123456789abcde1"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_add(test_ctx->ldb, msg01); + assert_int_equal(ret, LDB_SUCCESS); + + msg02 = ldb_msg_new(tmp_ctx); + assert_non_null(msg02); + + msg02->dn = ldb_dn_new_fmt(msg02, test_ctx->ldb, "dc=test02"); + assert_non_null(msg02->dn); + + ret = ldb_msg_add_string(msg02, "cn", "test_unique_index"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_msg_add_string(msg02, "objectUUID", + "0123456789abcde2"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_add(test_ctx->ldb, msg02); + assert_int_equal(ret, LDB_ERR_CONSTRAINT_VIOLATION); + talloc_free(tmp_ctx); +} + +static void test_ldb_add_to_index_duplicates_allowed(void **state) +{ + int ret; + struct ldb_message *msg01; + struct ldb_message *msg02; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + TALLOC_CTX *tmp_ctx; + + unique_values = false; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + + msg01 = ldb_msg_new(tmp_ctx); + assert_non_null(msg01); + + msg01->dn = ldb_dn_new_fmt(msg01, test_ctx->ldb, "dc=test01"); + assert_non_null(msg01->dn); + + ret = ldb_msg_add_string(msg01, "cn", "test_unique_index"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_msg_add_string(msg01, "objectUUID", + "0123456789abcde1"); + + ret = ldb_add(test_ctx->ldb, msg01); + assert_int_equal(ret, LDB_SUCCESS); + + msg02 = ldb_msg_new(tmp_ctx); + assert_non_null(msg02); + + msg02->dn = ldb_dn_new_fmt(msg02, test_ctx->ldb, "dc=test02"); + assert_non_null(msg02->dn); + + ret = ldb_msg_add_string(msg02, "cn", "test_unique_index"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_msg_add_string(msg02, "objectUUID", + "0123456789abcde2"); + + ret = ldb_add(test_ctx->ldb, msg02); + assert_int_equal(ret, LDB_SUCCESS); + talloc_free(tmp_ctx); +} + +static void test_ldb_add_to_index_unique_values_required(void **state) +{ + int ret; + struct ldb_message *msg01; + struct ldb_message *msg02; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + TALLOC_CTX *tmp_ctx; + + unique_values = true; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + + msg01 = ldb_msg_new(tmp_ctx); + assert_non_null(msg01); + + msg01->dn = ldb_dn_new_fmt(msg01, test_ctx->ldb, "dc=test01"); + assert_non_null(msg01->dn); + + ret = ldb_msg_add_string(msg01, "cn", "test_unique_index"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_msg_add_string(msg01, "objectUUID", + "0123456789abcde1"); + + ret = ldb_add(test_ctx->ldb, msg01); + assert_int_equal(ret, LDB_SUCCESS); + + msg02 = ldb_msg_new(tmp_ctx); + assert_non_null(msg02); + + msg02->dn = ldb_dn_new_fmt(msg02, test_ctx->ldb, "dc=test02"); + assert_non_null(msg02->dn); + + ret = ldb_msg_add_string(msg02, "cn", "test_unique_index"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_msg_add_string(msg02, "objectUUID", + "0123456789abcde2"); + + ret = ldb_add(test_ctx->ldb, msg02); + assert_int_equal(ret, LDB_ERR_CONSTRAINT_VIOLATION); + talloc_free(tmp_ctx); +} + +static void PRINTF_ATTRIBUTE(3, 0) ldb_debug_string( + void *context, + enum ldb_debug_level level, + const char *fmt, va_list ap) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(context, struct ldbtest_ctx); + + if (level <= LDB_DEBUG_WARNING) { + test_ctx->debug_string = talloc_vasprintf(test_ctx, fmt, ap); + } +} + +static void test_ldb_unique_index_duplicate_logging(void **state) +{ + int ret; + struct ldb_message *msg01; + struct ldb_message *msg02; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + TALLOC_CTX *tmp_ctx; + char *p = NULL; + + /* The GUID mode is not compatible with this test */ +#ifdef GUID_IDX + return; +#endif + + ldb_set_debug(test_ctx->ldb, ldb_debug_string, test_ctx); + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + msg01 = ldb_msg_new(tmp_ctx); + assert_non_null(msg01); + + msg01->dn = ldb_dn_new_fmt(msg01, test_ctx->ldb, "dc=test01"); + assert_non_null(msg01->dn); + + ret = ldb_msg_add_string(msg01, "cn", "test_unique_index"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_msg_add_string(msg01, "objectUUID", + "0123456789abcde1"); + + ret = ldb_add(test_ctx->ldb, msg01); + assert_int_equal(ret, LDB_SUCCESS); + + msg02 = ldb_msg_new(tmp_ctx); + assert_non_null(msg02); + + msg02->dn = ldb_dn_new_fmt(msg02, test_ctx->ldb, "dc=test02"); + assert_non_null(msg02->dn); + + ret = ldb_msg_add_string(msg02, "cn", "test_unique_index"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_msg_add_string(msg02, "objectUUID", + "0123456789abcde2"); + + ret = ldb_add(test_ctx->ldb, msg02); + assert_int_equal(ret, LDB_ERR_CONSTRAINT_VIOLATION); + + assert_non_null(test_ctx->debug_string); + p = strstr( + test_ctx->debug_string, + "unique index violation on cn " + "in dc=test02, conflicts with dc=test01 in " + "@INDEX:CN:test_unique_index"); + assert_non_null(p); + TALLOC_FREE(test_ctx->debug_string); + talloc_free(tmp_ctx); +} + +static void test_ldb_duplicate_dn_logging(void **state) +{ + int ret; + struct ldb_message *msg01; + struct ldb_message *msg02; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + TALLOC_CTX *tmp_ctx; + + /* The GUID mode is not compatible with this test */ +#ifdef GUID_IDX + return; +#endif + + ldb_set_debug(test_ctx->ldb, ldb_debug_string, test_ctx); + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + msg01 = ldb_msg_new(tmp_ctx); + assert_non_null(msg01); + + msg01->dn = ldb_dn_new_fmt(msg01, test_ctx->ldb, "dc=test01"); + assert_non_null(msg01->dn); + + ret = ldb_msg_add_string(msg01, "cn", "test_unique_index01"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_msg_add_string(msg01, "objectUUID", + "0123456789abcde1"); + + ret = ldb_add(test_ctx->ldb, msg01); + assert_int_equal(ret, LDB_SUCCESS); + + msg02 = ldb_msg_new(tmp_ctx); + assert_non_null(msg02); + + msg02->dn = ldb_dn_new_fmt(msg02, test_ctx->ldb, "dc=test01"); + assert_non_null(msg02->dn); + + ret = ldb_msg_add_string(msg02, "cn", "test_unique_index02"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_msg_add_string(msg02, "objectUUID", + "0123456789abcde2"); + + ret = ldb_add(test_ctx->ldb, msg02); + assert_int_equal(ret, LDB_ERR_ENTRY_ALREADY_EXISTS); + + assert_null(test_ctx->debug_string); + talloc_free(tmp_ctx); +} + +static int ldb_guid_index_test_setup(void **state) +{ + int ret; + struct ldb_ldif *ldif; + struct ldbtest_ctx *ldb_test_ctx; + const char *attrs_ldif = \ + "dn: @ATTRIBUTES\n" + "cn: UNIQUE_INDEX\n" + "\n"; + const char *index_ldif = \ + "dn: @INDEXLIST\n" + "@IDXATTR: cn\n" + "@IDXGUID: objectUUID\n" + "@IDX_DN_GUID: GUID\n" + "\n"; + + ldbtest_noconn_setup((void **) &ldb_test_ctx); + + + ret = ldb_connect(ldb_test_ctx->ldb, ldb_test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, 0); + + while ((ldif = ldb_ldif_read_string(ldb_test_ctx->ldb, &attrs_ldif))) { + ret = ldb_add(ldb_test_ctx->ldb, ldif->msg); + assert_int_equal(ret, LDB_SUCCESS); + } + + while ((ldif = ldb_ldif_read_string(ldb_test_ctx->ldb, &index_ldif))) { + ret = ldb_add(ldb_test_ctx->ldb, ldif->msg); + assert_int_equal(ret, LDB_SUCCESS); + } + + *state = ldb_test_ctx; + return 0; +} + +static int ldb_guid_index_test_teardown(void **state) +{ + int ret; + struct ldbtest_ctx *ldb_test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + struct ldb_dn *del_dn; + + del_dn = ldb_dn_new_fmt(ldb_test_ctx, + ldb_test_ctx->ldb, + "@INDEXLIST"); + assert_non_null(del_dn); + + ret = ldb_delete(ldb_test_ctx->ldb, del_dn); + if (ret != LDB_ERR_NO_SUCH_OBJECT) { + assert_int_equal(ret, LDB_SUCCESS); + } + + assert_dn_doesnt_exist(ldb_test_ctx, + "@INDEXLIST"); + + TALLOC_FREE(del_dn); + + del_dn = ldb_dn_new_fmt(ldb_test_ctx, + ldb_test_ctx->ldb, + "@ATTRIBUTES"); + assert_non_null(del_dn); + + ret = ldb_delete(ldb_test_ctx->ldb, del_dn); + if (ret != LDB_ERR_NO_SUCH_OBJECT) { + assert_int_equal(ret, LDB_SUCCESS); + } + + assert_dn_doesnt_exist(ldb_test_ctx, + "@ATTRIBUTES"); + + ldbtest_teardown((void **) &ldb_test_ctx); + return 0; +} + + +static void test_ldb_unique_index_duplicate_with_guid(void **state) +{ + int ret; + struct ldb_message *msg01; + struct ldb_message *msg02; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + TALLOC_CTX *tmp_ctx; + char *p = NULL; + + ldb_set_debug(test_ctx->ldb, ldb_debug_string, test_ctx); + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + msg01 = ldb_msg_new(tmp_ctx); + assert_non_null(msg01); + + msg01->dn = ldb_dn_new_fmt(msg01, test_ctx->ldb, "dc=test01"); + assert_non_null(msg01->dn); + + ret = ldb_msg_add_string(msg01, "cn", "test_unique_index"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_msg_add_string(msg01, "objectUUID", "0123456789abcdef"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_add(test_ctx->ldb, msg01); + assert_int_equal(ret, LDB_SUCCESS); + + msg02 = ldb_msg_new(tmp_ctx); + assert_non_null(msg02); + + msg02->dn = ldb_dn_new_fmt(msg02, test_ctx->ldb, "dc=test02"); + assert_non_null(msg02->dn); + + ret = ldb_msg_add_string(msg02, "cn", "test_unique_index"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_msg_add_string(msg02, "objectUUID", "0123456789abcde0"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_add(test_ctx->ldb, msg02); + assert_int_equal(ret, LDB_ERR_CONSTRAINT_VIOLATION); + + assert_non_null(test_ctx->debug_string); + p = strstr( + test_ctx->debug_string, + "unique index violation on cn in dc=test02, conflicts with " + "objectUUID 0123456789abcdef in @INDEX:CN:test_unique_index"); + assert_non_null(p); + TALLOC_FREE(test_ctx->debug_string); + talloc_free(tmp_ctx); +} + +static void test_ldb_guid_index_duplicate_dn_logging(void **state) +{ + int ret; + struct ldb_message *msg01; + struct ldb_message *msg02; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + TALLOC_CTX *tmp_ctx; + + ldb_set_debug(test_ctx->ldb, ldb_debug_string, test_ctx); + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + msg01 = ldb_msg_new(tmp_ctx); + assert_non_null(msg01); + + msg01->dn = ldb_dn_new_fmt(msg01, test_ctx->ldb, "dc=test01"); + assert_non_null(msg01->dn); + + ret = ldb_msg_add_string(msg01, "cn", "test_unique_index01"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_msg_add_string(msg01, "objectUUID", "0123456789abcdef"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_add(test_ctx->ldb, msg01); + assert_int_equal(ret, LDB_SUCCESS); + + msg02 = ldb_msg_new(tmp_ctx); + assert_non_null(msg02); + + msg02->dn = ldb_dn_new_fmt(msg02, test_ctx->ldb, "dc=test01"); + assert_non_null(msg02->dn); + + ret = ldb_msg_add_string(msg02, "cn", "test_unique_index02"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_msg_add_string(msg02, "objectUUID", "0123456789abcde1"); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_add(test_ctx->ldb, msg02); + assert_int_equal(ret, LDB_ERR_ENTRY_ALREADY_EXISTS); + + assert_null(test_ctx->debug_string); + talloc_free(tmp_ctx); +} + +static void test_ldb_talloc_destructor_transaction_cleanup(void **state) +{ + struct ldbtest_ctx *test_ctx = NULL; + + test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx); + assert_non_null(test_ctx); + + ldb_transaction_start(test_ctx->ldb); + + /* + * Trigger the destructor + */ + TALLOC_FREE(test_ctx->ldb); + + /* + * Now ensure that a new connection can be opened + */ + { + TALLOC_CTX *tctx = talloc_new(test_ctx); + struct ldbtest_ctx *ctx = talloc_zero(tctx, struct ldbtest_ctx); + struct ldb_dn *basedn; + struct ldb_result *result = NULL; + int ret; + + ldbtest_setup((void *)&ctx); + + basedn = ldb_dn_new_fmt(tctx, ctx->ldb, "dc=test"); + assert_non_null(basedn); + + ret = ldb_search(ctx->ldb, + tctx, + &result, + basedn, + LDB_SCOPE_BASE, + NULL, + NULL); + assert_int_equal(ret, 0); + assert_non_null(result); + assert_int_equal(result->count, 0); + + ldbtest_teardown((void *)&ctx); + } +} + +#ifdef TEST_LMDB +static int test_ldb_multiple_connections_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + int ret; + int pipes[2]; + char buf[2]; + int pid, child_pid; + int wstatus; + + switch (ares->type) { + case LDB_REPLY_ENTRY: + break; + + case LDB_REPLY_REFERRAL: + return LDB_SUCCESS; + + case LDB_REPLY_DONE: + return ldb_request_done(req, LDB_SUCCESS); + } + + { + /* + * We open a new ldb on an ldb that is already open and + * then close it. + * + * If the multiple connection wrapping is correct the + * underlying MDB_env will be left open and we should see + * an active reader in the child we fork next + */ + struct ldb_context *ldb = NULL; + struct tevent_context *ev = NULL; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + + ev = tevent_context_init(mem_ctx); + assert_non_null(ev); + + ldb = ldb_init(mem_ctx, ev); + assert_non_null(ldb); + + ret = ldb_connect(ldb, TEST_BE"://apitest.ldb" , 0, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + TALLOC_FREE(ldb); + TALLOC_FREE(mem_ctx); + } + + ret = pipe(pipes); + assert_int_equal(ret, 0); + + child_pid = fork(); + if (child_pid == 0) { + struct MDB_env *env = NULL; + struct MDB_envinfo stat; + close(pipes[0]); + + /* + * Check that there are exactly two readers on the MDB file + * backing the ldb. + * + */ + ret = mdb_env_create(&env); + if (ret != 0) { + print_error(__location__ + " mdb_env_create returned (%d)", + ret); + exit(ret); + } + + ret = mdb_env_open(env, + "apitest.ldb", + MDB_NOSUBDIR | MDB_NOTLS, + 0644); + if (ret != 0) { + print_error(__location__ + " mdb_env_open returned (%d)", + ret); + exit(ret); + } + + ret = mdb_env_info(env, &stat); + if (ret != 0) { + print_error(__location__ + " mdb_env_info returned (%d)", + ret); + exit(ret); + } + if (stat.me_numreaders != 2) { + print_error(__location__ + " Incorrect number of readers (%d)", + stat.me_numreaders); + exit(LDB_ERR_CONSTRAINT_VIOLATION); + } + + ret = write(pipes[1], "GO", 2); + if (ret != 2) { + print_error(__location__ + " write returned (%d)", + ret); + exit(LDB_ERR_OPERATIONS_ERROR); + } + exit(LDB_SUCCESS); + } + close(pipes[1]); + ret = read(pipes[0], buf, 2); + assert_int_equal(ret, 2); + + pid = waitpid(child_pid, &wstatus, 0); + assert_int_equal(pid, child_pid); + + assert_true(WIFEXITED(wstatus)); + + assert_int_equal(WEXITSTATUS(wstatus), 0); + return LDB_SUCCESS; + +} + +static void test_ldb_close_with_multiple_connections(void **state) +{ + struct search_test_ctx *search_test_ctx = NULL; + struct ldb_dn *search_dn = NULL; + struct ldb_request *req = NULL; + int ret = 0; + + search_test_ctx = talloc_get_type_abort(*state, struct search_test_ctx); + assert_non_null(search_test_ctx); + + search_dn = ldb_dn_new_fmt(search_test_ctx, + search_test_ctx->ldb_test_ctx->ldb, + "cn=test_search_cn," + "dc=search_test_entry"); + assert_non_null(search_dn); + + /* + * The search just needs to call DONE, we don't care about the + * contents of the search for this test + */ + ret = ldb_build_search_req(&req, + search_test_ctx->ldb_test_ctx->ldb, + search_test_ctx, + search_dn, + LDB_SCOPE_SUBTREE, + "(&(!(filterAttr=*))" + "(cn=test_search_cn))", + NULL, + NULL, + NULL, + test_ldb_multiple_connections_callback, + NULL); + assert_int_equal(ret, 0); + + ret = ldb_request(search_test_ctx->ldb_test_ctx->ldb, req); + assert_int_equal(ret, 0); + + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + assert_int_equal(ret, 0); +} + +#endif + +static void test_transaction_start_across_fork(void **state) +{ + struct ldb_context *ldb1 = NULL; + int ret; + struct ldbtest_ctx *test_ctx = NULL; + int pipes[2]; + char buf[2]; + int wstatus; + pid_t pid, child_pid; + + test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx); + + /* + * Open the database + */ + ldb1 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb1, test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, 0); + + ret = pipe(pipes); + assert_int_equal(ret, 0); + + child_pid = fork(); + if (child_pid == 0) { + close(pipes[0]); + ret = ldb_transaction_start(ldb1); + if (ret != LDB_ERR_PROTOCOL_ERROR) { + print_error(__location__": ldb_transaction_start " + "returned (%d) %s\n", + ret, + ldb1->err_string); + exit(LDB_ERR_OTHER); + } + + ret = write(pipes[1], "GO", 2); + if (ret != 2) { + print_error(__location__ + " write returned (%d)", + ret); + exit(LDB_ERR_OPERATIONS_ERROR); + } + exit(LDB_SUCCESS); + } + close(pipes[1]); + ret = read(pipes[0], buf, 2); + assert_int_equal(ret, 2); + + pid = waitpid(child_pid, &wstatus, 0); + assert_int_equal(pid, child_pid); + + assert_true(WIFEXITED(wstatus)); + + assert_int_equal(WEXITSTATUS(wstatus), 0); +} + +static void test_transaction_commit_across_fork(void **state) +{ + struct ldb_context *ldb1 = NULL; + int ret; + struct ldbtest_ctx *test_ctx = NULL; + int pipes[2]; + char buf[2]; + int wstatus; + pid_t pid, child_pid; + + test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx); + + /* + * Open the database + */ + ldb1 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb1, test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, 0); + + ret = ldb_transaction_start(ldb1); + assert_int_equal(ret, 0); + + ret = pipe(pipes); + assert_int_equal(ret, 0); + + child_pid = fork(); + if (child_pid == 0) { + close(pipes[0]); + ret = ldb_transaction_commit(ldb1); + + if (ret != LDB_ERR_PROTOCOL_ERROR) { + print_error(__location__": ldb_transaction_commit " + "returned (%d) %s\n", + ret, + ldb1->err_string); + exit(LDB_ERR_OTHER); + } + + ret = write(pipes[1], "GO", 2); + if (ret != 2) { + print_error(__location__ + " write returned (%d)", + ret); + exit(LDB_ERR_OPERATIONS_ERROR); + } + exit(LDB_SUCCESS); + } + close(pipes[1]); + ret = read(pipes[0], buf, 2); + assert_int_equal(ret, 2); + + pid = waitpid(child_pid, &wstatus, 0); + assert_int_equal(pid, child_pid); + + assert_true(WIFEXITED(wstatus)); + + assert_int_equal(WEXITSTATUS(wstatus), 0); +} + +static void test_lock_read_across_fork(void **state) +{ + struct ldb_context *ldb1 = NULL; + int ret; + struct ldbtest_ctx *test_ctx = NULL; + int pipes[2]; + char buf[2]; + int wstatus; + pid_t pid, child_pid; + + test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx); + + /* + * Open the database + */ + ldb1 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb1, test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, 0); + + ret = pipe(pipes); + assert_int_equal(ret, 0); + + child_pid = fork(); + if (child_pid == 0) { + struct ldb_dn *basedn; + struct ldb_result *result = NULL; + + close(pipes[0]); + + basedn = ldb_dn_new_fmt(test_ctx, test_ctx->ldb, "dc=test"); + assert_non_null(basedn); + + ret = ldb_search(test_ctx->ldb, + test_ctx, + &result, + basedn, + LDB_SCOPE_BASE, + NULL, + NULL); + if (ret != LDB_ERR_PROTOCOL_ERROR) { + print_error(__location__": ldb_search " + "returned (%d) %s\n", + ret, + ldb1->err_string); + exit(LDB_ERR_OTHER); + } + + ret = write(pipes[1], "GO", 2); + if (ret != 2) { + print_error(__location__ + " write returned (%d)", + ret); + exit(LDB_ERR_OPERATIONS_ERROR); + } + exit(LDB_SUCCESS); + } + close(pipes[1]); + ret = read(pipes[0], buf, 2); + assert_int_equal(ret, 2); + + pid = waitpid(child_pid, &wstatus, 0); + assert_int_equal(pid, child_pid); + + assert_true(WIFEXITED(wstatus)); + + assert_int_equal(WEXITSTATUS(wstatus), 0); + + { + /* + * Ensure that the search actually succeeds on the opening + * pid + */ + struct ldb_dn *basedn; + struct ldb_result *result = NULL; + + close(pipes[0]); + + basedn = ldb_dn_new_fmt(test_ctx, test_ctx->ldb, "dc=test"); + assert_non_null(basedn); + + ret = ldb_search(test_ctx->ldb, + test_ctx, + &result, + basedn, + LDB_SCOPE_BASE, + NULL, + NULL); + assert_int_equal(0, ret); + } +} + +static void test_multiple_opens_across_fork(void **state) +{ + struct ldb_context *ldb1 = NULL; + struct ldb_context *ldb2 = NULL; + int ret; + struct ldbtest_ctx *test_ctx = NULL; + int pipes[2]; + char buf[2]; + int wstatus; + pid_t pid, child_pid; + + test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx); + + /* + * Open the database again + */ + ldb1 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb1, test_ctx->dbpath, LDB_FLG_RDONLY, NULL); + assert_int_equal(ret, 0); + + ldb2 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb2, test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, 0); + + ret = pipe(pipes); + assert_int_equal(ret, 0); + + child_pid = fork(); + if (child_pid == 0) { + struct ldb_context *ldb3 = NULL; + + close(pipes[0]); + ldb3 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb3, test_ctx->dbpath, 0, NULL); + if (ret != 0) { + print_error(__location__": ldb_connect returned (%d)\n", + ret); + exit(ret); + } + ret = write(pipes[1], "GO", 2); + if (ret != 2) { + print_error(__location__ + " write returned (%d)", + ret); + exit(LDB_ERR_OPERATIONS_ERROR); + } + exit(LDB_SUCCESS); + } + close(pipes[1]); + ret = read(pipes[0], buf, 2); + assert_int_equal(ret, 2); + + pid = waitpid(child_pid, &wstatus, 0); + assert_int_equal(pid, child_pid); + + assert_true(WIFEXITED(wstatus)); + + assert_int_equal(WEXITSTATUS(wstatus), 0); +} + +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_connect, + ldbtest_noconn_setup, + ldbtest_noconn_teardown), + cmocka_unit_test_setup_teardown(test_ldif_message, + ldbtest_noconn_setup, + ldbtest_noconn_teardown), + cmocka_unit_test_setup_teardown(test_ldif_message_redacted, + ldbtest_noconn_setup, + ldbtest_noconn_teardown), + cmocka_unit_test_setup_teardown(test_ldb_add, + ldbtest_setup, + ldbtest_teardown), + cmocka_unit_test_setup_teardown(test_ldb_search, + ldbtest_setup, + ldbtest_teardown), + cmocka_unit_test_setup_teardown(test_ldb_del, + ldbtest_setup, + ldbtest_teardown), + cmocka_unit_test_setup_teardown(test_ldb_del_noexist, + ldbtest_setup, + ldbtest_teardown), + cmocka_unit_test_setup_teardown(test_ldb_handle, + ldbtest_setup, + ldbtest_teardown), + cmocka_unit_test_setup_teardown(test_ldb_build_search_req, + ldbtest_setup, + ldbtest_teardown), + cmocka_unit_test_setup_teardown(test_transactions, + ldbtest_setup, + ldbtest_teardown), + cmocka_unit_test_setup_teardown(test_nested_transactions, + ldbtest_setup, + ldbtest_teardown), + cmocka_unit_test_setup_teardown(test_ldb_modify_add_key, + ldb_modify_test_setup, + ldb_modify_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_modify_extend_key, + ldb_modify_test_setup, + ldb_modify_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_modify_add_key_noval, + ldb_modify_test_setup, + ldb_modify_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_modify_replace_key, + ldb_modify_test_setup, + ldb_modify_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_modify_replace_noexist_key, + ldb_modify_test_setup, + ldb_modify_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_modify_replace_zero_vals, + ldb_modify_test_setup, + ldb_modify_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_modify_replace_noexist_key_zero_vals, + ldb_modify_test_setup, + ldb_modify_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_modify_del_key, + ldb_modify_test_setup, + ldb_modify_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_modify_del_keyval, + ldb_modify_test_setup, + ldb_modify_test_teardown), + cmocka_unit_test_setup_teardown(test_search_match_none, + ldb_search_test_setup, + ldb_search_test_teardown), + cmocka_unit_test_setup_teardown(test_search_match_one, + ldb_search_test_setup, + ldb_search_test_teardown), + cmocka_unit_test_setup_teardown(test_search_match_filter, + ldb_search_test_setup, + ldb_search_test_teardown), + cmocka_unit_test_setup_teardown(test_search_match_both, + ldb_search_test_setup, + ldb_search_test_teardown), + cmocka_unit_test_setup_teardown(test_search_match_basedn, + ldb_search_test_setup, + ldb_search_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_search_against_transaction, + ldb_search_test_setup, + ldb_search_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_modify_during_unindexed_search, + ldb_search_test_setup, + ldb_search_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_modify_during_indexed_search, + ldb_search_test_setup, + ldb_search_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_rename_during_unindexed_search, + ldb_search_test_setup, + ldb_search_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_rename_during_indexed_search, + ldb_search_test_setup, + ldb_search_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_callback_rename_during_unindexed_search, + ldb_search_test_setup, + ldb_search_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_callback_rename_during_indexed_search, + ldb_search_test_setup, + ldb_search_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_callback_delete_during_unindexed_search, + ldb_search_test_setup, + ldb_search_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_callback_delete_during_indexed_search, + ldb_search_test_setup, + ldb_search_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_modify_during_whole_search, + ldb_search_test_setup, + ldb_search_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_modify_before_ldb_wait, + ldb_search_test_setup, + ldb_search_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_attrs_case_insensitive, + ldb_case_test_setup, + ldb_case_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_attrs_case_handler, + ldb_case_test_setup, + ldb_case_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_attrs_index_handler, + ldb_case_test_setup, + ldb_case_attrs_index_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_rename, + ldb_rename_test_setup, + ldb_rename_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_rename_from_doesnt_exist, + ldb_rename_test_setup, + ldb_rename_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_rename_to_exists, + ldb_rename_test_setup, + ldb_rename_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_rename_self, + ldb_rename_test_setup, + ldb_rename_test_teardown), + cmocka_unit_test_setup_teardown(test_ldb_rename_dn_case_change, + ldb_rename_test_setup, + ldb_rename_test_teardown), + cmocka_unit_test_setup_teardown(test_read_only, + ldb_read_only_setup, + ldb_read_only_teardown), + cmocka_unit_test_setup_teardown( + test_ldb_add_unique_value_to_unique_index, + ldb_unique_index_test_setup, + ldb_unique_index_test_teardown), + cmocka_unit_test_setup_teardown( + test_ldb_add_duplicate_value_to_unique_index, + ldb_unique_index_test_setup, + ldb_unique_index_test_teardown), + cmocka_unit_test_setup_teardown( + test_ldb_add_to_index_duplicates_allowed, + ldb_non_unique_index_test_setup, + ldb_non_unique_index_test_teardown), + cmocka_unit_test_setup_teardown( + test_ldb_add_to_index_unique_values_required, + ldb_non_unique_index_test_setup, + ldb_non_unique_index_test_teardown), + /* These tests are not compatible with mdb */ + cmocka_unit_test_setup_teardown( + test_ldb_unique_index_duplicate_logging, + ldb_unique_index_test_setup, + ldb_unique_index_test_teardown), + cmocka_unit_test_setup_teardown( + test_ldb_duplicate_dn_logging, + ldb_unique_index_test_setup, + ldb_unique_index_test_teardown), + cmocka_unit_test_setup_teardown( + test_ldb_guid_index_duplicate_dn_logging, + ldb_guid_index_test_setup, + ldb_guid_index_test_teardown), + cmocka_unit_test_setup_teardown( + test_ldb_unique_index_duplicate_with_guid, + ldb_guid_index_test_setup, + ldb_guid_index_test_teardown), + cmocka_unit_test_setup_teardown( + test_ldb_talloc_destructor_transaction_cleanup, + ldbtest_setup, + ldbtest_teardown), +#ifdef TEST_LMDB + cmocka_unit_test_setup_teardown( + test_ldb_close_with_multiple_connections, + ldb_search_test_setup, + ldb_search_test_teardown), +#endif + cmocka_unit_test_setup_teardown( + test_transaction_start_across_fork, + ldbtest_setup, + ldbtest_teardown), + cmocka_unit_test_setup_teardown( + test_transaction_commit_across_fork, + ldbtest_setup, + ldbtest_teardown), + cmocka_unit_test_setup_teardown( + test_lock_read_across_fork, + ldbtest_setup, + ldbtest_teardown), + cmocka_unit_test_setup_teardown( + test_multiple_opens_across_fork, + ldbtest_setup, + ldbtest_teardown), + }; + + if (argc > 1) { + cmocka_set_test_filter(argv[1]); + } + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/ldb/tests/ldb_msg.c b/lib/ldb/tests/ldb_msg.c new file mode 100644 index 0000000..31786a9 --- /dev/null +++ b/lib/ldb/tests/ldb_msg.c @@ -0,0 +1,380 @@ +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + */ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +struct test_ctx { + struct ldb_message *msg; +}; + +static int ldb_msg_setup(void **state) +{ + struct test_ctx *test_ctx; + + test_ctx = talloc_zero(NULL, struct test_ctx); + assert_non_null(test_ctx); + + test_ctx->msg = ldb_msg_new(test_ctx); + + *state = test_ctx; + return 0; +} + +static int ldb_msg_teardown(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + + talloc_free(test_ctx); + return 0; +} + + +static void add_uint_value(struct test_ctx *test_ctx, + struct ldb_message *msg, + const char *attr, + unsigned int x) +{ + int ret; + struct ldb_val v, v_dup; + char s[5]; + snprintf(s, sizeof(s), "%04x", x); + v.data = (uint8_t *)s; + v.length = 4; + v_dup = ldb_val_dup(test_ctx, &v); + assert_non_null(v_dup.data); + assert_ptr_not_equal(v_dup.data, v.data); + assert_int_equal(v_dup.length, 4); + + ret = ldb_msg_add_value(msg, attr, &v_dup, NULL); + assert_int_equal(ret, LDB_SUCCESS); +} + + +static void test_ldb_msg_find_duplicate_val(void **state) +{ + int ret; + unsigned int i; + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + struct ldb_message *msg = test_ctx->msg; + struct ldb_message_element *el; + struct ldb_val dummy; + struct ldb_val *dupe = &dummy; /* so we can tell it was modified to NULL, not left as NULL */ + + ret = ldb_msg_add_empty(msg, "el1", 0, &el); + assert_int_equal(ret, LDB_SUCCESS); + + /* An empty message contains no duplicates */ + ret = ldb_msg_find_duplicate_val(NULL, test_ctx, el, &dupe, 0); + assert_int_equal(ret, LDB_SUCCESS); + assert_null(dupe); + + for (i = 0; i < 5; i++) { + add_uint_value(test_ctx, msg, "el1", i); + } + /* at this point there are no duplicates, and the check uses the naive + quadratic path */ + ret = ldb_msg_find_duplicate_val(NULL, test_ctx, el, &dupe, 0); + assert_int_equal(ret, LDB_SUCCESS); + assert_null(dupe); + + /* add a duplicate, still using quadratric path */ + add_uint_value(test_ctx, msg, "el1", 3); + ret = ldb_msg_find_duplicate_val(NULL, test_ctx, el, &dupe, 0); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(dupe); + assert_int_equal(dupe->length, 4); + assert_memory_equal(dupe->data, "0003", 4); + + /* add some more, triggering algorithmic jump */ + for (i = 2; i < 11; i++) { + add_uint_value(test_ctx, msg, "el1", i); + } + ret = ldb_msg_find_duplicate_val(NULL, test_ctx, el, &dupe, 0); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(dupe); + assert_int_equal(dupe->length, 4); + /*XXX not really guaranteed by the API */ + assert_memory_equal(dupe->data, "0002", 4); + + /* start a new element without duplicates, for the clever algorithm */ + ldb_msg_add_empty(msg, "el2", 0, &el); + for (i = 0; i < 12; i++) { + add_uint_value(test_ctx, msg, "el2", i); + } + ret = ldb_msg_find_duplicate_val(NULL, test_ctx, el, &dupe, 0); + assert_int_equal(ret, LDB_SUCCESS); + assert_null(dupe); +} + + +static struct ldb_message_element *new_msg_element(TALLOC_CTX *mem_ctx, + const char *name, + unsigned int value_offset, + unsigned int num_values) +{ + unsigned int i, x; + struct ldb_message_element *el = talloc_zero(mem_ctx, + struct ldb_message_element); + + el->values = talloc_array(el, struct ldb_val, num_values); + for (i = 0; i < num_values; i++) { + struct ldb_val v; + char s[50]; + v.data = (uint8_t *)s; + /* % 3 is to ensure the values list is unsorted */ + x = i + value_offset; + v.length = snprintf(s, sizeof(s), "%u %u", x % 3, x); + el->values[i] = ldb_val_dup(mem_ctx, &v); + } + el->name = name; + el->num_values = num_values; + return el; +} + +static void _assert_element_equal(struct ldb_message_element *a, + struct ldb_message_element *b, + const char * const file, + const int line) +{ + unsigned int i; + _assert_int_equal(a->num_values, b->num_values, file, line); + _assert_int_equal(a->flags, b->flags, file, line); + _assert_string_equal(a->name, b->name, file, line); + for (i = 0; i < a->num_values; i++) { + struct ldb_val *v1 = &a->values[i]; + struct ldb_val *v2 = &b->values[i]; + _assert_int_equal(v1->length, v2->length, file, line); + _assert_memory_equal(v1->data, v2->data, v1->length, + file, line); + } +} + +#define assert_element_equal(a, b) \ + _assert_element_equal((a), (b), \ + __FILE__, __LINE__) + + +static void test_ldb_msg_find_common_values(void **state) +{ + /* we only use the state as a talloc context */ + struct ldb_message_element *el, *el2, *el3, *el4, *el2b, *empty; + struct ldb_message_element *orig, *orig2, *orig3, *orig4; + int ret; + const uint32_t remove_dupes = LDB_MSG_FIND_COMMON_REMOVE_DUPLICATES; + el = new_msg_element(*state, "test", 0, 4); + el2 = new_msg_element(*state, "test", 4, 4); + el3 = new_msg_element(*state, "test", 6, 4); + empty = new_msg_element(*state, "test", 0, 0); + orig = new_msg_element(*state, "test", 0, 4); + orig2 = new_msg_element(*state, "test", 4, 4); + orig3 = new_msg_element(*state, "test", 6, 4); + + /* first round is with short value arrays, using quadratic method */ + /* we expect no collisions here */ + ret = ldb_msg_find_common_values(NULL, *state, el, el2, 0); + assert_int_equal(ret, LDB_SUCCESS); + + /*or here */ + ret = ldb_msg_find_common_values(NULL, *state, el, el3, 0); + assert_int_equal(ret, LDB_SUCCESS); + + /* the same elements in reverse order */ + ret = ldb_msg_find_common_values(NULL, *state, el2, el, 0); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_msg_find_common_values(NULL, *state, el3, el, 0); + assert_int_equal(ret, LDB_SUCCESS); + + /* 6, 7 collide */ + ret = ldb_msg_find_common_values(NULL, *state, el2, el3, 0); + assert_int_equal(ret, LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS); + + /* and again */ + ret = ldb_msg_find_common_values(NULL, *state, el3, el2, 0); + assert_int_equal(ret, LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS); + + /* make sure the arrays haven't changed */ + assert_element_equal(el, orig); + assert_element_equal(el2, orig2); + assert_element_equal(el3, orig3); + + /* now with the control permisive flag, the first element should be + modified to remove the overlap.*/ + + /* 6, 7 collide, so el2 will only have 4 and 5 */ + ret = ldb_msg_find_common_values(NULL, *state, el2, el3, remove_dupes); + assert_int_equal(ret, LDB_SUCCESS); + + assert_element_equal(el3, orig3); + assert_int_not_equal(el2->num_values, orig2->num_values); + assert_int_equal(el2->num_values, 2); + el2b = new_msg_element(*state, "test", 4, 2); + assert_element_equal(el2, el2b); + + /* now try the same things with a long and a short value list. + this should still trigger the quadratic path. + */ + el2 = new_msg_element(*state, "test", 4, 10); + orig2 = new_msg_element(*state, "test", 4, 10); + + /* no collisions */ + ret = ldb_msg_find_common_values(NULL, *state, el, el2, 0); + assert_int_equal(ret, LDB_SUCCESS); + ret = ldb_msg_find_common_values(NULL, *state, el2, el, 0); + assert_int_equal(ret, LDB_SUCCESS); + + /*collisions */ + ret = ldb_msg_find_common_values(NULL, *state, el3, el2, 0); + assert_int_equal(ret, LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS); + + assert_element_equal(el, orig); + assert_element_equal(el2, orig2); + assert_element_equal(el3, orig3); + + /*collisions with permissive flag*/ + ret = ldb_msg_find_common_values(NULL, *state, el3, el2, remove_dupes); + assert_int_equal(ret, LDB_SUCCESS); + assert_element_equal(el2, orig2); + assert_int_equal(el3->num_values, 0); + + /* permutations involving empty elements. + everything should succeed. */ + ret = ldb_msg_find_common_values(NULL, *state, el3, el2, 0); + assert_int_equal(ret, LDB_SUCCESS); + ret = ldb_msg_find_common_values(NULL, *state, el3, el, 0); + assert_int_equal(ret, LDB_SUCCESS); + ret = ldb_msg_find_common_values(NULL, *state, el2, el3, 0); + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(el2->num_values, orig2->num_values); + ret = ldb_msg_find_common_values(NULL, *state, el3, el2, remove_dupes); + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(el2->num_values, orig2->num_values); + assert_int_equal(el3->num_values, 0); /* el3 is now empty */ + ret = ldb_msg_find_common_values(NULL, *state, el2, el3, remove_dupes); + assert_int_equal(ret, LDB_SUCCESS); + ret = ldb_msg_find_common_values(NULL, *state, el3, empty, 0); + assert_int_equal(ret, LDB_SUCCESS); + ret = ldb_msg_find_common_values(NULL, *state, empty, empty, 0); + assert_int_equal(ret, LDB_SUCCESS); + ret = ldb_msg_find_common_values(NULL, *state, empty, el3, 0); + assert_int_equal(ret, LDB_SUCCESS); + + assert_element_equal(el2, orig2); + assert_element_equal(el, orig); + assert_int_equal(el3->num_values, 0); + + /* now with two large value lists */ + el = new_msg_element(*state, "test", 0, 12); + orig = new_msg_element(*state, "test", 0, 12); + el4 = new_msg_element(*state, "test", 12, 12); + orig4 = new_msg_element(*state, "test", 12, 12); + + /* no collisions */ + ret = ldb_msg_find_common_values(NULL, *state, el, el4, 0); + assert_int_equal(ret, LDB_SUCCESS); + + ret = ldb_msg_find_common_values(NULL, *state, el4, el, 0); + assert_int_equal(ret, LDB_SUCCESS); + + /* collisions */ + ret = ldb_msg_find_common_values(NULL, *state, el4, el2, 0); + assert_int_equal(ret, LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS); + ret = ldb_msg_find_common_values(NULL, *state, el2, el4, 0); + assert_int_equal(ret, LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS); + ret = ldb_msg_find_common_values(NULL, *state, el2, el, 0); + assert_int_equal(ret, LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS); + + assert_element_equal(el, orig); + assert_element_equal(el2, orig2); + assert_element_equal(el4, orig4); + + /* with permissive control, but no collisions */ + ret = ldb_msg_find_common_values(NULL, *state, el, el4, remove_dupes); + assert_int_equal(ret, LDB_SUCCESS); + ret = ldb_msg_find_common_values(NULL, *state, el4, el, remove_dupes); + assert_int_equal(ret, LDB_SUCCESS); + + assert_element_equal(el, orig); + assert_element_equal(el4, orig4); + + /* now with collisions, thus modifications. + At this stage: + el is 0-11 (inclusive) + e2 is 4-13 + el3 is empty + el4 is 12-23 + */ + ret = ldb_msg_find_common_values(NULL, *state, el4, el2, remove_dupes); + assert_int_equal(ret, LDB_SUCCESS); + assert_element_equal(el2, orig2); + assert_int_not_equal(el4->num_values, orig4->num_values); + /* 4 should start at 14 */ + orig4 = new_msg_element(*state, "test", 14, 10); + assert_element_equal(el4, orig4); + + ret = ldb_msg_find_common_values(NULL, *state, el2, el, remove_dupes); + assert_int_equal(ret, LDB_SUCCESS); + assert_element_equal(el, orig); + assert_int_not_equal(el2->num_values, orig2->num_values); + orig2 = new_msg_element(*state, "test", 12, 2); + assert_element_equal(el2, orig2); + + /* test the empty el against the full elements */ + ret = ldb_msg_find_common_values(NULL, *state, el, empty, 0); + assert_int_equal(ret, LDB_SUCCESS); + ret = ldb_msg_find_common_values(NULL, *state, empty, el, 0); + assert_int_equal(ret, LDB_SUCCESS); + ret = ldb_msg_find_common_values(NULL, *state, el, empty, remove_dupes); + assert_int_equal(ret, LDB_SUCCESS); + ret = ldb_msg_find_common_values(NULL, *state, empty, el, remove_dupes); + assert_int_equal(ret, LDB_SUCCESS); + assert_element_equal(el, orig); + assert_element_equal(empty, el3); + + /* make sure an identical element with a different name is rejected */ + el2 = new_msg_element(*state, "fish", 12, 2); + ret = ldb_msg_find_common_values(NULL, *state, el2, el, remove_dupes); + assert_int_equal(ret, LDB_ERR_INAPPROPRIATE_MATCHING); +} + + + +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_ldb_msg_find_duplicate_val, + ldb_msg_setup, + ldb_msg_teardown), + cmocka_unit_test_setup_teardown( + test_ldb_msg_find_common_values, + ldb_msg_setup, + ldb_msg_teardown), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/ldb/tests/ldb_no_lmdb_test.c b/lib/ldb/tests/ldb_no_lmdb_test.c new file mode 100644 index 0000000..be23d74 --- /dev/null +++ b/lib/ldb/tests/ldb_no_lmdb_test.c @@ -0,0 +1,159 @@ +/* + * Ensure lmdb backend is disabled + * + * Copyright (C) Mathieu Parent 2019 + * + * 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 . + * + */ + +/* + * Ensure lmdb backend is disabled + * + * Setup and tear down code copied from ldb_lmdb_test.c + */ + +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + * + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define TEST_BE "mdb" + +struct ldbtest_ctx { + struct tevent_context *ev; + struct ldb_context *ldb; + + const char *dbfile; + const char *lockfile; /* lockfile is separate */ + + const char *dbpath; +}; + +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(); + } +} + +static int ldbtest_noconn_setup(void **state) +{ + struct ldbtest_ctx *test_ctx; + + 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->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->dbpath = talloc_asprintf(test_ctx, + TEST_BE"://%s", test_ctx->dbfile); + assert_non_null(test_ctx->dbpath); + + unlink_old_db(test_ctx); + *state = test_ctx; + return 0; +} + +static int ldbtest_noconn_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; +} + +static int ldbtest_setup(void **state) +{ + struct ldbtest_ctx *test_ctx; + int ret; + + ldbtest_noconn_setup((void **) &test_ctx); + + ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, LDB_ERR_OTHER); + + *state = test_ctx; + return 0; +} + +static int ldbtest_teardown(void **state) +{ + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + ldbtest_noconn_teardown((void **) &test_ctx); + return 0; +} + +static void test_ldb_lmdb_not_found(void **state) +{ + // Actual test in ldbtest_setup + assert_int_equal(0, 0); +} + +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + test_ldb_lmdb_not_found, + ldbtest_setup, + ldbtest_teardown), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/ldb/tests/ldb_parse_test.c b/lib/ldb/tests/ldb_parse_test.c new file mode 100644 index 0000000..b08c7b7 --- /dev/null +++ b/lib/ldb/tests/ldb_parse_test.c @@ -0,0 +1,172 @@ +/* + * Tests exercising the ldb parse operations. + * + * Copyright (C) Catalyst.NET Ltd 2017 + * Copyright (C) Michael Hanselmann 2019 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include "../include/ldb.h" + +struct test_ctx { uint8_t dummy; }; + +static int setup(void **state) +{ + struct test_ctx *ctx; + + ctx = talloc_zero(NULL, struct test_ctx); + assert_non_null(ctx); + + *state = ctx; + + return 0; +} + +static int teardown(void **state) +{ + struct test_ctx *ctx = + talloc_get_type_abort(*state, struct test_ctx); + + talloc_free(ctx); + + return 0; +} + +static void test_roundtrip(TALLOC_CTX *mem_ctx, const char *filter, const char *expected) +{ + struct ldb_parse_tree *tree; + char *serialized; + + assert_non_null(filter); + assert_non_null(expected); + + tree = ldb_parse_tree(mem_ctx, filter); + assert_non_null(tree); + + serialized = ldb_filter_from_tree(mem_ctx, tree); + assert_non_null(serialized); + + assert_string_equal(serialized, expected); +} + +static void test_parse_filtertype(void **state) +{ + struct test_ctx *ctx = + talloc_get_type_abort(*state, struct test_ctx); + + test_roundtrip(ctx, "", "(|(objectClass=*)(distinguishedName=*))"); + test_roundtrip(ctx, "a=value", "(a=value)"); + test_roundtrip(ctx, "(|(foo=bar)(baz=hello))", "(|(foo=bar)(baz=hello))"); + test_roundtrip(ctx, " ", "(|(objectClass=*)(distinguishedName=*))"); +} + +/* + * Test that a nested query with 128 levels of nesting is accepted + */ +static void test_nested_filter_eq_limit(void **state) +{ + struct test_ctx *ctx = + talloc_get_type_abort(*state, struct test_ctx); + + /* + * 128 nested clauses + */ + const char *nested_query = "" + "(|(!(|(&(|(|(|(|(|(|(|(|(|(|(|(|" + "(|(!(|(&(|(|(|(|(|(|(!(|(!(|(|(|" + "(|(!(|(&(|(|(&(|(|(|(|(|(!(!(!(|" + "(|(!(|(&(|(|(|(|(|(|(|(|(|(|(|(|" + "(|(!(|(&(|(|(|(!(|(|(&(|(|(|(|(|" + "(|(!(|(&(|(|(&(|(|(|(|(|(&(&(|(|" + "(|(!(|(&(|(|(|(|(|(|(!(|(|(|(|(|" + "(|(!(|(&(|(|(!(|(|(|(|(|(|(|(|(|" + "(a=b)" + "))))))))))))))))" + "))))))))))))))))" + "))))))))))))))))" + "))))))))))))))))" + "))))))))))))))))" + "))))))))))))))))" + "))))))))))))))))" + "))))))))))))))))"; + + struct ldb_parse_tree *tree = ldb_parse_tree(ctx, nested_query); + + assert_non_null(tree); + /* + * Check that we get the same query back + */ + test_roundtrip(ctx, nested_query, nested_query); +} + +/* + * Test that a nested query with 129 levels of nesting is rejected. + */ +static void test_nested_filter_gt_limit(void **state) +{ + struct test_ctx *ctx = + talloc_get_type_abort(*state, struct test_ctx); + + /* + * 129 nested clauses + */ + const char *nested_query = "" + "(|(!(|(|(&(|(|(|(|(&(|(|(|(|(|(|" + "(|(!(|(|(&(|(|(|(|(|(|(|(|(|(|(|" + "(|(!(|(|(&(|(|(!(|(|(|(|(!(|(|(|" + "(|(!(|(|(&(|(|(|(|(|(|(|(|(|(|(|" + "(|(!(|(|(&(|(|(|(!(&(|(|(|(|(|(|" + "(|(!(|(|(&(|(|(|(|(|(|(|(|(|(|(|" + "(|(!(|(|(&(|(|(|(|(|(|(|(|(|(|(|" + "(|(!(|(|(&(|(|(|(|(|(|(|(|(&(|(|" + "(|" + "(a=b)" + ")" + "))))))))))))))))" + "))))))))))))))))" + "))))))))))))))))" + "))))))))))))))))" + "))))))))))))))))" + "))))))))))))))))" + "))))))))))))))))" + "))))))))))))))))"; + + struct ldb_parse_tree *tree = ldb_parse_tree(ctx, nested_query); + + assert_null(tree); +} + +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + test_parse_filtertype, setup, teardown), + cmocka_unit_test_setup_teardown( + test_nested_filter_eq_limit, setup, teardown), + cmocka_unit_test_setup_teardown( + test_nested_filter_gt_limit, setup, teardown), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/ldb/tests/ldb_tdb_test.c b/lib/ldb/tests/ldb_tdb_test.c new file mode 100644 index 0000000..64e5983 --- /dev/null +++ b/lib/ldb/tests/ldb_tdb_test.c @@ -0,0 +1,389 @@ +/* + * tdb backend specific tests for ldb + * + * Copyright (C) Andrew Bartlett 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 . + * + */ + +/* + * tdb backend specific tests for ldb + * + * Setup and tear down code copied from ldb_mod_op_test.c + */ + +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + * + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../ldb_tdb/ldb_tdb.h" +#include "../ldb_key_value/ldb_kv.h" + +#define TEST_BE "tdb" + +struct ldbtest_ctx { + struct tevent_context *ev; + struct ldb_context *ldb; + + const char *dbfile; + + const char *dbpath; +}; + +static void unlink_old_db(struct ldbtest_ctx *test_ctx) +{ + int ret; + + errno = 0; + ret = unlink(test_ctx->dbfile); + if (ret == -1 && errno != ENOENT) { + fail(); + } +} + +static int ldbtest_noconn_setup(void **state) +{ + struct ldbtest_ctx *test_ctx; + + 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->dbfile = talloc_strdup(test_ctx, "apitest.ldb"); + assert_non_null(test_ctx->dbfile); + + test_ctx->dbpath = talloc_asprintf(test_ctx, + TEST_BE"://%s", test_ctx->dbfile); + assert_non_null(test_ctx->dbpath); + + unlink_old_db(test_ctx); + *state = test_ctx; + return 0; +} + +static int ldbtest_noconn_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; +} + +static int ldbtest_setup(void **state) +{ + struct ldbtest_ctx *test_ctx; + int ret; + struct ldb_ldif *ldif; + const char *index_ldif = \ + "dn: @INDEXLIST\n" + "@IDXGUID: objectUUID\n" + "@IDX_DN_GUID: GUID\n" + "\n"; + + ldbtest_noconn_setup((void **) &test_ctx); + + ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, 0); + + while ((ldif = ldb_ldif_read_string(test_ctx->ldb, &index_ldif))) { + ret = ldb_add(test_ctx->ldb, ldif->msg); + assert_int_equal(ret, LDB_SUCCESS); + } + *state = test_ctx; + return 0; +} + +static int ldbtest_teardown(void **state) +{ + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + ldbtest_noconn_teardown((void **) &test_ctx); + return 0; +} + + +static TDB_CONTEXT *get_tdb_context(struct ldb_context *ldb) +{ + void *data = NULL; + struct ldb_kv_private *ldb_kv = NULL; + TDB_CONTEXT *tdb = NULL; + + data = ldb_module_get_private(ldb->modules); + assert_non_null(data); + + ldb_kv = talloc_get_type(data, struct ldb_kv_private); + assert_non_null(ldb_kv); + + tdb = ldb_kv->tdb; + assert_non_null(tdb); + + return tdb; +} + +static void test_multiple_opens(void **state) +{ + struct ldb_context *ldb1 = NULL; + struct ldb_context *ldb2 = NULL; + struct ldb_context *ldb3 = NULL; + TDB_CONTEXT *tdb1 = NULL; + TDB_CONTEXT *tdb2 = NULL; + TDB_CONTEXT *tdb3 = NULL; + int ret; + struct ldbtest_ctx *test_ctx = NULL; + + test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx); + + /* + * Open the database again + */ + ldb1 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb1, test_ctx->dbpath, LDB_FLG_RDONLY, NULL); + assert_int_equal(ret, 0); + + ldb2 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb2, test_ctx->dbpath, LDB_FLG_RDONLY, NULL); + assert_int_equal(ret, 0); + + ldb3 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb3, test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, 0); + /* + * We now have 3 ldb's open pointing to the same on disk database + * they should all share the same MDB_env + */ + tdb1 = get_tdb_context(ldb1); + tdb2 = get_tdb_context(ldb2); + tdb3 = get_tdb_context(ldb3); + + assert_ptr_equal(tdb1, tdb2); + assert_ptr_equal(tdb1, tdb3); +} + +static void test_multiple_opens_across_fork(void **state) +{ + struct ldb_context *ldb1 = NULL; + struct ldb_context *ldb2 = NULL; + TDB_CONTEXT *tdb1 = NULL; + TDB_CONTEXT *tdb2 = NULL; + int ret; + struct ldbtest_ctx *test_ctx = NULL; + int pipes[2]; + char buf[2]; + int wstatus; + pid_t pid, child_pid; + + test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx); + + /* + * Open the database again + */ + ldb1 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb1, test_ctx->dbpath, LDB_FLG_RDONLY, NULL); + assert_int_equal(ret, 0); + + ldb2 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb2, test_ctx->dbpath, LDB_FLG_RDONLY, NULL); + assert_int_equal(ret, 0); + + tdb1 = get_tdb_context(ldb1); + tdb2 = get_tdb_context(ldb2); + + ret = pipe(pipes); + assert_int_equal(ret, 0); + + child_pid = fork(); + if (child_pid == 0) { + struct ldb_context *ldb3 = NULL; + TDB_CONTEXT *tdb3 = NULL; + + close(pipes[0]); + ldb3 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb3, test_ctx->dbpath, 0, NULL); + if (ret != 0) { + print_error(__location__": ldb_connect returned (%d)\n", + ret); + exit(ret); + } + tdb3 = get_tdb_context(ldb3); + if (tdb1 != tdb2) { + print_error(__location__": tdb1 != tdb2\n"); + exit(LDB_ERR_OPERATIONS_ERROR); + } + if (tdb1 != tdb3) { + print_error(__location__": tdb1 != tdb3\n"); + exit(LDB_ERR_OPERATIONS_ERROR); + } + ret = write(pipes[1], "GO", 2); + if (ret != 2) { + print_error(__location__ + " write returned (%d)", + ret); + exit(LDB_ERR_OPERATIONS_ERROR); + } + exit(LDB_SUCCESS); + } + close(pipes[1]); + ret = read(pipes[0], buf, 2); + assert_int_equal(ret, 2); + + pid = waitpid(child_pid, &wstatus, 0); + assert_int_equal(pid, child_pid); + + assert_true(WIFEXITED(wstatus)); + + assert_int_equal(WEXITSTATUS(wstatus), 0); +} + +static void test_multiple_opens_across_fork_triggers_reopen(void **state) +{ + struct ldb_context *ldb1 = NULL; + struct ldb_context *ldb2 = NULL; + TDB_CONTEXT *tdb1 = NULL; + TDB_CONTEXT *tdb2 = NULL; + int ret; + struct ldbtest_ctx *test_ctx = NULL; + int pipes[2]; + char buf[2]; + int wstatus; + pid_t pid, child_pid; + + test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx); + + /* + * Open the database again + */ + ldb1 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb1, test_ctx->dbpath, LDB_FLG_RDONLY, NULL); + assert_int_equal(ret, 0); + + ldb2 = ldb_init(test_ctx, test_ctx->ev); + ret = ldb_connect(ldb2, test_ctx->dbpath, LDB_FLG_RDONLY, NULL); + assert_int_equal(ret, 0); + + tdb1 = get_tdb_context(ldb1); + tdb2 = get_tdb_context(ldb2); + assert_ptr_equal(tdb1, tdb2); + + /* + * Break the internal tdb_reopen() by making a + * transaction + * + * This shows that the tdb_reopen() is called, which is + * essential if the host OS does not have pread() + */ + ret = tdb_transaction_start(tdb1); + assert_int_equal(ret, 0); + + ret = pipe(pipes); + assert_int_equal(ret, 0); + + child_pid = fork(); + if (child_pid == 0) { + struct ldb_context *ldb3 = NULL; + + close(pipes[0]); + ldb3 = ldb_init(test_ctx, test_ctx->ev); + + /* + * This should fail as we have taken out a lock + * against the raw TDB above, and tdb_reopen() + * will fail in that state. + * + * This check matters as tdb_reopen() is important + * if the host does not have pread() + */ + ret = ldb_connect(ldb3, test_ctx->dbpath, 0, NULL); + if (ret == 0) { + print_error(__location__": ldb_connect expected " + "LDB_ERR_OPERATIONS_ERROR " + "returned (%d)\n", + ret); + exit(5000); + } + ret = write(pipes[1], "GO", 2); + if (ret != 2) { + print_error(__location__ + " write returned (%d)", + ret); + exit(LDB_ERR_OPERATIONS_ERROR); + } + exit(LDB_SUCCESS); + } + close(pipes[1]); + ret = read(pipes[0], buf, 2); + assert_int_equal(ret, 2); + + pid = waitpid(child_pid, &wstatus, 0); + assert_int_equal(pid, child_pid); + + assert_true(WIFEXITED(wstatus)); + + assert_int_equal(WEXITSTATUS(wstatus), 0); +} + +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + test_multiple_opens, + ldbtest_setup, + ldbtest_teardown), + cmocka_unit_test_setup_teardown( + test_multiple_opens_across_fork, + ldbtest_setup, + ldbtest_teardown), + cmocka_unit_test_setup_teardown( + test_multiple_opens_across_fork_triggers_reopen, + ldbtest_setup, + ldbtest_teardown), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/ldb/tests/lldb_ldap.c b/lib/ldb/tests/lldb_ldap.c new file mode 100644 index 0000000..eea9f22 --- /dev/null +++ b/lib/ldb/tests/lldb_ldap.c @@ -0,0 +1,105 @@ +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + */ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +int ldb_ldap_init(const char *version); + +#include "ldb_ldap/ldb_ldap.c" + +struct test_ctx { + struct tevent_context *ev; + struct ldb_context *ldb; + struct ldb_message *msg; +}; + +static int lldb_msg_setup(void **state) +{ + struct test_ctx *test_ctx; + + test_ctx = talloc_zero(NULL, struct test_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->msg = ldb_msg_new(test_ctx); + assert_non_null(test_ctx->msg); + + *state = test_ctx; + return 0; +} + +static int lldb_msg_teardown(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + + talloc_free(test_ctx); + return 0; +} + +static void test_lldb_add_msg_attr(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + struct ldb_message *msg = test_ctx->msg; + int ret; + unsigned int num_elements = 0; + struct berval **v = NULL; + + v = talloc_zero_array(test_ctx, struct berval *, 2); + assert_non_null(v); + + v[0] = talloc_zero(v, struct berval); + assert_non_null(v[0]); + + v[0]->bv_val = talloc_strdup(msg, "dc=example,dc=test"); + assert_non_null(v[0]->bv_val); + + v[0]->bv_len = strlen(v[0]->bv_val); + + num_elements = msg->num_elements; + + ret = lldb_add_msg_attr(test_ctx->ldb, msg, "defaultNamingContext", v); + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(msg->num_elements, num_elements + 1); +} + + +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_lldb_add_msg_attr, + lldb_msg_setup, + lldb_msg_teardown), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/ldb/tests/photo.ldif b/lib/ldb/tests/photo.ldif new file mode 100644 index 0000000..95ab065 --- /dev/null +++ b/lib/ldb/tests/photo.ldif @@ -0,0 +1,5 @@ +dn: cn=Hampster Ursula,ou=Alumni Association,ou=People,o=University of Michigan,c=TEST +changetype: modify +add: jpegPhoto +jpegPhoto:< file://tests/samba4.png + diff --git a/lib/ldb/tests/python/api.py b/lib/ldb/tests/python/api.py new file mode 100755 index 0000000..043b721 --- /dev/null +++ b/lib/ldb/tests/python/api.py @@ -0,0 +1,3858 @@ +#!/usr/bin/env python3 +# Simple tests for the ldb python bindings. +# Copyright (C) 2007 Jelmer Vernooij + +import os +from unittest import TestCase +import sys +sys.path.insert(0, "bin/python") +import gc +import time +import ldb +import shutil +import errno + + +TDB_PREFIX = "tdb://" +MDB_PREFIX = "mdb://" + +MDB_INDEX_OBJ = { + "dn": "@INDEXLIST", + "@IDXONE": [b"1"], + "@IDXGUID": [b"objectUUID"], + "@IDX_DN_GUID": [b"GUID"] +} + + +def tempdir(): + import tempfile + try: + dir_prefix = os.path.join(os.environ["SELFTEST_PREFIX"], "tmp") + except KeyError: + dir_prefix = None + return tempfile.mkdtemp(dir=dir_prefix) + + +class NoContextTests(TestCase): + + def test_valid_attr_name(self): + self.assertTrue(ldb.valid_attr_name("foo")) + self.assertFalse(ldb.valid_attr_name("24foo")) + + def test_timestring(self): + self.assertEqual("19700101000000.0Z", ldb.timestring(0)) + self.assertEqual("20071119191012.0Z", ldb.timestring(1195499412)) + + self.assertEqual("00000101000000.0Z", ldb.timestring(-62167219200)) + self.assertEqual("99991231235959.0Z", ldb.timestring(253402300799)) + + # should result with OSError EOVERFLOW from gmtime() + with self.assertRaises(OSError) as err: + ldb.timestring(-62167219201) + self.assertEqual(err.exception.errno, errno.EOVERFLOW) + with self.assertRaises(OSError) as err: + ldb.timestring(253402300800) + self.assertEqual(err.exception.errno, errno.EOVERFLOW) + with self.assertRaises(OSError) as err: + ldb.timestring(0x7fffffffffffffff) + self.assertEqual(err.exception.errno, errno.EOVERFLOW) + + def test_string_to_time(self): + self.assertEqual(0, ldb.string_to_time("19700101000000.0Z")) + self.assertEqual(1195499412, ldb.string_to_time("20071119191012.0Z")) + + self.assertEqual(-62167219200, ldb.string_to_time("00000101000000.0Z")) + self.assertEqual(253402300799, ldb.string_to_time("99991231235959.0Z")) + + def test_binary_encode(self): + encoded = ldb.binary_encode(b'test\\x') + decoded = ldb.binary_decode(encoded) + self.assertEqual(decoded, b'test\\x') + + encoded2 = ldb.binary_encode('test\\x') + self.assertEqual(encoded2, encoded) + + +class LdbBaseTest(TestCase): + def setUp(self): + super(LdbBaseTest, self).setUp() + try: + if self.prefix is None: + self.prefix = TDB_PREFIX + except AttributeError: + self.prefix = TDB_PREFIX + + def tearDown(self): + super(LdbBaseTest, self).tearDown() + + def url(self): + return self.prefix + self.filename + + def flags(self): + if self.prefix == MDB_PREFIX: + return ldb.FLG_NOSYNC + else: + return 0 + + +class SimpleLdb(LdbBaseTest): + + def setUp(self): + super(SimpleLdb, self).setUp() + self.testdir = tempdir() + self.filename = os.path.join(self.testdir, "test.ldb") + self.ldb = ldb.Ldb(self.url(), flags=self.flags()) + try: + self.ldb.add(self.index) + except AttributeError: + pass + + def tearDown(self): + shutil.rmtree(self.testdir) + super(SimpleLdb, self).tearDown() + # Ensure the LDB is closed now, so we close the FD + del(self.ldb) + + def test_connect(self): + ldb.Ldb(self.url(), flags=self.flags()) + + def test_connect_none(self): + ldb.Ldb() + + def test_connect_later(self): + x = ldb.Ldb() + x.connect(self.url(), flags=self.flags()) + + def test_repr(self): + x = ldb.Ldb() + self.assertTrue(repr(x).startswith("]", repr(x.modules())) + + def test_firstmodule_none(self): + x = ldb.Ldb() + self.assertEqual(x.firstmodule, None) + + def test_firstmodule_tdb(self): + x = ldb.Ldb(self.url(), flags=self.flags()) + mod = x.firstmodule + self.assertEqual(repr(mod), "") + + def test_search(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + self.assertEqual(len(l.search()), 0) + + def test_search_controls(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + self.assertEqual(len(l.search(controls=["paged_results:0:5"])), 0) + + def test_utf8_ldb_Dn(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + dn = ldb.Dn(l, (b'a=' + b'\xc4\x85\xc4\x87\xc4\x99\xc5\x82\xc5\x84\xc3\xb3\xc5\x9b\xc5\xba\xc5\xbc').decode('utf8')) + + def test_utf8_encoded_ldb_Dn(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + dn_encoded_utf8 = b'a=' + b'\xc4\x85\xc4\x87\xc4\x99\xc5\x82\xc5\x84\xc3\xb3\xc5\x9b\xc5\xba\xc5\xbc' + try: + dn = ldb.Dn(l, dn_encoded_utf8) + except UnicodeDecodeError as e: + raise + except TypeError as te: + p3errors = ["argument 2 must be str, not bytes", + "Can't convert 'bytes' object to str implicitly"] + self.assertIn(str(te), p3errors) + + def test_search_attrs(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + self.assertEqual(len(l.search(ldb.Dn(l, ""), ldb.SCOPE_SUBTREE, "(dc=*)", ["dc"])), 0) + + def test_search_string_dn(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + self.assertEqual(len(l.search("", ldb.SCOPE_SUBTREE, "(dc=*)", ["dc"])), 0) + + def test_search_attr_string(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + self.assertRaises(TypeError, l.search, attrs="dc") + self.assertRaises(TypeError, l.search, attrs=b"dc") + + def test_opaque(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + l.set_opaque("my_opaque", l) + self.assertTrue(l.get_opaque("my_opaque") is not None) + self.assertEqual(None, l.get_opaque("unknown")) + + def test_search_scope_base_empty_db(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + self.assertEqual(len(l.search(ldb.Dn(l, "dc=foo1"), + ldb.SCOPE_BASE)), 0) + + def test_search_scope_onelevel_empty_db(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + self.assertEqual(len(l.search(ldb.Dn(l, "dc=foo1"), + ldb.SCOPE_ONELEVEL)), 0) + + def test_delete(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + self.assertRaises(ldb.LdbError, lambda: l.delete(ldb.Dn(l, "dc=foo2"))) + + def test_delete_w_unhandled_ctrl(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=foo1") + m["b"] = [b"a"] + m["objectUUID"] = b"0123456789abcdef" + l.add(m) + self.assertRaises(ldb.LdbError, lambda: l.delete(m.dn, ["search_options:1:2"])) + l.delete(m.dn) + + def test_contains(self): + name = self.url() + l = ldb.Ldb(name, flags=self.flags()) + self.assertFalse(ldb.Dn(l, "dc=foo3") in l) + l = ldb.Ldb(name, flags=self.flags()) + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=foo3") + m["b"] = ["a"] + m["objectUUID"] = b"0123456789abcdef" + l.add(m) + try: + self.assertTrue(ldb.Dn(l, "dc=foo3") in l) + self.assertFalse(ldb.Dn(l, "dc=foo4") in l) + finally: + l.delete(m.dn) + + def test_get_config_basedn(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + self.assertEqual(None, l.get_config_basedn()) + + def test_get_root_basedn(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + self.assertEqual(None, l.get_root_basedn()) + + def test_get_schema_basedn(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + self.assertEqual(None, l.get_schema_basedn()) + + def test_get_default_basedn(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + self.assertEqual(None, l.get_default_basedn()) + + def test_add(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=foo4") + m["bla"] = b"bla" + m["objectUUID"] = b"0123456789abcdef" + self.assertEqual(len(l.search()), 0) + l.add(m) + try: + self.assertEqual(len(l.search()), 1) + finally: + l.delete(ldb.Dn(l, "dc=foo4")) + + def test_search_iterator(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + s = l.search_iterator() + s.abandon() + try: + for me in s: + self.fail() + self.fail() + except RuntimeError as re: + pass + try: + s.abandon() + self.fail() + except RuntimeError as re: + pass + try: + s.result() + self.fail() + except RuntimeError as re: + pass + + s = l.search_iterator() + count = 0 + for me in s: + self.assertTrue(isinstance(me, ldb.Message)) + count += 1 + r = s.result() + self.assertEqual(len(r), 0) + self.assertEqual(count, 0) + + m1 = ldb.Message() + m1.dn = ldb.Dn(l, "dc=foo4") + m1["bla"] = b"bla" + m1["objectUUID"] = b"0123456789abcdef" + l.add(m1) + try: + s = l.search_iterator() + msgs = [] + for me in s: + self.assertTrue(isinstance(me, ldb.Message)) + count += 1 + msgs.append(me) + r = s.result() + self.assertEqual(len(r), 0) + self.assertEqual(len(msgs), 1) + self.assertEqual(msgs[0].dn, m1.dn) + + m2 = ldb.Message() + m2.dn = ldb.Dn(l, "dc=foo5") + m2["bla"] = b"bla" + m2["objectUUID"] = b"0123456789abcdee" + l.add(m2) + + s = l.search_iterator() + msgs = [] + for me in s: + self.assertTrue(isinstance(me, ldb.Message)) + count += 1 + msgs.append(me) + r = s.result() + self.assertEqual(len(r), 0) + self.assertEqual(len(msgs), 2) + if msgs[0].dn == m1.dn: + self.assertEqual(msgs[0].dn, m1.dn) + self.assertEqual(msgs[1].dn, m2.dn) + else: + self.assertEqual(msgs[0].dn, m2.dn) + self.assertEqual(msgs[1].dn, m1.dn) + + s = l.search_iterator() + msgs = [] + for me in s: + self.assertTrue(isinstance(me, ldb.Message)) + count += 1 + msgs.append(me) + break + try: + s.result() + self.fail() + except RuntimeError as re: + pass + for me in s: + self.assertTrue(isinstance(me, ldb.Message)) + count += 1 + msgs.append(me) + break + for me in s: + self.fail() + + r = s.result() + self.assertEqual(len(r), 0) + self.assertEqual(len(msgs), 2) + if msgs[0].dn == m1.dn: + self.assertEqual(msgs[0].dn, m1.dn) + self.assertEqual(msgs[1].dn, m2.dn) + else: + self.assertEqual(msgs[0].dn, m2.dn) + self.assertEqual(msgs[1].dn, m1.dn) + finally: + l.delete(ldb.Dn(l, "dc=foo4")) + l.delete(ldb.Dn(l, "dc=foo5")) + + def test_add_text(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=foo4") + m["bla"] = "bla" + m["objectUUID"] = b"0123456789abcdef" + self.assertEqual(len(l.search()), 0) + l.add(m) + try: + self.assertEqual(len(l.search()), 1) + finally: + l.delete(ldb.Dn(l, "dc=foo4")) + + def test_add_w_unhandled_ctrl(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=foo4") + m["bla"] = b"bla" + self.assertEqual(len(l.search()), 0) + self.assertRaises(ldb.LdbError, lambda: l.add(m, ["search_options:1:2"])) + + def test_add_dict(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + m = {"dn": ldb.Dn(l, "dc=foo5"), + "bla": b"bla", + "objectUUID": b"0123456789abcdef"} + self.assertEqual(len(l.search()), 0) + l.add(m) + try: + self.assertEqual(len(l.search()), 1) + finally: + l.delete(ldb.Dn(l, "dc=foo5")) + + def test_add_dict_text(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + m = {"dn": ldb.Dn(l, "dc=foo5"), + "bla": "bla", + "objectUUID": b"0123456789abcdef"} + self.assertEqual(len(l.search()), 0) + l.add(m) + try: + self.assertEqual(len(l.search()), 1) + finally: + l.delete(ldb.Dn(l, "dc=foo5")) + + def test_add_dict_string_dn(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + m = {"dn": "dc=foo6", "bla": b"bla", + "objectUUID": b"0123456789abcdef"} + self.assertEqual(len(l.search()), 0) + l.add(m) + try: + self.assertEqual(len(l.search()), 1) + finally: + l.delete(ldb.Dn(l, "dc=foo6")) + + def test_add_dict_bytes_dn(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + m = {"dn": b"dc=foo6", "bla": b"bla", + "objectUUID": b"0123456789abcdef"} + self.assertEqual(len(l.search()), 0) + l.add(m) + try: + self.assertEqual(len(l.search()), 1) + finally: + l.delete(ldb.Dn(l, "dc=foo6")) + + def test_rename(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=foo7") + m["bla"] = b"bla" + m["objectUUID"] = b"0123456789abcdef" + self.assertEqual(len(l.search()), 0) + l.add(m) + try: + l.rename(ldb.Dn(l, "dc=foo7"), ldb.Dn(l, "dc=bar")) + self.assertEqual(len(l.search()), 1) + finally: + l.delete(ldb.Dn(l, "dc=bar")) + + def test_rename_string_dns(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=foo8") + m["bla"] = b"bla" + m["objectUUID"] = b"0123456789abcdef" + self.assertEqual(len(l.search()), 0) + l.add(m) + self.assertEqual(len(l.search()), 1) + try: + l.rename("dc=foo8", "dc=bar") + self.assertEqual(len(l.search()), 1) + finally: + l.delete(ldb.Dn(l, "dc=bar")) + + def test_rename_bad_string_dns(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=foo8") + m["bla"] = b"bla" + m["objectUUID"] = b"0123456789abcdef" + self.assertEqual(len(l.search()), 0) + l.add(m) + self.assertEqual(len(l.search()), 1) + self.assertRaises(ldb.LdbError,lambda: l.rename("dcXfoo8", "dc=bar")) + self.assertRaises(ldb.LdbError,lambda: l.rename("dc=foo8", "dcXbar")) + l.delete(ldb.Dn(l, "dc=foo8")) + + def test_empty_dn(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + self.assertEqual(0, len(l.search())) + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=empty") + m["objectUUID"] = b"0123456789abcdef" + l.add(m) + rm = l.search() + self.assertEqual(1, len(rm)) + self.assertEqual(set(["dn", "distinguishedName", "objectUUID"]), + set(rm[0].keys())) + + rm = l.search(m.dn) + self.assertEqual(1, len(rm)) + self.assertEqual(set(["dn", "distinguishedName", "objectUUID"]), + set(rm[0].keys())) + rm = l.search(m.dn, attrs=["blah"]) + self.assertEqual(1, len(rm)) + self.assertEqual(0, len(rm[0])) + + def test_modify_delete(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=modifydelete") + m["bla"] = [b"1234"] + m["objectUUID"] = b"0123456789abcdef" + l.add(m) + rm = l.search(m.dn)[0] + self.assertEqual([b"1234"], list(rm["bla"])) + try: + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=modifydelete") + m["bla"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "bla") + self.assertEqual(ldb.FLAG_MOD_DELETE, m["bla"].flags()) + l.modify(m) + rm = l.search(m.dn) + self.assertEqual(1, len(rm)) + self.assertEqual(set(["dn", "distinguishedName", "objectUUID"]), + set(rm[0].keys())) + rm = l.search(m.dn, attrs=["bla"]) + self.assertEqual(1, len(rm)) + self.assertEqual(0, len(rm[0])) + finally: + l.delete(ldb.Dn(l, "dc=modifydelete")) + + def test_modify_delete_text(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=modifydelete") + m.text["bla"] = ["1234"] + m["objectUUID"] = b"0123456789abcdef" + l.add(m) + rm = l.search(m.dn)[0] + self.assertEqual(["1234"], list(rm.text["bla"])) + try: + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=modifydelete") + m["bla"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "bla") + self.assertEqual(ldb.FLAG_MOD_DELETE, m["bla"].flags()) + l.modify(m) + rm = l.search(m.dn) + self.assertEqual(1, len(rm)) + self.assertEqual(set(["dn", "distinguishedName", "objectUUID"]), + set(rm[0].keys())) + rm = l.search(m.dn, attrs=["bla"]) + self.assertEqual(1, len(rm)) + self.assertEqual(0, len(rm[0])) + finally: + l.delete(ldb.Dn(l, "dc=modifydelete")) + + def test_modify_add(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=add") + m["bla"] = [b"1234"] + m["objectUUID"] = b"0123456789abcdef" + l.add(m) + try: + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=add") + m["bla"] = ldb.MessageElement([b"456"], ldb.FLAG_MOD_ADD, "bla") + self.assertEqual(ldb.FLAG_MOD_ADD, m["bla"].flags()) + l.modify(m) + rm = l.search(m.dn)[0] + self.assertEqual(3, len(rm)) + self.assertEqual([b"1234", b"456"], list(rm["bla"])) + finally: + l.delete(ldb.Dn(l, "dc=add")) + + def test_modify_add_text(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=add") + m.text["bla"] = ["1234"] + m["objectUUID"] = b"0123456789abcdef" + l.add(m) + try: + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=add") + m["bla"] = ldb.MessageElement(["456"], ldb.FLAG_MOD_ADD, "bla") + self.assertEqual(ldb.FLAG_MOD_ADD, m["bla"].flags()) + l.modify(m) + rm = l.search(m.dn)[0] + self.assertEqual(3, len(rm)) + self.assertEqual(["1234", "456"], list(rm.text["bla"])) + finally: + l.delete(ldb.Dn(l, "dc=add")) + + def test_modify_replace(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=modify2") + m["bla"] = [b"1234", b"456"] + m["objectUUID"] = b"0123456789abcdef" + l.add(m) + try: + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=modify2") + m["bla"] = ldb.MessageElement([b"789"], ldb.FLAG_MOD_REPLACE, "bla") + self.assertEqual(ldb.FLAG_MOD_REPLACE, m["bla"].flags()) + l.modify(m) + rm = l.search(m.dn)[0] + self.assertEqual(3, len(rm)) + self.assertEqual([b"789"], list(rm["bla"])) + rm = l.search(m.dn, attrs=["bla"])[0] + self.assertEqual(1, len(rm)) + finally: + l.delete(ldb.Dn(l, "dc=modify2")) + + def test_modify_replace_text(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=modify2") + m.text["bla"] = ["1234", "456"] + m["objectUUID"] = b"0123456789abcdef" + l.add(m) + try: + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=modify2") + m["bla"] = ldb.MessageElement(["789"], ldb.FLAG_MOD_REPLACE, "bla") + self.assertEqual(ldb.FLAG_MOD_REPLACE, m["bla"].flags()) + l.modify(m) + rm = l.search(m.dn)[0] + self.assertEqual(3, len(rm)) + self.assertEqual(["789"], list(rm.text["bla"])) + rm = l.search(m.dn, attrs=["bla"])[0] + self.assertEqual(1, len(rm)) + finally: + l.delete(ldb.Dn(l, "dc=modify2")) + + def test_modify_flags_change(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=add") + m["bla"] = [b"1234"] + m["objectUUID"] = b"0123456789abcdef" + l.add(m) + try: + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=add") + m["bla"] = ldb.MessageElement([b"456"], ldb.FLAG_MOD_ADD, "bla") + self.assertEqual(ldb.FLAG_MOD_ADD, m["bla"].flags()) + l.modify(m) + rm = l.search(m.dn)[0] + self.assertEqual(3, len(rm)) + self.assertEqual([b"1234", b"456"], list(rm["bla"])) + + # Now create another modify, but switch the flags before we do it + m["bla"] = ldb.MessageElement([b"456"], ldb.FLAG_MOD_ADD, "bla") + m["bla"].set_flags(ldb.FLAG_MOD_DELETE) + l.modify(m) + rm = l.search(m.dn, attrs=["bla"])[0] + self.assertEqual(1, len(rm)) + self.assertEqual([b"1234"], list(rm["bla"])) + finally: + l.delete(ldb.Dn(l, "dc=add")) + + def test_modify_flags_change_text(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=add") + m.text["bla"] = ["1234"] + m["objectUUID"] = b"0123456789abcdef" + l.add(m) + try: + m = ldb.Message() + m.dn = ldb.Dn(l, "dc=add") + m["bla"] = ldb.MessageElement(["456"], ldb.FLAG_MOD_ADD, "bla") + self.assertEqual(ldb.FLAG_MOD_ADD, m["bla"].flags()) + l.modify(m) + rm = l.search(m.dn)[0] + self.assertEqual(3, len(rm)) + self.assertEqual(["1234", "456"], list(rm.text["bla"])) + + # Now create another modify, but switch the flags before we do it + m["bla"] = ldb.MessageElement(["456"], ldb.FLAG_MOD_ADD, "bla") + m["bla"].set_flags(ldb.FLAG_MOD_DELETE) + l.modify(m) + rm = l.search(m.dn, attrs=["bla"])[0] + self.assertEqual(1, len(rm)) + self.assertEqual(["1234"], list(rm.text["bla"])) + finally: + l.delete(ldb.Dn(l, "dc=add")) + + def test_transaction_commit(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + l.transaction_start() + m = ldb.Message(ldb.Dn(l, "dc=foo9")) + m["foo"] = [b"bar"] + m["objectUUID"] = b"0123456789abcdef" + l.add(m) + l.transaction_commit() + l.delete(m.dn) + + def test_transaction_cancel(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + l.transaction_start() + m = ldb.Message(ldb.Dn(l, "dc=foo10")) + m["foo"] = [b"bar"] + m["objectUUID"] = b"0123456789abcdee" + l.add(m) + l.transaction_cancel() + self.assertEqual(0, len(l.search(ldb.Dn(l, "dc=foo10")))) + + def test_set_debug(self): + def my_report_fn(level, text): + pass + l = ldb.Ldb(self.url(), flags=self.flags()) + l.set_debug(my_report_fn) + + def test_zero_byte_string(self): + """Testing we do not get trapped in the \0 byte in a property string.""" + l = ldb.Ldb(self.url(), flags=self.flags()) + l.add({ + "dn": b"dc=somedn", + "objectclass": b"user", + "cN": b"LDAPtestUSER", + "givenname": b"ldap", + "displayname": b"foo\0bar", + "objectUUID": b"0123456789abcdef" + }) + res = l.search(expression="(dn=dc=somedn)") + self.assertEqual(b"foo\0bar", res[0]["displayname"][0]) + + def test_no_crash_broken_expr(self): + l = ldb.Ldb(self.url(), flags=self.flags()) + self.assertRaises(ldb.LdbError, lambda: l.search("", ldb.SCOPE_SUBTREE, "&(dc=*)(dn=*)", ["dc"])) + +# Run the SimpleLdb tests against an lmdb backend + + +class SimpleLdbLmdb(SimpleLdb): + + def setUp(self): + if os.environ.get('HAVE_LMDB', '1') == '0': + self.skipTest("No lmdb backend") + self.prefix = MDB_PREFIX + self.index = MDB_INDEX_OBJ + super(SimpleLdbLmdb, self).setUp() + + def tearDown(self): + super(SimpleLdbLmdb, self).tearDown() + + +class SimpleLdbNoLmdb(LdbBaseTest): + + def setUp(self): + if os.environ.get('HAVE_LMDB', '1') != '0': + self.skipTest("lmdb backend enabled") + self.prefix = MDB_PREFIX + self.index = MDB_INDEX_OBJ + super(SimpleLdbNoLmdb, self).setUp() + + def tearDown(self): + super(SimpleLdbNoLmdb, self).tearDown() + + def test_lmdb_disabled(self): + self.testdir = tempdir() + self.filename = os.path.join(self.testdir, "test.ldb") + try: + self.ldb = ldb.Ldb(self.url(), flags=self.flags()) + self.fail("Should have failed on missing LMDB") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_OTHER) + + +class SearchTests(LdbBaseTest): + def tearDown(self): + shutil.rmtree(self.testdir) + super(SearchTests, self).tearDown() + + # Ensure the LDB is closed now, so we close the FD + del(self.l) + + def setUp(self): + super(SearchTests, self).setUp() + self.testdir = tempdir() + self.filename = os.path.join(self.testdir, "search_test.ldb") + options = ["modules:rdn_name"] + if hasattr(self, 'IDXCHECK'): + options.append("disable_full_db_scan_for_self_test:1") + self.l = ldb.Ldb(self.url(), + flags=self.flags(), + options=options) + try: + self.l.add(self.index) + except AttributeError: + pass + + self.l.add({"dn": "@ATTRIBUTES", + "DC": "CASE_INSENSITIVE"}) + + # Note that we can't use the name objectGUID here, as we + # want to stay clear of the objectGUID handler in LDB and + # instead use just the 16 bytes raw, which we just keep + # to printable chars here for ease of handling. + + self.l.add({"dn": "DC=ORG", + "name": b"org", + "objectUUID": b"0000000000abcdef"}) + self.l.add({"dn": "DC=EXAMPLE,DC=ORG", + "name": b"org", + "objectUUID": b"0000000001abcdef"}) + self.l.add({"dn": "OU=OU1,DC=EXAMPLE,DC=ORG", + "name": b"OU #1", + "x": "y", "y": "a", + "objectUUID": b"0023456789abcde3"}) + self.l.add({"dn": "OU=OU2,DC=EXAMPLE,DC=ORG", + "name": b"OU #2", + "x": "y", "y": "a", + "objectUUID": b"0023456789abcde4"}) + self.l.add({"dn": "OU=OU3,DC=EXAMPLE,DC=ORG", + "name": b"OU #3", + "x": "y", "y": "a", + "objectUUID": b"0023456789abcde5"}) + self.l.add({"dn": "OU=OU4,DC=EXAMPLE,DC=ORG", + "name": b"OU #4", + "x": "z", "y": "b", + "objectUUID": b"0023456789abcde6"}) + self.l.add({"dn": "OU=OU5,DC=EXAMPLE,DC=ORG", + "name": b"OU #5", + "x": "y", "y": "a", + "objectUUID": b"0023456789abcde7"}) + self.l.add({"dn": "OU=OU6,DC=EXAMPLE,DC=ORG", + "name": b"OU #6", + "x": "y", "y": "a", + "objectUUID": b"0023456789abcde8"}) + self.l.add({"dn": "OU=OU7,DC=EXAMPLE,DC=ORG", + "name": b"OU #7", + "x": "y", "y": "c", + "objectUUID": b"0023456789abcde9"}) + self.l.add({"dn": "OU=OU8,DC=EXAMPLE,DC=ORG", + "name": b"OU #8", + "x": "y", "y": "b", + "objectUUID": b"0023456789abcde0"}) + self.l.add({"dn": "OU=OU9,DC=EXAMPLE,DC=ORG", + "name": b"OU #9", + "x": "y", "y": "a", + "objectUUID": b"0023456789abcdea"}) + + self.l.add({"dn": "DC=EXAMPLE,DC=COM", + "name": b"org", + "objectUUID": b"0000000011abcdef"}) + + self.l.add({"dn": "DC=EXAMPLE,DC=NET", + "name": b"org", + "objectUUID": b"0000000021abcdef"}) + + self.l.add({"dn": "OU=UNIQUE,DC=EXAMPLE,DC=NET", + "objectUUID": b"0000000022abcdef"}) + + self.l.add({"dn": "DC=SAMBA,DC=ORG", + "name": b"samba.org", + "objectUUID": b"0123456789abcdef"}) + self.l.add({"dn": "OU=ADMIN,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde1"}) + self.l.add({"dn": "OU=USERS,DC=SAMBA,DC=ORG", + "name": b"Users", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde2"}) + self.l.add({"dn": "OU=OU1,DC=SAMBA,DC=ORG", + "name": b"OU #1", + "x": "y", "y": "a", + "objectUUID": b"0123456789abcde3"}) + self.l.add({"dn": "OU=OU2,DC=SAMBA,DC=ORG", + "name": b"OU #2", + "x": "y", "y": "a", + "objectUUID": b"0123456789abcde4"}) + self.l.add({"dn": "OU=OU3,DC=SAMBA,DC=ORG", + "name": b"OU #3", + "x": "y", "y": "a", + "objectUUID": b"0123456789abcde5"}) + self.l.add({"dn": "OU=OU4,DC=SAMBA,DC=ORG", + "name": b"OU #4", + "x": "y", "y": "a", + "objectUUID": b"0123456789abcde6"}) + self.l.add({"dn": "OU=OU5,DC=SAMBA,DC=ORG", + "name": b"OU #5", + "x": "y", "y": "a", + "objectUUID": b"0123456789abcde7"}) + self.l.add({"dn": "OU=OU6,DC=SAMBA,DC=ORG", + "name": b"OU #6", + "x": "y", "y": "a", + "objectUUID": b"0123456789abcde8"}) + self.l.add({"dn": "OU=OU7,DC=SAMBA,DC=ORG", + "name": b"OU #7", + "x": "y", "y": "a", + "objectUUID": b"0123456789abcde9"}) + self.l.add({"dn": "OU=OU8,DC=SAMBA,DC=ORG", + "name": b"OU #8", + "x": "y", "y": "a", + "objectUUID": b"0123456789abcde0"}) + self.l.add({"dn": "OU=OU9,DC=SAMBA,DC=ORG", + "name": b"OU #9", + "x": "y", "y": "a", + "objectUUID": b"0123456789abcdea"}) + self.l.add({"dn": "OU=OU10,DC=SAMBA,DC=ORG", + "name": b"OU #10", + "x": "y", "y": "a", + "objectUUID": b"0123456789abcdeb"}) + self.l.add({"dn": "OU=OU11,DC=SAMBA,DC=ORG", + "name": b"OU #10", + "x": "y", "y": "a", + "objectUUID": b"0123456789abcdec"}) + self.l.add({"dn": "OU=OU12,DC=SAMBA,DC=ORG", + "name": b"OU #10", + "x": "y", "y": "b", + "objectUUID": b"0123456789abcded"}) + self.l.add({"dn": "OU=OU13,DC=SAMBA,DC=ORG", + "name": b"OU #10", + "x": "x", "y": "b", + "objectUUID": b"0123456789abcdee"}) + self.l.add({"dn": "OU=OU14,DC=SAMBA,DC=ORG", + "name": b"OU #10", + "x": "x", "y": "b", + "objectUUID": b"0123456789abcd01"}) + self.l.add({"dn": "OU=OU15,DC=SAMBA,DC=ORG", + "name": b"OU #10", + "x": "x", "y": "b", + "objectUUID": b"0123456789abcd02"}) + self.l.add({"dn": "OU=OU16,DC=SAMBA,DC=ORG", + "name": b"OU #10", + "x": "x", "y": "b", + "objectUUID": b"0123456789abcd03"}) + self.l.add({"dn": "OU=OU17,DC=SAMBA,DC=ORG", + "name": b"OU #10", + "x": "x", "y": "b", + "objectUUID": b"0123456789abcd04"}) + self.l.add({"dn": "OU=OU18,DC=SAMBA,DC=ORG", + "name": b"OU #10", + "x": "x", "y": "b", + "objectUUID": b"0123456789abcd05"}) + self.l.add({"dn": "OU=OU19,DC=SAMBA,DC=ORG", + "name": b"OU #10", + "x": "x", "y": "b", + "objectUUID": b"0123456789abcd06"}) + self.l.add({"dn": "OU=OU20,DC=SAMBA,DC=ORG", + "name": b"OU #10", + "x": "x", "y": "b", + "objectUUID": b"0123456789abcd07"}) + self.l.add({"dn": "OU=OU21,DC=SAMBA,DC=ORG", + "name": b"OU #10", + "x": "x", "y": "c", + "objectUUID": b"0123456789abcd08"}) + self.l.add({"dn": "OU=OU22,DC=SAMBA,DC=ORG", + "name": b"OU #10", + "x": "x", "y": "c", + "objectUUID": b"0123456789abcd09"}) + + def test_base(self): + """Testing a search""" + + res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_BASE) + self.assertEqual(len(res11), 1) + + def test_base_lower(self): + """Testing a search""" + + res11 = self.l.search(base="OU=OU11,DC=samba,DC=org", + scope=ldb.SCOPE_BASE) + self.assertEqual(len(res11), 1) + + def test_base_or(self): + """Testing a search""" + + res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_BASE, + expression="(|(ou=ou11)(ou=ou12))") + self.assertEqual(len(res11), 1) + + def test_base_or2(self): + """Testing a search""" + + res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_BASE, + expression="(|(x=y)(y=b))") + self.assertEqual(len(res11), 1) + + def test_base_and(self): + """Testing a search""" + + res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_BASE, + expression="(&(ou=ou11)(ou=ou12))") + self.assertEqual(len(res11), 0) + + def test_base_and2(self): + """Testing a search""" + + res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_BASE, + expression="(&(x=y)(y=a))") + self.assertEqual(len(res11), 1) + + def test_base_false(self): + """Testing a search""" + + res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_BASE, + expression="(|(ou=ou13)(ou=ou12))") + self.assertEqual(len(res11), 0) + + def test_check_base_false(self): + """Testing a search""" + res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_BASE, + expression="(|(ou=ou13)(ou=ou12))") + self.assertEqual(len(res11), 0) + + def test_check_base_error(self): + """Testing a search""" + checkbaseonsearch = {"dn": "@OPTIONS", + "checkBaseOnSearch": b"TRUE"} + try: + self.l.add(checkbaseonsearch) + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS) + m = ldb.Message.from_dict(self.l, + checkbaseonsearch) + self.l.modify(m) + + try: + res11 = self.l.search(base="OU=OU11x,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_BASE, + expression="(|(ou=ou13)(ou=ou12))") + self.fail("Should have failed on missing base") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_NO_SUCH_OBJECT) + + def test_subtree(self): + """Testing a search""" + + try: + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE) + if hasattr(self, 'IDXCHECK'): + self.fail() + except ldb.LdbError as err: + enum = err.args[0] + estr = err.args[1] + self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING) + self.assertIn(estr, "ldb FULL SEARCH disabled") + else: + self.assertEqual(len(res11), 25) + + def test_subtree2(self): + """Testing a search""" + + try: + res11 = self.l.search(base="DC=ORG", + scope=ldb.SCOPE_SUBTREE) + if hasattr(self, 'IDXCHECK'): + self.fail() + except ldb.LdbError as err: + enum = err.args[0] + estr = err.args[1] + self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING) + self.assertIn(estr, "ldb FULL SEARCH disabled") + else: + self.assertEqual(len(res11), 36) + + def test_subtree_and(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(&(ou=ou11)(ou=ou12))") + self.assertEqual(len(res11), 0) + + def test_subtree_and2(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(&(x=y)(|(y=b)(y=c)))") + self.assertEqual(len(res11), 1) + + def test_subtree_and2_lower(self): + """Testing a search""" + + res11 = self.l.search(base="DC=samba,DC=org", + scope=ldb.SCOPE_SUBTREE, + expression="(&(x=y)(|(y=b)(y=c)))") + self.assertEqual(len(res11), 1) + + def test_subtree_or(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(|(ou=ou11)(ou=ou12))") + self.assertEqual(len(res11), 2) + + def test_subtree_or2(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(|(x=y)(y=b))") + self.assertEqual(len(res11), 20) + + def test_subtree_or3(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(|(x=y)(y=b)(y=c))") + self.assertEqual(len(res11), 22) + + def test_one_and(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(ou=ou11)(ou=ou12))") + self.assertEqual(len(res11), 0) + + def test_one_and2(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(x=y)(y=b))") + self.assertEqual(len(res11), 1) + + def test_one_or(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(|(ou=ou11)(ou=ou12))") + self.assertEqual(len(res11), 2) + + def test_one_or2(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(|(x=y)(y=b))") + self.assertEqual(len(res11), 20) + + def test_one_or2_lower(self): + """Testing a search""" + + res11 = self.l.search(base="DC=samba,DC=org", + scope=ldb.SCOPE_ONELEVEL, + expression="(|(x=y)(y=b))") + self.assertEqual(len(res11), 20) + + def test_one_unindexable(self): + """Testing a search""" + + try: + res11 = self.l.search(base="DC=samba,DC=org", + scope=ldb.SCOPE_ONELEVEL, + expression="(y=b*)") + if hasattr(self, 'IDX') and \ + not hasattr(self, 'IDXONE') and \ + hasattr(self, 'IDXCHECK'): + self.fail("Should have failed as un-indexed search") + + self.assertEqual(len(res11), 9) + + except ldb.LdbError as err: + enum = err.args[0] + estr = err.args[1] + self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING) + self.assertIn(estr, "ldb FULL SEARCH disabled") + + def test_one_unindexable_presence(self): + """Testing a search""" + + try: + res11 = self.l.search(base="DC=samba,DC=org", + scope=ldb.SCOPE_ONELEVEL, + expression="(y=*)") + if hasattr(self, 'IDX') and \ + not hasattr(self, 'IDXONE') and \ + hasattr(self, 'IDXCHECK'): + self.fail("Should have failed as un-indexed search") + + self.assertEqual(len(res11), 24) + + except ldb.LdbError as err: + enum = err.args[0] + estr = err.args[1] + self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING) + self.assertIn(estr, "ldb FULL SEARCH disabled") + + def test_subtree_and_or(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(&(|(x=z)(y=b))(x=x)(y=c))") + self.assertEqual(len(res11), 0) + + def test_subtree_and_or2(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(&(x=x)(y=c)(|(x=z)(y=b)))") + self.assertEqual(len(res11), 0) + + def test_subtree_and_or3(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(&(|(ou=ou11)(ou=ou10))(|(x=y)(y=b)(y=c)))") + self.assertEqual(len(res11), 2) + + def test_subtree_and_or4(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(&(|(x=y)(y=b)(y=c))(|(ou=ou11)(ou=ou10)))") + self.assertEqual(len(res11), 2) + + def test_subtree_and_or5(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(&(|(x=y)(y=b)(y=c))(ou=ou11))") + self.assertEqual(len(res11), 1) + + def test_subtree_or_and(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(|(x=x)(y=c)(&(x=z)(y=b)))") + self.assertEqual(len(res11), 10) + + def test_subtree_large_and_unique(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(&(ou=ou10)(y=a))") + self.assertEqual(len(res11), 1) + + def test_subtree_unique(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(ou=ou10)") + self.assertEqual(len(res11), 1) + + def test_subtree_unique_elsewhere(self): + """Testing a search""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(ou=ou10)") + self.assertEqual(len(res11), 0) + + def test_subtree_unique_elsewhere2(self): + """Testing a search""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=NET", + scope=ldb.SCOPE_SUBTREE, + expression="(ou=unique)") + self.assertEqual(len(res11), 1) + + def test_subtree_unique_elsewhere3(self): + """Testing a search""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(ou=unique)") + self.assertEqual(len(res11), 0) + + def test_subtree_unique_elsewhere4(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(ou=unique)") + self.assertEqual(len(res11), 0) + + def test_subtree_unique_elsewhere5(self): + """Testing a search""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=COM", + scope=ldb.SCOPE_SUBTREE, + expression="(ou=unique)") + self.assertEqual(len(res11), 0) + + def test_subtree_unique_elsewhere6(self): + """Testing a search""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(ou=unique)") + self.assertEqual(len(res11), 0) + + def test_subtree_unique_elsewhere7(self): + """Testing a search""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=COM", + scope=ldb.SCOPE_SUBTREE, + expression="(ou=ou10)") + self.assertEqual(len(res11), 0) + + def test_subtree_unique_here(self): + """Testing a search""" + + res11 = self.l.search(base="OU=UNIQUE,DC=EXAMPLE,DC=NET", + scope=ldb.SCOPE_SUBTREE, + expression="(ou=unique)") + self.assertEqual(len(res11), 1) + + def test_subtree_and_none(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(&(ou=ouX)(y=a))") + self.assertEqual(len(res11), 0) + + def test_subtree_and_idx_record(self): + """Testing a search against the index record""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(@IDXDN=DC=SAMBA,DC=ORG)") + self.assertEqual(len(res11), 0) + + def test_subtree_and_idxone_record(self): + """Testing a search against the index record""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(@IDXONE=DC=SAMBA,DC=ORG)") + self.assertEqual(len(res11), 0) + + def test_onelevel(self): + """Testing a search""" + + try: + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL) + if hasattr(self, 'IDXCHECK') \ + and not hasattr(self, 'IDXONE'): + self.fail() + except ldb.LdbError as err: + enum = err.args[0] + estr = err.args[1] + self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING) + self.assertIn(estr, "ldb FULL SEARCH disabled") + else: + self.assertEqual(len(res11), 24) + + def test_onelevel2(self): + """Testing a search""" + + try: + res11 = self.l.search(base="DC=EXAMPLE,DC=ORG", + scope=ldb.SCOPE_ONELEVEL) + if hasattr(self, 'IDXCHECK') \ + and not hasattr(self, 'IDXONE'): + self.fail() + self.fail() + except ldb.LdbError as err: + enum = err.args[0] + estr = err.args[1] + self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING) + self.assertIn(estr, "ldb FULL SEARCH disabled") + else: + self.assertEqual(len(res11), 9) + + def test_onelevel_and_or(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(|(x=z)(y=b))(x=x)(y=c))") + self.assertEqual(len(res11), 0) + + def test_onelevel_and_or2(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(x=x)(y=c)(|(x=z)(y=b)))") + self.assertEqual(len(res11), 0) + + def test_onelevel_and_or3(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(|(ou=ou11)(ou=ou10))(|(x=y)(y=b)(y=c)))") + self.assertEqual(len(res11), 2) + + def test_onelevel_and_or4(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(|(x=y)(y=b)(y=c))(|(ou=ou11)(ou=ou10)))") + self.assertEqual(len(res11), 2) + + def test_onelevel_and_or5(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(|(x=y)(y=b)(y=c))(ou=ou11))") + self.assertEqual(len(res11), 1) + + def test_onelevel_or_and(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(|(x=x)(y=c)(&(x=z)(y=b)))") + self.assertEqual(len(res11), 10) + + def test_onelevel_large_and_unique(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(ou=ou10)(y=a))") + self.assertEqual(len(res11), 1) + + def test_onelevel_unique(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(ou=ou10)") + self.assertEqual(len(res11), 1) + + def test_onelevel_unique_elsewhere(self): + """Testing a search""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(ou=ou10)") + self.assertEqual(len(res11), 0) + + def test_onelevel_unique_elsewhere2(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=NET", + scope=ldb.SCOPE_ONELEVEL, + expression="(ou=unique)") + self.assertEqual(len(res11), 1) + + def test_onelevel_unique_elsewhere3(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(ou=unique)") + self.assertEqual(len(res11), 0) + + def test_onelevel_unique_elsewhere4(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(ou=unique)") + self.assertEqual(len(res11), 0) + + def test_onelevel_unique_elsewhere5(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=COM", + scope=ldb.SCOPE_ONELEVEL, + expression="(ou=unique)") + self.assertEqual(len(res11), 0) + + def test_onelevel_unique_elsewhere6(self): + """Testing a search""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=COM", + scope=ldb.SCOPE_ONELEVEL, + expression="(ou=ou10)") + self.assertEqual(len(res11), 0) + + def test_onelevel_unique_here(self): + """Testing a search""" + + res11 = self.l.search(base="OU=UNIQUE,DC=EXAMPLE,DC=NET", + scope=ldb.SCOPE_ONELEVEL, + expression="(ou=unique)") + self.assertEqual(len(res11), 0) + + def test_onelevel_and_none(self): + """Testing a search""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(ou=ouX)(y=a))") + self.assertEqual(len(res11), 0) + + def test_onelevel_and_idx_record(self): + """Testing a search against the index record""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(@IDXDN=DC=SAMBA,DC=ORG)") + self.assertEqual(len(res11), 0) + + def test_onelevel_and_idxone_record(self): + """Testing a search against the index record""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(@IDXONE=DC=SAMBA,DC=ORG)") + self.assertEqual(len(res11), 0) + + def test_subtree_unindexable(self): + """Testing a search""" + + try: + res11 = self.l.search(base="DC=samba,DC=org", + scope=ldb.SCOPE_SUBTREE, + expression="(y=b*)") + if hasattr(self, 'IDX') and \ + hasattr(self, 'IDXCHECK'): + self.fail("Should have failed as un-indexed search") + + self.assertEqual(len(res11), 9) + + except ldb.LdbError as err: + enum = err.args[0] + estr = err.args[1] + self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING) + self.assertIn(estr, "ldb FULL SEARCH disabled") + + def test_onelevel_only_and_or(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(|(x=z)(y=b))(x=x)(y=c))") + self.assertEqual(len(res11), 0) + + def test_onelevel_only_and_or2(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(x=x)(y=c)(|(x=z)(y=b)))") + self.assertEqual(len(res11), 0) + + def test_onelevel_only_and_or3(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(|(ou=ou11)(ou=ou10))(|(x=y)(y=b)(y=c)))") + self.assertEqual(len(res11), 0) + + def test_onelevel_only_and_or4(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(|(x=y)(y=b)(y=c))(|(ou=ou11)(ou=ou10)))") + self.assertEqual(len(res11), 0) + + def test_onelevel_only_and_or5(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(|(x=y)(y=b)(y=c))(ou=ou11))") + self.assertEqual(len(res11), 0) + + def test_onelevel_only_or_and(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(|(x=x)(y=c)(&(x=z)(y=b)))") + self.assertEqual(len(res11), 0) + + def test_onelevel_only_large_and_unique(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(ou=ou10)(y=a))") + self.assertEqual(len(res11), 0) + + def test_onelevel_only_unique(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(ou=ou10)") + self.assertEqual(len(res11), 0) + + def test_onelevel_only_unique2(self): + """Testing a search""" + + res11 = self.l.search(base="DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(ou=unique)") + self.assertEqual(len(res11), 0) + + def test_onelevel_only_and_none(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(ou=ouX)(y=a))") + self.assertEqual(len(res11), 0) + + def test_onelevel_small_and_or(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(|(x=z)(y=b))(x=x)(y=c))") + self.assertEqual(len(res11), 0) + + def test_onelevel_small_and_or2(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(x=x)(y=c)(|(x=z)(y=b)))") + self.assertEqual(len(res11), 0) + + def test_onelevel_small_and_or3(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(|(ou=ou1)(ou=ou2))(|(x=y)(y=b)(y=c)))") + self.assertEqual(len(res11), 2) + + def test_onelevel_small_and_or4(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(|(x=y)(y=b)(y=c))(|(ou=ou1)(ou=ou2)))") + self.assertEqual(len(res11), 2) + + def test_onelevel_small_and_or5(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(|(x=y)(y=b)(y=c))(ou=ou1))") + self.assertEqual(len(res11), 1) + + def test_onelevel_small_or_and(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(|(x=x)(y=c)(&(x=z)(y=b)))") + self.assertEqual(len(res11), 2) + + def test_onelevel_small_large_and_unique(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(ou=ou9)(y=a))") + self.assertEqual(len(res11), 1) + + def test_onelevel_small_unique_elsewhere(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(ou=ou10)") + self.assertEqual(len(res11), 0) + + def test_onelevel_small_and_none(self): + """Testing a search (showing that onelevel is not subtree)""" + + res11 = self.l.search(base="DC=EXAMPLE,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(&(ou=ouX)(y=a))") + self.assertEqual(len(res11), 0) + + def test_subtree_unindexable_presence(self): + """Testing a search""" + + try: + res11 = self.l.search(base="DC=samba,DC=org", + scope=ldb.SCOPE_SUBTREE, + expression="(y=*)") + if hasattr(self, 'IDX') and \ + hasattr(self, 'IDXCHECK'): + self.fail("Should have failed as un-indexed search") + + self.assertEqual(len(res11), 24) + + except ldb.LdbError as err: + enum = err.args[0] + estr = err.args[1] + self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING) + self.assertIn(estr, "ldb FULL SEARCH disabled") + + def test_dn_filter_one(self): + """Testing that a dn= filter succeeds + (or fails with disallowDNFilter + set and IDXGUID or (IDX and not IDXONE) mode) + when the scope is SCOPE_ONELEVEL. + + This should be made more consistent, but for now lock in + the behaviour + + """ + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(dn=OU=OU1,DC=SAMBA,DC=ORG)") + if hasattr(self, 'disallowDNFilter') and \ + hasattr(self, 'IDX') and \ + (hasattr(self, 'IDXGUID') or + ((not hasattr(self, 'IDXONE') and hasattr(self, 'IDX')))): + self.assertEqual(len(res11), 0) + else: + self.assertEqual(len(res11), 1) + + def test_dn_filter_subtree(self): + """Testing that a dn= filter succeeds + (or fails with disallowDNFilter set) + when the scope is SCOPE_SUBTREE""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(dn=OU=OU1,DC=SAMBA,DC=ORG)") + if hasattr(self, 'disallowDNFilter') \ + and hasattr(self, 'IDX'): + self.assertEqual(len(res11), 0) + else: + self.assertEqual(len(res11), 1) + + def test_dn_filter_base(self): + """Testing that (incorrectly) a dn= filter works + when the scope is SCOPE_BASE""" + + res11 = self.l.search(base="OU=OU1,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_BASE, + expression="(dn=OU=OU1,DC=SAMBA,DC=ORG)") + + # At some point we should fix this, but it isn't trivial + self.assertEqual(len(res11), 1) + + def test_distinguishedName_filter_one(self): + """Testing that a distinguishedName= filter succeeds + when the scope is SCOPE_ONELEVEL. + + This should be made more consistent, but for now lock in + the behaviour + + """ + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(distinguishedName=OU=OU1,DC=SAMBA,DC=ORG)") + self.assertEqual(len(res11), 1) + + def test_distinguishedName_filter_subtree(self): + """Testing that a distinguishedName= filter succeeds + when the scope is SCOPE_SUBTREE""" + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(distinguishedName=OU=OU1,DC=SAMBA,DC=ORG)") + self.assertEqual(len(res11), 1) + + def test_distinguishedName_filter_base(self): + """Testing that (incorrectly) a distinguishedName= filter works + when the scope is SCOPE_BASE""" + + res11 = self.l.search(base="OU=OU1,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_BASE, + expression="(distinguishedName=OU=OU1,DC=SAMBA,DC=ORG)") + + # At some point we should fix this, but it isn't trivial + self.assertEqual(len(res11), 1) + + def test_bad_dn_filter_base(self): + """Testing that a dn= filter on an invalid DN works + when the scope is SCOPE_BASE but + returns zero results""" + + res11 = self.l.search(base="OU=OU1,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_BASE, + expression="(dn=OU=OU1,DC=SAMBA,DCXXXX)") + + # At some point we should fix this, but it isn't trivial + self.assertEqual(len(res11), 0) + + + def test_bad_dn_filter_one(self): + """Testing that a dn= filter succeeds but returns zero + results when the DN is not valid on a SCOPE_ONELEVEL search + + """ + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(dn=OU=OU1,DC=SAMBA,DCXXXX)") + self.assertEqual(len(res11), 0) + + def test_bad_dn_filter_subtree(self): + """Testing that a dn= filter succeeds but returns zero + results when the DN is not valid on a SCOPE_SUBTREE search + + """ + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(dn=OU=OU1,DC=SAMBA,DCXXXX)") + self.assertEqual(len(res11), 0) + + def test_bad_distinguishedName_filter_base(self): + """Testing that a distinguishedName= filter on an invalid DN works + when the scope is SCOPE_BASE but + returns zero results""" + + res11 = self.l.search(base="OU=OU1,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_BASE, + expression="(distinguishedName=OU=OU1,DC=SAMBA,DCXXXX)") + + # At some point we should fix this, but it isn't trivial + self.assertEqual(len(res11), 0) + + + def test_bad_distinguishedName_filter_one(self): + """Testing that a distinguishedName= filter succeeds but returns zero + results when the DN is not valid on a SCOPE_ONELEVEL search + + """ + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression="(distinguishedName=OU=OU1,DC=SAMBA,DCXXXX)") + self.assertEqual(len(res11), 0) + + def test_bad_distinguishedName_filter_subtree(self): + """Testing that a distinguishedName= filter succeeds but returns zero + results when the DN is not valid on a SCOPE_SUBTREE search + + """ + + res11 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(distinguishedName=OU=OU1,DC=SAMBA,DCXXXX)") + self.assertEqual(len(res11), 0) + + def test_bad_dn_search_base(self): + """Testing with a bad base DN (SCOPE_BASE)""" + + try: + res11 = self.l.search(base="OU=OU1,DC=SAMBA,DCXXX", + scope=ldb.SCOPE_BASE) + self.fail("Should have failed with ERR_INVALID_DN_SYNTAX") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_INVALID_DN_SYNTAX) + + + def test_bad_dn_search_one(self): + """Testing with a bad base DN (SCOPE_ONELEVEL)""" + + try: + res11 = self.l.search(base="DC=SAMBA,DCXXXX", + scope=ldb.SCOPE_ONELEVEL) + self.fail("Should have failed with ERR_INVALID_DN_SYNTAX") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_INVALID_DN_SYNTAX) + + def test_bad_dn_search_subtree(self): + """Testing with a bad base DN (SCOPE_SUBTREE)""" + + try: + res11 = self.l.search(base="DC=SAMBA,DCXXXX", + scope=ldb.SCOPE_SUBTREE) + self.fail("Should have failed with ERR_INVALID_DN_SYNTAX") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_INVALID_DN_SYNTAX) + + + +# Run the search tests against an lmdb backend +class SearchTestsLmdb(SearchTests): + + def setUp(self): + if os.environ.get('HAVE_LMDB', '1') == '0': + self.skipTest("No lmdb backend") + self.prefix = MDB_PREFIX + self.index = MDB_INDEX_OBJ + super(SearchTestsLmdb, self).setUp() + + def tearDown(self): + super(SearchTestsLmdb, self).tearDown() + + +class IndexedSearchTests(SearchTests): + """Test searches using the index, to ensure the index doesn't + break things""" + + def setUp(self): + super(IndexedSearchTests, self).setUp() + self.l.add({"dn": "@INDEXLIST", + "@IDXATTR": [b"x", b"y", b"ou"]}) + self.IDX = True + + +class IndexedCheckSearchTests(IndexedSearchTests): + """Test searches using the index, to ensure the index doesn't + break things (full scan disabled)""" + + def setUp(self): + self.IDXCHECK = True + super(IndexedCheckSearchTests, self).setUp() + + +class IndexedSearchDnFilterTests(SearchTests): + """Test searches using the index, to ensure the index doesn't + break things""" + + def setUp(self): + super(IndexedSearchDnFilterTests, self).setUp() + self.l.add({"dn": "@OPTIONS", + "disallowDNFilter": "TRUE"}) + self.disallowDNFilter = True + + self.l.add({"dn": "@INDEXLIST", + "@IDXATTR": [b"x", b"y", b"ou"]}) + self.IDX = True + + +class IndexedAndOneLevelSearchTests(SearchTests): + """Test searches using the index including @IDXONE, to ensure + the index doesn't break things""" + + def setUp(self): + super(IndexedAndOneLevelSearchTests, self).setUp() + self.l.add({"dn": "@INDEXLIST", + "@IDXATTR": [b"x", b"y", b"ou"], + "@IDXONE": [b"1"]}) + self.IDX = True + self.IDXONE = True + + +class IndexedCheckedAndOneLevelSearchTests(IndexedAndOneLevelSearchTests): + """Test searches using the index including @IDXONE, to ensure + the index doesn't break things (full scan disabled)""" + + def setUp(self): + self.IDXCHECK = True + super(IndexedCheckedAndOneLevelSearchTests, self).setUp() + + +class IndexedAndOneLevelDNFilterSearchTests(SearchTests): + """Test searches using the index including @IDXONE, to ensure + the index doesn't break things""" + + def setUp(self): + super(IndexedAndOneLevelDNFilterSearchTests, self).setUp() + self.l.add({"dn": "@OPTIONS", + "disallowDNFilter": "TRUE", + "checkBaseOnSearch": "TRUE"}) + self.disallowDNFilter = True + self.checkBaseOnSearch = True + + self.l.add({"dn": "@INDEXLIST", + "@IDXATTR": [b"x", b"y", b"ou"], + "@IDXONE": [b"1"]}) + self.IDX = True + self.IDXONE = True + + +class GUIDIndexedSearchTests(SearchTests): + """Test searches using the index, to ensure the index doesn't + break things""" + + def setUp(self): + self.index = {"dn": "@INDEXLIST", + "@IDXATTR": [b"x", b"y", b"ou"], + "@IDXGUID": [b"objectUUID"], + "@IDX_DN_GUID": [b"GUID"]} + super(GUIDIndexedSearchTests, self).setUp() + + self.IDXGUID = True + + +class GUIDIndexedDNFilterSearchTests(SearchTests): + """Test searches using the index, to ensure the index doesn't + break things""" + + def setUp(self): + self.index = {"dn": "@INDEXLIST", + "@IDXATTR": [b"x", b"y", b"ou"], + "@IDXGUID": [b"objectUUID"], + "@IDX_DN_GUID": [b"GUID"]} + super(GUIDIndexedDNFilterSearchTests, self).setUp() + self.l.add({"dn": "@OPTIONS", + "disallowDNFilter": "TRUE", + "checkBaseOnSearch": "TRUE"}) + self.disallowDNFilter = True + self.checkBaseOnSearch = True + self.IDX = True + self.IDXGUID = True + + +class GUIDAndOneLevelIndexedSearchTests(SearchTests): + """Test searches using the index including @IDXONE, to ensure + the index doesn't break things""" + + def setUp(self): + self.index = {"dn": "@INDEXLIST", + "@IDXATTR": [b"x", b"y", b"ou"], + "@IDXONE": [b"1"], + "@IDXGUID": [b"objectUUID"], + "@IDX_DN_GUID": [b"GUID"]} + super(GUIDAndOneLevelIndexedSearchTests, self).setUp() + self.l.add({"dn": "@OPTIONS", + "disallowDNFilter": "TRUE", + "checkBaseOnSearch": "TRUE"}) + self.disallowDNFilter = True + self.checkBaseOnSearch = True + self.IDX = True + self.IDXGUID = True + self.IDXONE = True + + +class GUIDIndexedSearchTestsLmdb(GUIDIndexedSearchTests): + + def setUp(self): + if os.environ.get('HAVE_LMDB', '1') == '0': + self.skipTest("No lmdb backend") + self.prefix = MDB_PREFIX + super(GUIDIndexedSearchTestsLmdb, self).setUp() + + def tearDown(self): + super(GUIDIndexedSearchTestsLmdb, self).tearDown() + + +class GUIDIndexedDNFilterSearchTestsLmdb(GUIDIndexedDNFilterSearchTests): + + def setUp(self): + if os.environ.get('HAVE_LMDB', '1') == '0': + self.skipTest("No lmdb backend") + self.prefix = MDB_PREFIX + super(GUIDIndexedDNFilterSearchTestsLmdb, self).setUp() + + def tearDown(self): + super(GUIDIndexedDNFilterSearchTestsLmdb, self).tearDown() + + +class GUIDAndOneLevelIndexedSearchTestsLmdb(GUIDAndOneLevelIndexedSearchTests): + + def setUp(self): + if os.environ.get('HAVE_LMDB', '1') == '0': + self.skipTest("No lmdb backend") + self.prefix = MDB_PREFIX + super(GUIDAndOneLevelIndexedSearchTestsLmdb, self).setUp() + + def tearDown(self): + super(GUIDAndOneLevelIndexedSearchTestsLmdb, self).tearDown() + + +class AddModifyTests(LdbBaseTest): + def tearDown(self): + shutil.rmtree(self.testdir) + super(AddModifyTests, self).tearDown() + + # Ensure the LDB is closed now, so we close the FD + del(self.l) + + def setUp(self): + super(AddModifyTests, self).setUp() + self.testdir = tempdir() + self.filename = os.path.join(self.testdir, "add_test.ldb") + self.l = ldb.Ldb(self.url(), + flags=self.flags(), + options=["modules:rdn_name"]) + try: + self.l.add(self.index) + except AttributeError: + pass + + self.l.add({"dn": "DC=SAMBA,DC=ORG", + "name": b"samba.org", + "objectUUID": b"0123456789abcdef"}) + self.l.add({"dn": "@ATTRIBUTES", + "objectUUID": "UNIQUE_INDEX"}) + + def test_add_dup(self): + self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde1"}) + try: + self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde2"}) + self.fail("Should have failed adding duplicate entry") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS) + + def test_add_bad(self): + try: + self.l.add({"dn": "BAD,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde1"}) + self.fail("Should have failed adding entry with invalid DN") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_INVALID_DN_SYNTAX) + + def test_add_del_add(self): + self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde1"}) + self.l.delete("OU=DUP,DC=SAMBA,DC=ORG") + self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde2"}) + + def test_add_move_add(self): + self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde1"}) + self.l.rename("OU=DUP,DC=SAMBA,DC=ORG", + "OU=DUP2,DC=SAMBA,DC=ORG") + self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde2"}) + + def test_add_move_fail_move_move(self): + self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde1"}) + self.l.add({"dn": "OU=DUP2,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde2"}) + + res2 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(objectUUID=0123456789abcde1)") + self.assertEqual(len(res2), 1) + self.assertEqual(str(res2[0].dn), "OU=DUP,DC=SAMBA,DC=ORG") + + res3 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(objectUUID=0123456789abcde2)") + self.assertEqual(len(res3), 1) + self.assertEqual(str(res3[0].dn), "OU=DUP2,DC=SAMBA,DC=ORG") + + try: + self.l.rename("OU=DUP,DC=SAMBA,DC=ORG", + "OU=DUP2,DC=SAMBA,DC=ORG") + self.fail("Should have failed on duplicate DN") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS) + + self.l.rename("OU=DUP2,DC=SAMBA,DC=ORG", + "OU=DUP3,DC=SAMBA,DC=ORG") + + self.l.rename("OU=DUP,DC=SAMBA,DC=ORG", + "OU=DUP2,DC=SAMBA,DC=ORG") + + res2 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(objectUUID=0123456789abcde1)") + self.assertEqual(len(res2), 1) + self.assertEqual(str(res2[0].dn), "OU=DUP2,DC=SAMBA,DC=ORG") + + res3 = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(objectUUID=0123456789abcde2)") + self.assertEqual(len(res3), 1) + self.assertEqual(str(res3[0].dn), "OU=DUP3,DC=SAMBA,DC=ORG") + + def test_move_missing(self): + try: + self.l.rename("OU=DUP,DC=SAMBA,DC=ORG", + "OU=DUP2,DC=SAMBA,DC=ORG") + self.fail("Should have failed on missing") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_NO_SUCH_OBJECT) + + def test_move_missing2(self): + self.l.add({"dn": "OU=DUP2,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde2"}) + + try: + self.l.rename("OU=DUP,DC=SAMBA,DC=ORG", + "OU=DUP2,DC=SAMBA,DC=ORG") + self.fail("Should have failed on missing") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_NO_SUCH_OBJECT) + + def test_move_bad(self): + self.l.add({"dn": "OU=DUP2,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde2"}) + + try: + self.l.rename("OUXDUP,DC=SAMBA,DC=ORG", + "OU=DUP2,DC=SAMBA,DC=ORG") + self.fail("Should have failed on invalid DN") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_INVALID_DN_SYNTAX) + + def test_move_bad2(self): + self.l.add({"dn": "OU=DUP2,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde2"}) + + try: + self.l.rename("OU=DUP,DC=SAMBA,DC=ORG", + "OUXDUP2,DC=SAMBA,DC=ORG") + self.fail("Should have failed on missing") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_INVALID_DN_SYNTAX) + + def test_move_fail_move_add(self): + self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde1"}) + self.l.add({"dn": "OU=DUP2,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde2"}) + try: + self.l.rename("OU=DUP,DC=SAMBA,DC=ORG", + "OU=DUP2,DC=SAMBA,DC=ORG") + self.fail("Should have failed on duplicate DN") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS) + + self.l.rename("OU=DUP2,DC=SAMBA,DC=ORG", + "OU=DUP3,DC=SAMBA,DC=ORG") + + self.l.add({"dn": "OU=DUP2,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde3"}) + + +class AddModifyTestsLmdb(AddModifyTests): + + def setUp(self): + if os.environ.get('HAVE_LMDB', '1') == '0': + self.skipTest("No lmdb backend") + self.prefix = MDB_PREFIX + self.index = MDB_INDEX_OBJ + super(AddModifyTestsLmdb, self).setUp() + + def tearDown(self): + super(AddModifyTestsLmdb, self).tearDown() + + +class IndexedAddModifyTests(AddModifyTests): + """Test searches using the index, to ensure the index doesn't + break things""" + + def setUp(self): + if not hasattr(self, 'index'): + self.index = {"dn": "@INDEXLIST", + "@IDXATTR": [b"x", b"y", b"ou", b"objectUUID", b"z"], + "@IDXONE": [b"1"]} + super(IndexedAddModifyTests, self).setUp() + + def test_duplicate_GUID(self): + try: + self.l.add({"dn": "OU=DUPGUID,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcdef"}) + self.fail("Should have failed adding duplicate GUID") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_CONSTRAINT_VIOLATION) + + def test_duplicate_name_dup_GUID(self): + self.l.add({"dn": "OU=ADMIN,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"a123456789abcdef"}) + try: + self.l.add({"dn": "OU=ADMIN,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"a123456789abcdef"}) + self.fail("Should have failed adding duplicate GUID") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS) + + def test_duplicate_name_dup_GUID2(self): + self.l.add({"dn": "OU=ADMIN,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"abc3456789abcdef"}) + try: + self.l.add({"dn": "OU=ADMIN,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"aaa3456789abcdef"}) + self.fail("Should have failed adding duplicate DN") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS) + + # Checking the GUID didn't stick in the index + self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"aaa3456789abcdef"}) + + def test_add_dup_guid_add(self): + self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde1"}) + try: + self.l.add({"dn": "OU=DUP2,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde1"}) + self.fail("Should have failed on duplicate GUID") + + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_CONSTRAINT_VIOLATION) + + self.l.add({"dn": "OU=DUP2,DC=SAMBA,DC=ORG", + "name": b"Admins", + "x": "z", "y": "a", + "objectUUID": b"0123456789abcde2"}) + + def test_duplicate_index_values(self): + self.l.add({"dn": "OU=DIV1,DC=SAMBA,DC=ORG", + "name": b"Admins", + "z": "1", + "objectUUID": b"0123456789abcdff"}) + self.l.add({"dn": "OU=DIV2,DC=SAMBA,DC=ORG", + "name": b"Admins", + "z": "1", + "objectUUID": b"0123456789abcdfd"}) + + +class GUIDIndexedAddModifyTests(IndexedAddModifyTests): + """Test searches using the index, to ensure the index doesn't + break things""" + + def setUp(self): + self.index = {"dn": "@INDEXLIST", + "@IDXATTR": [b"x", b"y", b"ou"], + "@IDXONE": [b"1"], + "@IDXGUID": [b"objectUUID"], + "@IDX_DN_GUID": [b"GUID"]} + super(GUIDIndexedAddModifyTests, self).setUp() + + +class GUIDTransIndexedAddModifyTests(GUIDIndexedAddModifyTests): + """Test GUID index behaviour insdie the transaction""" + + def setUp(self): + super(GUIDTransIndexedAddModifyTests, self).setUp() + self.l.transaction_start() + + def tearDown(self): + self.l.transaction_commit() + super(GUIDTransIndexedAddModifyTests, self).tearDown() + + +class TransIndexedAddModifyTests(IndexedAddModifyTests): + """Test index behaviour insdie the transaction""" + + def setUp(self): + super(TransIndexedAddModifyTests, self).setUp() + self.l.transaction_start() + + def tearDown(self): + self.l.transaction_commit() + super(TransIndexedAddModifyTests, self).tearDown() + + +class GuidIndexedAddModifyTestsLmdb(GUIDIndexedAddModifyTests): + + def setUp(self): + if os.environ.get('HAVE_LMDB', '1') == '0': + self.skipTest("No lmdb backend") + self.prefix = MDB_PREFIX + super(GuidIndexedAddModifyTestsLmdb, self).setUp() + + def tearDown(self): + super(GuidIndexedAddModifyTestsLmdb, self).tearDown() + + +class GuidTransIndexedAddModifyTestsLmdb(GUIDTransIndexedAddModifyTests): + + def setUp(self): + if os.environ.get('HAVE_LMDB', '1') == '0': + self.skipTest("No lmdb backend") + self.prefix = MDB_PREFIX + super(GuidTransIndexedAddModifyTestsLmdb, self).setUp() + + def tearDown(self): + super(GuidTransIndexedAddModifyTestsLmdb, self).tearDown() + + +class BadIndexTests(LdbBaseTest): + def setUp(self): + super(BadIndexTests, self).setUp() + self.testdir = tempdir() + self.filename = os.path.join(self.testdir, "test.ldb") + self.ldb = ldb.Ldb(self.url(), flags=self.flags()) + if hasattr(self, 'IDXGUID'): + self.ldb.add({"dn": "@INDEXLIST", + "@IDXATTR": [b"x", b"y", b"ou"], + "@IDXGUID": [b"objectUUID"], + "@IDX_DN_GUID": [b"GUID"]}) + else: + self.ldb.add({"dn": "@INDEXLIST", + "@IDXATTR": [b"x", b"y", b"ou"]}) + + super(BadIndexTests, self).setUp() + + def test_unique(self): + self.ldb.add({"dn": "x=x,dc=samba,dc=org", + "objectUUID": b"0123456789abcde1", + "y": "1"}) + self.ldb.add({"dn": "x=y,dc=samba,dc=org", + "objectUUID": b"0123456789abcde2", + "y": "1"}) + self.ldb.add({"dn": "x=z,dc=samba,dc=org", + "objectUUID": b"0123456789abcde3", + "y": "1"}) + + res = self.ldb.search(expression="(y=1)", + base="dc=samba,dc=org") + self.assertEqual(len(res), 3) + + # Now set this to unique index, but forget to check the result + try: + self.ldb.add({"dn": "@ATTRIBUTES", + "y": "UNIQUE_INDEX"}) + self.fail() + except ldb.LdbError: + pass + + # We must still have a working index + res = self.ldb.search(expression="(y=1)", + base="dc=samba,dc=org") + self.assertEqual(len(res), 3) + + def test_unique_transaction(self): + self.ldb.add({"dn": "x=x,dc=samba,dc=org", + "objectUUID": b"0123456789abcde1", + "y": "1"}) + self.ldb.add({"dn": "x=y,dc=samba,dc=org", + "objectUUID": b"0123456789abcde2", + "y": "1"}) + self.ldb.add({"dn": "x=z,dc=samba,dc=org", + "objectUUID": b"0123456789abcde3", + "y": "1"}) + + res = self.ldb.search(expression="(y=1)", + base="dc=samba,dc=org") + self.assertEqual(len(res), 3) + + self.ldb.transaction_start() + + # Now set this to unique index, but forget to check the result + try: + self.ldb.add({"dn": "@ATTRIBUTES", + "y": "UNIQUE_INDEX"}) + except ldb.LdbError: + pass + + try: + self.ldb.transaction_commit() + self.fail() + + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_OPERATIONS_ERROR) + + # We must still have a working index + res = self.ldb.search(expression="(y=1)", + base="dc=samba,dc=org") + + self.assertEqual(len(res), 3) + + def test_casefold(self): + self.ldb.add({"dn": "x=x,dc=samba,dc=org", + "objectUUID": b"0123456789abcde1", + "y": "a"}) + self.ldb.add({"dn": "x=y,dc=samba,dc=org", + "objectUUID": b"0123456789abcde2", + "y": "A"}) + self.ldb.add({"dn": "x=z,dc=samba,dc=org", + "objectUUID": b"0123456789abcde3", + "y": ["a", "A"]}) + + res = self.ldb.search(expression="(y=a)", + base="dc=samba,dc=org") + self.assertEqual(len(res), 2) + + self.ldb.add({"dn": "@ATTRIBUTES", + "y": "CASE_INSENSITIVE"}) + + # We must still have a working index + res = self.ldb.search(expression="(y=a)", + base="dc=samba,dc=org") + + if hasattr(self, 'IDXGUID'): + self.assertEqual(len(res), 3) + else: + # We should not return this entry twice, but sadly + # we have not yet fixed + # https://bugzilla.samba.org/show_bug.cgi?id=13361 + self.assertEqual(len(res), 4) + + def test_casefold_transaction(self): + self.ldb.add({"dn": "x=x,dc=samba,dc=org", + "objectUUID": b"0123456789abcde1", + "y": "a"}) + self.ldb.add({"dn": "x=y,dc=samba,dc=org", + "objectUUID": b"0123456789abcde2", + "y": "A"}) + self.ldb.add({"dn": "x=z,dc=samba,dc=org", + "objectUUID": b"0123456789abcde3", + "y": ["a", "A"]}) + + res = self.ldb.search(expression="(y=a)", + base="dc=samba,dc=org") + self.assertEqual(len(res), 2) + + self.ldb.transaction_start() + + self.ldb.add({"dn": "@ATTRIBUTES", + "y": "CASE_INSENSITIVE"}) + + self.ldb.transaction_commit() + + # We must still have a working index + res = self.ldb.search(expression="(y=a)", + base="dc=samba,dc=org") + + if hasattr(self, 'IDXGUID'): + self.assertEqual(len(res), 3) + else: + # We should not return this entry twice, but sadly + # we have not yet fixed + # https://bugzilla.samba.org/show_bug.cgi?id=13361 + self.assertEqual(len(res), 4) + + def test_modify_transaction(self): + self.ldb.add({"dn": "x=y,dc=samba,dc=org", + "objectUUID": b"0123456789abcde1", + "y": "2", + "z": "2"}) + + res = self.ldb.search(expression="(y=2)", + base="dc=samba,dc=org") + self.assertEqual(len(res), 1) + + self.ldb.add({"dn": "@ATTRIBUTES", + "y": "UNIQUE_INDEX"}) + + self.ldb.transaction_start() + + m = ldb.Message() + m.dn = ldb.Dn(self.ldb, "x=y,dc=samba,dc=org") + m["0"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "y") + m["1"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "not-here") + + try: + self.ldb.modify(m) + self.fail() + + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_NO_SUCH_ATTRIBUTE) + + try: + self.ldb.transaction_commit() + # We should fail here, but we want to be sure + # we fail below + + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_OPERATIONS_ERROR) + + # The index should still be pointing to x=y + res = self.ldb.search(expression="(y=2)", + base="dc=samba,dc=org") + self.assertEqual(len(res), 1) + + try: + self.ldb.add({"dn": "x=y2,dc=samba,dc=org", + "objectUUID": b"0123456789abcde2", + "y": "2"}) + self.fail("Added unique attribute twice") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_CONSTRAINT_VIOLATION) + + res = self.ldb.search(expression="(y=2)", + base="dc=samba,dc=org") + self.assertEqual(len(res), 1) + self.assertEqual(str(res[0].dn), "x=y,dc=samba,dc=org") + + def tearDown(self): + super(BadIndexTests, self).tearDown() + + +class GUIDBadIndexTests(BadIndexTests): + """Test Bad index things with GUID index mode""" + + def setUp(self): + self.IDXGUID = True + + super(GUIDBadIndexTests, self).setUp() + + +class GUIDBadIndexTestsLmdb(BadIndexTests): + + def setUp(self): + if os.environ.get('HAVE_LMDB', '1') == '0': + self.skipTest("No lmdb backend") + self.prefix = MDB_PREFIX + self.index = MDB_INDEX_OBJ + self.IDXGUID = True + super(GUIDBadIndexTestsLmdb, self).setUp() + + def tearDown(self): + super(GUIDBadIndexTestsLmdb, self).tearDown() + + +class BatchModeTests(LdbBaseTest): + + def setUp(self): + super(BatchModeTests, self).setUp() + self.testdir = tempdir() + self.filename = os.path.join(self.testdir, "test.ldb") + self.ldb = ldb.Ldb(self.url(), + flags=self.flags(), + options=["batch_mode:1"]) + if hasattr(self, 'IDXGUID'): + self.ldb.add({"dn": "@INDEXLIST", + "@IDXATTR": [b"x", b"y", b"ou"], + "@IDXGUID": [b"objectUUID"], + "@IDX_DN_GUID": [b"GUID"]}) + else: + self.ldb.add({"dn": "@INDEXLIST", + "@IDXATTR": [b"x", b"y", b"ou"]}) + + def test_modify_transaction(self): + self.ldb.add({"dn": "x=y,dc=samba,dc=org", + "objectUUID": b"0123456789abcde1", + "y": "2", + "z": "2"}) + + res = self.ldb.search(expression="(y=2)", + base="dc=samba,dc=org") + self.assertEqual(len(res), 1) + + self.ldb.add({"dn": "@ATTRIBUTES", + "y": "UNIQUE_INDEX"}) + + self.ldb.transaction_start() + + m = ldb.Message() + m.dn = ldb.Dn(self.ldb, "x=y,dc=samba,dc=org") + m["0"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "y") + m["1"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "not-here") + + try: + self.ldb.modify(m) + self.fail() + + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_NO_SUCH_ATTRIBUTE) + + try: + self.ldb.transaction_commit() + self.fail("Commit should have failed as we were in batch mode") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_OPERATIONS_ERROR) + + def tearDown(self): + super(BatchModeTests, self).tearDown() + + +class DnTests(TestCase): + + def setUp(self): + super(DnTests, self).setUp() + self.ldb = ldb.Ldb() + + def tearDown(self): + super(DnTests, self).tearDown() + del(self.ldb) + + def test_set_dn_invalid(self): + x = ldb.Message() + + def assign(): + x.dn = "astring" + self.assertRaises(TypeError, assign) + + def test_eq(self): + x = ldb.Dn(self.ldb, "dc=foo11,bar=bloe") + y = ldb.Dn(self.ldb, "dc=foo11,bar=bloe") + self.assertEqual(x, y) + y = ldb.Dn(self.ldb, "dc=foo11,bar=blie") + self.assertNotEqual(x, y) + + def test_str(self): + x = ldb.Dn(self.ldb, "dc=foo12,bar=bloe") + self.assertEqual(x.__str__(), "dc=foo12,bar=bloe") + + def test_repr(self): + x = ldb.Dn(self.ldb, "dc=foo13,bla=blie") + self.assertEqual(x.__repr__(), "Dn('dc=foo13,bla=blie')") + + def test_get_casefold_2(self): + x = ldb.Dn(self.ldb, "dc=foo14,bar=bloe") + self.assertEqual(x.get_casefold(), "DC=FOO14,BAR=bloe") + + def test_validate(self): + x = ldb.Dn(self.ldb, "dc=foo15,bar=bloe") + self.assertTrue(x.validate()) + + def test_parent(self): + x = ldb.Dn(self.ldb, "dc=foo16,bar=bloe") + self.assertEqual("bar=bloe", x.parent().__str__()) + + def test_parent_nonexistent(self): + x = ldb.Dn(self.ldb, "@BLA") + self.assertEqual(None, x.parent()) + + def test_is_valid(self): + x = ldb.Dn(self.ldb, "dc=foo18,dc=bloe") + self.assertTrue(x.is_valid()) + x = ldb.Dn(self.ldb, "") + self.assertTrue(x.is_valid()) + + def test_is_special(self): + x = ldb.Dn(self.ldb, "dc=foo19,bar=bloe") + self.assertFalse(x.is_special()) + x = ldb.Dn(self.ldb, "@FOOBAR") + self.assertTrue(x.is_special()) + + def test_check_special(self): + x = ldb.Dn(self.ldb, "dc=foo20,bar=bloe") + self.assertFalse(x.check_special("FOOBAR")) + x = ldb.Dn(self.ldb, "@FOOBAR") + self.assertTrue(x.check_special("@FOOBAR")) + + def test_len(self): + x = ldb.Dn(self.ldb, "dc=foo21,bar=bloe") + self.assertEqual(2, len(x)) + x = ldb.Dn(self.ldb, "dc=foo21") + self.assertEqual(1, len(x)) + + def test_add_child(self): + x = ldb.Dn(self.ldb, "dc=foo22,bar=bloe") + self.assertTrue(x.add_child(ldb.Dn(self.ldb, "bla=bloe"))) + self.assertEqual("bla=bloe,dc=foo22,bar=bloe", x.__str__()) + + def test_add_base(self): + x = ldb.Dn(self.ldb, "dc=foo23,bar=bloe") + base = ldb.Dn(self.ldb, "bla=bloe") + self.assertTrue(x.add_base(base)) + self.assertEqual("dc=foo23,bar=bloe,bla=bloe", x.__str__()) + + def test_add_child_str(self): + x = ldb.Dn(self.ldb, "dc=foo22,bar=bloe") + self.assertTrue(x.add_child("bla=bloe")) + self.assertEqual("bla=bloe,dc=foo22,bar=bloe", x.__str__()) + + def test_add_base_str(self): + x = ldb.Dn(self.ldb, "dc=foo23,bar=bloe") + base = "bla=bloe" + self.assertTrue(x.add_base(base)) + self.assertEqual("dc=foo23,bar=bloe,bla=bloe", x.__str__()) + + def test_add(self): + x = ldb.Dn(self.ldb, "dc=foo24") + y = ldb.Dn(self.ldb, "bar=bla") + self.assertEqual("dc=foo24,bar=bla", str(x + y)) + + def test_remove_base_components(self): + x = ldb.Dn(self.ldb, "dc=foo24,dc=samba,dc=org") + x.remove_base_components(len(x) - 1) + self.assertEqual("dc=foo24", str(x)) + + def test_parse_ldif(self): + msgs = self.ldb.parse_ldif("dn: foo=bar\n") + msg = next(msgs) + self.assertEqual("foo=bar", str(msg[1].dn)) + self.assertTrue(isinstance(msg[1], ldb.Message)) + ldif = self.ldb.write_ldif(msg[1], ldb.CHANGETYPE_NONE) + self.assertEqual("dn: foo=bar\n\n", ldif) + + def test_parse_ldif_more(self): + msgs = self.ldb.parse_ldif("dn: foo=bar\n\n\ndn: bar=bar") + msg = next(msgs) + self.assertEqual("foo=bar", str(msg[1].dn)) + msg = next(msgs) + self.assertEqual("bar=bar", str(msg[1].dn)) + + def test_print_ldif(self): + ldif = '''dn: dc=foo27 +foo: foo + +''' + self.msg = ldb.Message(ldb.Dn(self.ldb, "dc=foo27")) + self.msg["foo"] = [b"foo"] + self.assertEqual(ldif, + self.ldb.write_ldif(self.msg, + ldb.CHANGETYPE_NONE)) + + def test_print_ldif_binary(self): + # this also confirms that ldb flags are set even without a URL) + self.ldb = ldb.Ldb(flags=ldb.FLG_SHOW_BINARY) + ldif = '''dn: dc=foo27 +foo: f +öö + +''' + self.msg = ldb.Message(ldb.Dn(self.ldb, "dc=foo27")) + self.msg["foo"] = ["f\nöö"] + self.assertEqual(ldif, + self.ldb.write_ldif(self.msg, + ldb.CHANGETYPE_NONE)) + + + def test_print_ldif_no_base64_bad(self): + ldif = '''dn: dc=foo27 +foo: f +öö + +''' + self.msg = ldb.Message(ldb.Dn(self.ldb, "dc=foo27")) + self.msg["foo"] = ["f\nöö"] + self.msg["foo"].set_flags(ldb.FLAG_FORCE_NO_BASE64_LDIF) + self.assertEqual(ldif, + self.ldb.write_ldif(self.msg, + ldb.CHANGETYPE_NONE)) + + def test_print_ldif_no_base64_good(self): + ldif = '''dn: dc=foo27 +foo: föö + +''' + self.msg = ldb.Message(ldb.Dn(self.ldb, "dc=foo27")) + self.msg["foo"] = ["föö"] + self.msg["foo"].set_flags(ldb.FLAG_FORCE_NO_BASE64_LDIF) + self.assertEqual(ldif, + self.ldb.write_ldif(self.msg, + ldb.CHANGETYPE_NONE)) + + def test_canonical_string(self): + x = ldb.Dn(self.ldb, "dc=foo25,bar=bloe") + self.assertEqual("/bloe/foo25", x.canonical_str()) + + def test_canonical_ex_string(self): + x = ldb.Dn(self.ldb, "dc=foo26,bar=bloe") + self.assertEqual("/bloe\nfoo26", x.canonical_ex_str()) + + def test_ldb_is_child_of(self): + """Testing ldb_dn_compare_dn""" + dn1 = ldb.Dn(self.ldb, "dc=base") + dn2 = ldb.Dn(self.ldb, "cn=foo,dc=base") + dn3 = ldb.Dn(self.ldb, "cn=bar,dc=base") + dn4 = ldb.Dn(self.ldb, "cn=baz,cn=bar,dc=base") + + self.assertTrue(dn1.is_child_of(dn1)) + self.assertTrue(dn2.is_child_of(dn1)) + self.assertTrue(dn4.is_child_of(dn1)) + self.assertTrue(dn4.is_child_of(dn3)) + self.assertTrue(dn4.is_child_of(dn4)) + self.assertFalse(dn3.is_child_of(dn2)) + self.assertFalse(dn1.is_child_of(dn4)) + + def test_ldb_is_child_of_str(self): + """Testing ldb_dn_compare_dn""" + dn1_str = "dc=base" + dn2_str = "cn=foo,dc=base" + dn3_str = "cn=bar,dc=base" + dn4_str = "cn=baz,cn=bar,dc=base" + + dn1 = ldb.Dn(self.ldb, dn1_str) + dn2 = ldb.Dn(self.ldb, dn2_str) + dn3 = ldb.Dn(self.ldb, dn3_str) + dn4 = ldb.Dn(self.ldb, dn4_str) + + self.assertTrue(dn1.is_child_of(dn1_str)) + self.assertTrue(dn2.is_child_of(dn1_str)) + self.assertTrue(dn4.is_child_of(dn1_str)) + self.assertTrue(dn4.is_child_of(dn3_str)) + self.assertTrue(dn4.is_child_of(dn4_str)) + self.assertFalse(dn3.is_child_of(dn2_str)) + self.assertFalse(dn1.is_child_of(dn4_str)) + + def test_get_component_name(self): + dn = ldb.Dn(self.ldb, "cn=foo,dc=base") + self.assertEqual(dn.get_component_name(0), 'cn') + self.assertEqual(dn.get_component_name(1), 'dc') + self.assertEqual(dn.get_component_name(2), None) + self.assertEqual(dn.get_component_name(-1), None) + + def test_get_component_value(self): + dn = ldb.Dn(self.ldb, "cn=foo,dc=base") + self.assertEqual(dn.get_component_value(0), 'foo') + self.assertEqual(dn.get_component_value(1), 'base') + self.assertEqual(dn.get_component_name(2), None) + self.assertEqual(dn.get_component_name(-1), None) + + def test_set_component(self): + dn = ldb.Dn(self.ldb, "cn=foo,dc=base") + dn.set_component(0, 'cn', 'bar') + self.assertEqual(str(dn), "cn=bar,dc=base") + dn.set_component(1, 'o', 'asep') + self.assertEqual(str(dn), "cn=bar,o=asep") + self.assertRaises(TypeError, dn.set_component, 2, 'dc', 'base') + self.assertEqual(str(dn), "cn=bar,o=asep") + dn.set_component(1, 'o', 'a,b+c') + self.assertEqual(str(dn), r"cn=bar,o=a\,b\+c") + + def test_set_component_bytes(self): + dn = ldb.Dn(self.ldb, "cn=foo,dc=base") + dn.set_component(0, 'cn', b'bar') + self.assertEqual(str(dn), "cn=bar,dc=base") + dn.set_component(1, 'o', b'asep') + self.assertEqual(str(dn), "cn=bar,o=asep") + + def test_set_component_none(self): + dn = ldb.Dn(self.ldb, "cn=foo,cn=bar,dc=base") + self.assertRaises(TypeError, dn.set_component, 1, 'cn', None) + + def test_get_extended_component_null(self): + dn = ldb.Dn(self.ldb, "cn=foo,cn=bar,dc=base") + self.assertEqual(dn.get_extended_component("TEST"), None) + + def test_get_extended_component(self): + self.ldb._register_test_extensions() + dn = ldb.Dn(self.ldb, ";cn=bar,dc=base") + self.assertEqual(dn.get_extended_component("TEST"), b"foo") + + def test_set_extended_component(self): + self.ldb._register_test_extensions() + dn = ldb.Dn(self.ldb, "dc=base") + dn.set_extended_component("TEST", "foo") + self.assertEqual(dn.get_extended_component("TEST"), b"foo") + dn.set_extended_component("TEST", b"bar") + self.assertEqual(dn.get_extended_component("TEST"), b"bar") + + def test_extended_str(self): + self.ldb._register_test_extensions() + dn = ldb.Dn(self.ldb, ";cn=bar,dc=base") + self.assertEqual(dn.extended_str(), ";cn=bar,dc=base") + + def test_get_rdn_name(self): + dn = ldb.Dn(self.ldb, "cn=foo,dc=base") + self.assertEqual(dn.get_rdn_name(), 'cn') + + def test_get_rdn_value(self): + dn = ldb.Dn(self.ldb, "cn=foo,dc=base") + self.assertEqual(dn.get_rdn_value(), 'foo') + + def test_get_casefold(self): + dn = ldb.Dn(self.ldb, "cn=foo,dc=base") + self.assertEqual(dn.get_casefold(), 'CN=FOO,DC=BASE') + + def test_get_linearized(self): + dn = ldb.Dn(self.ldb, "cn=foo,dc=base") + self.assertEqual(dn.get_linearized(), 'cn=foo,dc=base') + + def test_is_null(self): + dn = ldb.Dn(self.ldb, "cn=foo,dc=base") + self.assertFalse(dn.is_null()) + + dn = ldb.Dn(self.ldb, '') + self.assertTrue(dn.is_null()) + + +class LdbMsgTests(TestCase): + + def setUp(self): + super(LdbMsgTests, self).setUp() + self.msg = ldb.Message() + + def test_init_dn(self): + self.msg = ldb.Message(ldb.Dn(ldb.Ldb(), "dc=foo27")) + self.assertEqual("dc=foo27", str(self.msg.dn)) + + def test_iter_items(self): + self.assertEqual(0, len(self.msg.items())) + self.msg.dn = ldb.Dn(ldb.Ldb(), "dc=foo28") + self.assertEqual(1, len(self.msg.items())) + + def test_items(self): + self.msg["foo"] = ["foo"] + self.msg["bar"] = ["bar"] + try: + items = self.msg.items() + except: + self.fail() + self.assertEqual([("foo", ldb.MessageElement(["foo"])), + ("bar", ldb.MessageElement(["bar"]))], + items) + + self.msg.dn = ldb.Dn(ldb.Ldb(), "dc=test") + try: + items = self.msg.items() + except: + self.fail() + self.assertEqual([("dn", ldb.Dn(ldb.Ldb(), "dc=test")), + ("foo", ldb.MessageElement(["foo"])), + ("bar", ldb.MessageElement(["bar"]))], + items) + + def test_repr(self): + self.msg.dn = ldb.Dn(ldb.Ldb(), "dc=foo29") + self.msg["dc"] = b"foo" + self.assertIn(repr(self.msg), [ + "Message({'dn': Dn('dc=foo29'), 'dc': MessageElement([b'foo'])})", + "Message({'dc': MessageElement([b'foo']), 'dn': Dn('dc=foo29')})", + ]) + self.assertIn(repr(self.msg.text), [ + "Message({'dn': Dn('dc=foo29'), 'dc': MessageElement([b'foo'])}).text", + "Message({'dc': MessageElement([b'foo']), 'dn': Dn('dc=foo29')}).text", + ]) + + def test_len(self): + self.assertEqual(0, len(self.msg)) + + def test_notpresent(self): + self.assertRaises(KeyError, lambda: self.msg["foo"]) + + def test_invalid(self): + try: + self.assertRaises(TypeError, lambda: self.msg[42]) + except KeyError: + self.fail() + + def test_del(self): + del self.msg["foo"] + + def test_add(self): + self.msg.add(ldb.MessageElement([b"456"], ldb.FLAG_MOD_ADD, "bla")) + + def test_add_text(self): + self.msg.add(ldb.MessageElement(["456"], ldb.FLAG_MOD_ADD, "bla")) + + def test_elements_empty(self): + self.assertEqual([], self.msg.elements()) + + def test_elements(self): + el = ldb.MessageElement([b"456"], ldb.FLAG_MOD_ADD, "bla") + self.msg.add(el) + self.assertEqual([el], self.msg.elements()) + self.assertEqual([el.text], self.msg.text.elements()) + + def test_add_value(self): + self.assertEqual(0, len(self.msg)) + self.msg["foo"] = [b"foo"] + self.assertEqual(1, len(self.msg)) + + def test_add_value_text(self): + self.assertEqual(0, len(self.msg)) + self.msg["foo"] = ["foo"] + self.assertEqual(1, len(self.msg)) + + def test_add_value_multiple(self): + self.assertEqual(0, len(self.msg)) + self.msg["foo"] = [b"foo", b"bla"] + self.assertEqual(1, len(self.msg)) + self.assertEqual([b"foo", b"bla"], list(self.msg["foo"])) + + def test_add_value_multiple_text(self): + self.assertEqual(0, len(self.msg)) + self.msg["foo"] = ["foo", "bla"] + self.assertEqual(1, len(self.msg)) + self.assertEqual(["foo", "bla"], list(self.msg.text["foo"])) + + def test_set_value(self): + self.msg["foo"] = [b"fool"] + self.assertEqual([b"fool"], list(self.msg["foo"])) + self.msg["foo"] = [b"bar"] + self.assertEqual([b"bar"], list(self.msg["foo"])) + + def test_set_value_text(self): + self.msg["foo"] = ["fool"] + self.assertEqual(["fool"], list(self.msg.text["foo"])) + self.msg["foo"] = ["bar"] + self.assertEqual(["bar"], list(self.msg.text["foo"])) + + def test_keys(self): + self.msg.dn = ldb.Dn(ldb.Ldb(), "@BASEINFO") + self.msg["foo"] = [b"bla"] + self.msg["bar"] = [b"bla"] + self.assertEqual(["dn", "foo", "bar"], list(self.msg.keys())) + + def test_keys_text(self): + self.msg.dn = ldb.Dn(ldb.Ldb(), "@BASEINFO") + self.msg["foo"] = ["bla"] + self.msg["bar"] = ["bla"] + self.assertEqual(["dn", "foo", "bar"], list(self.msg.text.keys())) + + def test_dn(self): + self.msg.dn = ldb.Dn(ldb.Ldb(), "@BASEINFO") + self.assertEqual("@BASEINFO", self.msg.dn.__str__()) + + def test_get_dn(self): + self.msg.dn = ldb.Dn(ldb.Ldb(), "@BASEINFO") + self.assertEqual("@BASEINFO", self.msg.get("dn").__str__()) + + def test_dn_text(self): + self.msg.text.dn = ldb.Dn(ldb.Ldb(), "@BASEINFO") + self.assertEqual("@BASEINFO", str(self.msg.dn)) + self.assertEqual("@BASEINFO", str(self.msg.text.dn)) + + def test_get_dn_text(self): + self.msg.dn = ldb.Dn(ldb.Ldb(), "@BASEINFO") + self.assertEqual("@BASEINFO", str(self.msg.get("dn"))) + self.assertEqual("@BASEINFO", str(self.msg.text.get("dn"))) + + def test_get_invalid(self): + self.msg.dn = ldb.Dn(ldb.Ldb(), "@BASEINFO") + self.assertRaises(TypeError, self.msg.get, 42) + + def test_get_other(self): + self.msg["foo"] = [b"bar"] + self.assertEqual(b"bar", self.msg.get("foo")[0]) + self.assertEqual(b"bar", self.msg.get("foo", idx=0)) + self.assertEqual(None, self.msg.get("foo", idx=1)) + self.assertEqual("", self.msg.get("foo", default='', idx=1)) + + def test_get_other_text(self): + self.msg["foo"] = ["bar"] + self.assertEqual(["bar"], list(self.msg.text.get("foo"))) + self.assertEqual("bar", self.msg.text.get("foo")[0]) + self.assertEqual("bar", self.msg.text.get("foo", idx=0)) + self.assertEqual(None, self.msg.get("foo", idx=1)) + self.assertEqual("", self.msg.get("foo", default='', idx=1)) + + def test_get_default(self): + self.assertEqual(None, self.msg.get("tatayoyo", idx=0)) + self.assertEqual("anniecordie", self.msg.get("tatayoyo", "anniecordie")) + + def test_get_default_text(self): + self.assertEqual(None, self.msg.text.get("tatayoyo", idx=0)) + self.assertEqual("anniecordie", self.msg.text.get("tatayoyo", "anniecordie")) + + def test_get_unknown(self): + self.assertEqual(None, self.msg.get("lalalala")) + + def test_get_unknown_text(self): + self.assertEqual(None, self.msg.text.get("lalalala")) + + def test_contains(self): + self.msg['foo'] = ['bar'] + self.assertIn('foo', self.msg) + + self.msg['Foo'] = ['bar'] + self.assertIn('Foo', self.msg) + + def test_contains_case(self): + self.msg['foo'] = ['bar'] + self.assertIn('Foo', self.msg) + + self.msg['Foo'] = ['bar'] + self.assertIn('foo', self.msg) + + def test_contains_dn(self): + self.assertIn('dn', self.msg) + + def test_contains_dn_case(self): + self.assertIn('DN', self.msg) + + def test_contains_invalid(self): + self.assertRaises(TypeError, lambda: None in self.msg) + + def test_msg_diff(self): + l = ldb.Ldb() + msgs = l.parse_ldif("dn: foo=bar\nfoo: bar\nbaz: do\n\ndn: foo=bar\nfoo: bar\nbaz: dont\n") + msg1 = next(msgs)[1] + msg2 = next(msgs)[1] + msgdiff = l.msg_diff(msg1, msg2) + self.assertEqual("foo=bar", msgdiff.get("dn").__str__()) + self.assertRaises(KeyError, lambda: msgdiff["foo"]) + self.assertEqual(1, len(msgdiff)) + + def test_equal_empty(self): + msg1 = ldb.Message() + msg2 = ldb.Message() + self.assertEqual(msg1, msg2) + + def test_equal_simplel(self): + db = ldb.Ldb() + msg1 = ldb.Message() + msg1.dn = ldb.Dn(db, "foo=bar") + msg2 = ldb.Message() + msg2.dn = ldb.Dn(db, "foo=bar") + self.assertEqual(msg1, msg2) + msg1['foo'] = b'bar' + msg2['foo'] = b'bar' + self.assertEqual(msg1, msg2) + msg2['foo'] = b'blie' + self.assertNotEqual(msg1, msg2) + msg2['foo'] = b'blie' + + def test_from_dict(self): + rec = {"dn": "dc=fromdict", + "a1": [b"a1-val1", b"a1-val1"]} + l = ldb.Ldb() + # check different types of input Flags + for flags in [ldb.FLAG_MOD_ADD, ldb.FLAG_MOD_REPLACE, ldb.FLAG_MOD_DELETE]: + m = ldb.Message.from_dict(l, rec, flags) + self.assertEqual(rec["a1"], list(m["a1"])) + self.assertEqual(flags, m["a1"].flags()) + # check input params + self.assertRaises(TypeError, ldb.Message.from_dict, dict(), rec, ldb.FLAG_MOD_REPLACE) + self.assertRaises(TypeError, ldb.Message.from_dict, l, list(), ldb.FLAG_MOD_REPLACE) + self.assertRaises(ValueError, ldb.Message.from_dict, l, rec, 0) + # Message.from_dict expects dictionary with 'dn' + err_rec = {"a1": [b"a1-val1", b"a1-val1"]} + self.assertRaises(TypeError, ldb.Message.from_dict, l, err_rec, ldb.FLAG_MOD_REPLACE) + + def test_from_dict_text(self): + rec = {"dn": "dc=fromdict", + "a1": ["a1-val1", "a1-val1"]} + l = ldb.Ldb() + # check different types of input Flags + for flags in [ldb.FLAG_MOD_ADD, ldb.FLAG_MOD_REPLACE, ldb.FLAG_MOD_DELETE]: + m = ldb.Message.from_dict(l, rec, flags) + self.assertEqual(rec["a1"], list(m.text["a1"])) + self.assertEqual(flags, m.text["a1"].flags()) + # check input params + self.assertRaises(TypeError, ldb.Message.from_dict, dict(), rec, ldb.FLAG_MOD_REPLACE) + self.assertRaises(TypeError, ldb.Message.from_dict, l, list(), ldb.FLAG_MOD_REPLACE) + self.assertRaises(ValueError, ldb.Message.from_dict, l, rec, 0) + # Message.from_dict expects dictionary with 'dn' + err_rec = {"a1": ["a1-val1", "a1-val1"]} + self.assertRaises(TypeError, ldb.Message.from_dict, l, err_rec, ldb.FLAG_MOD_REPLACE) + + def test_copy_add_message_element(self): + m = ldb.Message() + m["1"] = ldb.MessageElement([b"val 111"], ldb.FLAG_MOD_ADD, "1") + m["2"] = ldb.MessageElement([b"val 222"], ldb.FLAG_MOD_ADD, "2") + mto = ldb.Message() + mto["1"] = m["1"] + mto["2"] = m["2"] + self.assertEqual(mto["1"], m["1"]) + self.assertEqual(mto["2"], m["2"]) + mto = ldb.Message() + mto.add(m["1"]) + mto.add(m["2"]) + self.assertEqual(mto["1"], m["1"]) + self.assertEqual(mto["2"], m["2"]) + + def test_copy_add_message_element_text(self): + m = ldb.Message() + m["1"] = ldb.MessageElement(["val 111"], ldb.FLAG_MOD_ADD, "1") + m["2"] = ldb.MessageElement(["val 222"], ldb.FLAG_MOD_ADD, "2") + mto = ldb.Message() + mto["1"] = m["1"] + mto["2"] = m["2"] + self.assertEqual(mto["1"], m.text["1"]) + self.assertEqual(mto["2"], m.text["2"]) + mto = ldb.Message() + mto.add(m["1"]) + mto.add(m["2"]) + self.assertEqual(mto.text["1"], m.text["1"]) + self.assertEqual(mto.text["2"], m.text["2"]) + self.assertEqual(mto["1"], m["1"]) + self.assertEqual(mto["2"], m["2"]) + + +class MessageElementTests(TestCase): + + def test_cmp_element(self): + x = ldb.MessageElement([b"foo"]) + y = ldb.MessageElement([b"foo"]) + z = ldb.MessageElement([b"bzr"]) + self.assertEqual(x, y) + self.assertNotEqual(x, z) + + def test_cmp_element_text(self): + x = ldb.MessageElement([b"foo"]) + y = ldb.MessageElement(["foo"]) + self.assertEqual(x, y) + + def test_create_iterable(self): + x = ldb.MessageElement([b"foo"]) + self.assertEqual([b"foo"], list(x)) + self.assertEqual(["foo"], list(x.text)) + + def test_repr(self): + x = ldb.MessageElement([b"foo"]) + self.assertEqual("MessageElement([b'foo'])", repr(x)) + self.assertEqual("MessageElement([b'foo']).text", repr(x.text)) + x = ldb.MessageElement([b"foo", b"bla"]) + self.assertEqual(2, len(x)) + self.assertEqual("MessageElement([b'foo',b'bla'])", repr(x)) + self.assertEqual("MessageElement([b'foo',b'bla']).text", repr(x.text)) + + def test_get_item(self): + x = ldb.MessageElement([b"foo", b"bar"]) + self.assertEqual(b"foo", x[0]) + self.assertEqual(b"bar", x[1]) + self.assertEqual(b"bar", x[-1]) + self.assertRaises(IndexError, lambda: x[45]) + + def test_get_item_text(self): + x = ldb.MessageElement(["foo", "bar"]) + self.assertEqual("foo", x.text[0]) + self.assertEqual("bar", x.text[1]) + self.assertEqual("bar", x.text[-1]) + self.assertRaises(IndexError, lambda: x[45]) + + def test_len(self): + x = ldb.MessageElement([b"foo", b"bar"]) + self.assertEqual(2, len(x)) + + def test_eq(self): + x = ldb.MessageElement([b"foo", b"bar"]) + y = ldb.MessageElement([b"foo", b"bar"]) + self.assertEqual(y, x) + x = ldb.MessageElement([b"foo"]) + self.assertNotEqual(y, x) + y = ldb.MessageElement([b"foo"]) + self.assertEqual(y, x) + + def test_extended(self): + el = ldb.MessageElement([b"456"], ldb.FLAG_MOD_ADD, "bla") + self.assertEqual("MessageElement([b'456'])", repr(el)) + self.assertEqual("MessageElement([b'456']).text", repr(el.text)) + + def test_bad_text(self): + el = ldb.MessageElement(b'\xba\xdd') + self.assertRaises(UnicodeDecodeError, el.text.__getitem__, 0) + + +class ModuleTests(TestCase): + + def setUp(self): + super(ModuleTests, self).setUp() + self.testdir = tempdir() + self.filename = os.path.join(self.testdir, "test.ldb") + self.ldb = ldb.Ldb(self.filename) + + def tearDown(self): + shutil.rmtree(self.testdir) + super(ModuleTests, self).setUp() + + def test_register_module(self): + class ExampleModule: + name = "example" + ldb.register_module(ExampleModule) + + def test_use_module(self): + ops = [] + + class ExampleModule: + name = "bla" + + def __init__(self, ldb, next): + ops.append("init") + self.next = next + + def search(self, *args, **kwargs): + return self.next.search(*args, **kwargs) + + def request(self, *args, **kwargs): + pass + + ldb.register_module(ExampleModule) + l = ldb.Ldb(self.filename) + l.add({"dn": "@MODULES", "@LIST": "bla"}) + self.assertEqual([], ops) + l = ldb.Ldb(self.filename) + self.assertEqual(["init"], ops) + + +class LdbResultTests(LdbBaseTest): + + def setUp(self): + super(LdbResultTests, self).setUp() + self.testdir = tempdir() + self.filename = os.path.join(self.testdir, "test.ldb") + self.l = ldb.Ldb(self.url(), flags=self.flags()) + try: + self.l.add(self.index) + except AttributeError: + pass + self.l.add({"dn": "DC=SAMBA,DC=ORG", "name": b"samba.org", + "objectUUID": b"0123456789abcde0"}) + self.l.add({"dn": "OU=ADMIN,DC=SAMBA,DC=ORG", "name": b"Admins", + "objectUUID": b"0123456789abcde1"}) + self.l.add({"dn": "OU=USERS,DC=SAMBA,DC=ORG", "name": b"Users", + "objectUUID": b"0123456789abcde2"}) + self.l.add({"dn": "OU=OU1,DC=SAMBA,DC=ORG", "name": b"OU #1", + "objectUUID": b"0123456789abcde3"}) + self.l.add({"dn": "OU=OU2,DC=SAMBA,DC=ORG", "name": b"OU #2", + "objectUUID": b"0123456789abcde4"}) + self.l.add({"dn": "OU=OU3,DC=SAMBA,DC=ORG", "name": b"OU #3", + "objectUUID": b"0123456789abcde5"}) + self.l.add({"dn": "OU=OU4,DC=SAMBA,DC=ORG", "name": b"OU #4", + "objectUUID": b"0123456789abcde6"}) + self.l.add({"dn": "OU=OU5,DC=SAMBA,DC=ORG", "name": b"OU #5", + "objectUUID": b"0123456789abcde7"}) + self.l.add({"dn": "OU=OU6,DC=SAMBA,DC=ORG", "name": b"OU #6", + "objectUUID": b"0123456789abcde8"}) + self.l.add({"dn": "OU=OU7,DC=SAMBA,DC=ORG", "name": b"OU #7", + "objectUUID": b"0123456789abcde9"}) + self.l.add({"dn": "OU=OU8,DC=SAMBA,DC=ORG", "name": b"OU #8", + "objectUUID": b"0123456789abcdea"}) + self.l.add({"dn": "OU=OU9,DC=SAMBA,DC=ORG", "name": b"OU #9", + "objectUUID": b"0123456789abcdeb"}) + self.l.add({"dn": "OU=OU10,DC=SAMBA,DC=ORG", "name": b"OU #10", + "objectUUID": b"0123456789abcdec"}) + + def tearDown(self): + shutil.rmtree(self.testdir) + super(LdbResultTests, self).tearDown() + # Ensure the LDB is closed now, so we close the FD + del(self.l) + + def test_return_type(self): + res = self.l.search() + self.assertEqual(str(res), "") + + def test_get_msgs(self): + res = self.l.search() + list = res.msgs + + def test_get_controls(self): + res = self.l.search() + list = res.controls + + def test_get_referals(self): + res = self.l.search() + list = res.referals + + def test_iter_msgs(self): + found = False + for l in self.l.search().msgs: + if str(l.dn) == "OU=OU10,DC=SAMBA,DC=ORG": + found = True + self.assertTrue(found) + + def test_iter_msgs_count(self): + self.assertTrue(self.l.search().count > 0) + # 13 objects has been added to the DC=SAMBA, DC=ORG + self.assertEqual(self.l.search(base="DC=SAMBA,DC=ORG").count, 13) + + def test_iter_controls(self): + res = self.l.search().controls + it = iter(res) + + def test_create_control(self): + self.assertRaises(ValueError, ldb.Control, self.l, "tatayoyo:0") + c = ldb.Control(self.l, "relax:1") + self.assertEqual(c.critical, True) + self.assertEqual(c.oid, "1.3.6.1.4.1.4203.666.5.12") + + def test_iter_refs(self): + res = self.l.search().referals + it = iter(res) + + def test_search_sequence_msgs(self): + found = False + res = self.l.search().msgs + + for i in range(0, len(res)): + l = res[i] + if str(l.dn) == "OU=OU10,DC=SAMBA,DC=ORG": + found = True + self.assertTrue(found) + + def test_search_as_iter(self): + found = False + res = self.l.search() + + for l in res: + if str(l.dn) == "OU=OU10,DC=SAMBA,DC=ORG": + found = True + self.assertTrue(found) + + def test_search_iter(self): + found = False + res = self.l.search_iterator() + + for l in res: + if str(l.dn) == "OU=OU10,DC=SAMBA,DC=ORG": + found = True + self.assertTrue(found) + + # Show that search results can't see into a transaction + + def test_search_against_trans(self): + found11 = False + + (r1, w1) = os.pipe() + + (r2, w2) = os.pipe() + + # For the first element, fork a child that will + # write to the DB + pid = os.fork() + if pid == 0: + # In the child, re-open + del(self.l) + gc.collect() + + child_ldb = ldb.Ldb(self.url(), flags=self.flags()) + # start a transaction + child_ldb.transaction_start() + + # write to it + child_ldb.add({"dn": "OU=OU11,DC=SAMBA,DC=ORG", + "name": b"samba.org", + "objectUUID": b"o123456789acbdef"}) + + os.write(w1, b"added") + + # Now wait for the search to be done + os.read(r2, 6) + + # and commit + try: + child_ldb.transaction_commit() + except ldb.LdbError as err: + # We print this here to see what went wrong in the child + print(err) + os._exit(1) + + os.write(w1, b"transaction") + os._exit(0) + + self.assertEqual(os.read(r1, 5), b"added") + + # This should not turn up until the transaction is concluded + res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_BASE) + self.assertEqual(len(res11), 0) + + os.write(w2, b"search") + + # Now wait for the transaction to be done. This should + # deadlock, but the search doesn't hold a read lock for the + # iterator lifetime currently. + self.assertEqual(os.read(r1, 11), b"transaction") + + # This should now turn up, as the transaction is over + res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_BASE) + self.assertEqual(len(res11), 1) + + self.assertFalse(found11) + + (got_pid, status) = os.waitpid(pid, 0) + self.assertEqual(got_pid, pid) + + def test_search_iter_against_trans(self): + found = False + found11 = False + + # We need to hold this iterator open to hold the all-record + # lock + res = self.l.search_iterator() + + (r1, w1) = os.pipe() + + (r2, w2) = os.pipe() + + # For the first element, with the sequence open (which + # means with ldb locks held), fork a child that will + # write to the DB + pid = os.fork() + if pid == 0: + # In the child, re-open + del(res) + del(self.l) + gc.collect() + + child_ldb = ldb.Ldb(self.url(), flags=self.flags()) + # start a transaction + child_ldb.transaction_start() + + # write to it + child_ldb.add({"dn": "OU=OU11,DC=SAMBA,DC=ORG", + "name": b"samba.org", + "objectUUID": b"o123456789acbdef"}) + + os.write(w1, b"added") + + # Now wait for the search to be done + os.read(r2, 6) + + # and commit + try: + child_ldb.transaction_commit() + except ldb.LdbError as err: + # We print this here to see what went wrong in the child + print(err) + os._exit(1) + + os.write(w1, b"transaction") + os._exit(0) + + self.assertEqual(os.read(r1, 5), b"added") + + # This should not turn up until the transaction is concluded + res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_BASE) + self.assertEqual(len(res11), 0) + + os.write(w2, b"search") + + # allow the transaction to start + time.sleep(1) + + # This should not turn up until the search finishes and + # removed the read lock, but for ldb_tdb that happened as soon + # as we called the first res.next() + res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_BASE) + self.assertEqual(len(res11), 0) + + # These results are all collected at the first next(res) call + for l in res: + if str(l.dn) == "OU=OU10,DC=SAMBA,DC=ORG": + found = True + if str(l.dn) == "OU=OU11,DC=SAMBA,DC=ORG": + found11 = True + + # Now wait for the transaction to be done. + self.assertEqual(os.read(r1, 11), b"transaction") + + # This should now turn up, as the transaction is over and all + # read locks are gone + res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_BASE) + self.assertEqual(len(res11), 1) + + self.assertTrue(found) + self.assertFalse(found11) + + (got_pid, status) = os.waitpid(pid, 0) + self.assertEqual(got_pid, pid) + + +class LdbResultTestsLmdb(LdbResultTests): + + def setUp(self): + if os.environ.get('HAVE_LMDB', '1') == '0': + self.skipTest("No lmdb backend") + self.prefix = MDB_PREFIX + self.index = MDB_INDEX_OBJ + super(LdbResultTestsLmdb, self).setUp() + + def tearDown(self): + super(LdbResultTestsLmdb, self).tearDown() + + +class BadTypeTests(TestCase): + def test_control(self): + l = ldb.Ldb() + self.assertRaises(TypeError, ldb.Control, '', 'relax:1') + self.assertRaises(TypeError, ldb.Control, ldb, 1234) + + def test_modify(self): + l = ldb.Ldb() + dn = ldb.Dn(l, 'a=b') + m = ldb.Message(dn) + self.assertRaises(TypeError, l.modify, '') + self.assertRaises(TypeError, l.modify, m, '') + + def test_add(self): + l = ldb.Ldb() + dn = ldb.Dn(l, 'a=b') + m = ldb.Message(dn) + self.assertRaises(TypeError, l.add, '') + self.assertRaises(TypeError, l.add, m, '') + + def test_delete(self): + l = ldb.Ldb() + dn = ldb.Dn(l, 'a=b') + self.assertRaises(TypeError, l.add, '') + self.assertRaises(TypeError, l.add, dn, '') + + def test_rename(self): + l = ldb.Ldb() + dn = ldb.Dn(l, 'a=b') + self.assertRaises(TypeError, l.add, '', dn) + self.assertRaises(TypeError, l.add, dn, '') + self.assertRaises(TypeError, l.add, dn, dn, '') + + def test_search(self): + l = ldb.Ldb() + self.assertRaises(TypeError, l.search, base=1234) + self.assertRaises(TypeError, l.search, scope='') + self.assertRaises(TypeError, l.search, expression=1234) + self.assertRaises(TypeError, l.search, attrs='') + self.assertRaises(TypeError, l.search, controls='') + + +class VersionTests(TestCase): + + def test_version(self): + self.assertTrue(isinstance(ldb.__version__, str)) + +class NestedTransactionTests(LdbBaseTest): + def setUp(self): + super(NestedTransactionTests, self).setUp() + self.testdir = tempdir() + self.filename = os.path.join(self.testdir, "test.ldb") + self.ldb = ldb.Ldb(self.url(), flags=self.flags()) + self.ldb.add({"dn": "@INDEXLIST", + "@IDXATTR": [b"x", b"y", b"ou"], + "@IDXGUID": [b"objectUUID"], + "@IDX_DN_GUID": [b"GUID"]}) + + super(NestedTransactionTests, self).setUp() + + # + # This test documents that currently ldb does not support true nested + # transactions. + # + # Note: The test is written so that it treats failure as pass. + # It is done this way as standalone ldb builds do not use the samba + # known fail mechanism + # + def test_nested_transactions(self): + + self.ldb.transaction_start() + + self.ldb.add({"dn": "x=x1,dc=samba,dc=org", + "objectUUID": b"0123456789abcde1"}) + res = self.ldb.search(expression="(objectUUID=0123456789abcde1)", + base="dc=samba,dc=org") + self.assertEqual(len(res), 1) + + self.ldb.add({"dn": "x=x2,dc=samba,dc=org", + "objectUUID": b"0123456789abcde2"}) + res = self.ldb.search(expression="(objectUUID=0123456789abcde2)", + base="dc=samba,dc=org") + self.assertEqual(len(res), 1) + + self.ldb.transaction_start() + self.ldb.add({"dn": "x=x3,dc=samba,dc=org", + "objectUUID": b"0123456789abcde3"}) + res = self.ldb.search(expression="(objectUUID=0123456789abcde3)", + base="dc=samba,dc=org") + self.assertEqual(len(res), 1) + self.ldb.transaction_cancel() + # + # Check that we can not see the record added by the cancelled + # transaction. + # Currently this fails as ldb does not support true nested + # transactions, and only the outer commits and cancels have an effect + # + res = self.ldb.search(expression="(objectUUID=0123456789abcde3)", + base="dc=samba,dc=org") + # + # FIXME this test currently passes on a failure, i.e. if nested + # transaction support worked correctly the correct test would + # be. + # self.assertEqual(len(res), 0) + # as the add of objectUUID=0123456789abcde3 would reverted when + # the sub transaction it was nested in was rolled back. + # + # Currently this is not the case so the record is still present. + self.assertEqual(len(res), 1) + + + # Commit the outer transaction + # + self.ldb.transaction_commit() + # + # Now check we can still see the records added in the outer + # transaction. + # + res = self.ldb.search(expression="(objectUUID=0123456789abcde1)", + base="dc=samba,dc=org") + self.assertEqual(len(res), 1) + res = self.ldb.search(expression="(objectUUID=0123456789abcde2)", + base="dc=samba,dc=org") + self.assertEqual(len(res), 1) + # + # And that we can't see the records added by the nested transaction. + # + res = self.ldb.search(expression="(objectUUID=0123456789abcde3)", + base="dc=samba,dc=org") + # FIXME again if nested transactions worked correctly we would not + # see this record. The test should be. + # self.assertEqual(len(res), 0) + self.assertEqual(len(res), 1) + + def tearDown(self): + super(NestedTransactionTests, self).tearDown() + + +class LmdbNestedTransactionTests(NestedTransactionTests): + + def setUp(self): + if os.environ.get('HAVE_LMDB', '1') == '0': + self.skipTest("No lmdb backend") + self.prefix = MDB_PREFIX + self.index = MDB_INDEX_OBJ + super(LmdbNestedTransactionTests, self).setUp() + + def tearDown(self): + super(LmdbNestedTransactionTests, self).tearDown() + + +if __name__ == '__main__': + import unittest + unittest.TestProgram() diff --git a/lib/ldb/tests/python/crash.py b/lib/ldb/tests/python/crash.py new file mode 100644 index 0000000..3283981 --- /dev/null +++ b/lib/ldb/tests/python/crash.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# +# Tests for crashing functions + +import os +from unittest import TestCase +import os +import sys +import traceback + +import ldb + + +def segfault_detector(f): + def wrapper(*args, **kwargs): + pid = os.fork() + if pid == 0: + # child, crashing? + try: + f(*args, **kwargs) + except Exception as e: + traceback.print_exc() + sys.stderr.flush() + sys.stdout.flush() + os._exit(0) + + # parent, waiting + pid2, status = os.waitpid(pid, 0) + if os.WIFSIGNALED(status): + signal = os.WTERMSIG(status) + raise AssertionError("Failed with signal %d" % signal) + + return wrapper + + +class LdbDnCrashTests(TestCase): + @segfault_detector + def test_ldb_dn_explode_crash(self): + for i in range(106, 150): + dn = ldb.Dn(ldb.Ldb(), "a=b%s,c= " % (' ' * i)) + dn.validate() + +if __name__ == '__main__': + import unittest + unittest.TestProgram() diff --git a/lib/ldb/tests/python/index.py b/lib/ldb/tests/python/index.py new file mode 100755 index 0000000..c1da76d --- /dev/null +++ b/lib/ldb/tests/python/index.py @@ -0,0 +1,1454 @@ +#!/usr/bin/env python3 +# +# Tests for truncated index keys +# +# Copyright (C) Andrew Bartlett 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 . +# +"""Tests for truncated index keys + +Databases such as lmdb have a maximum key length, these tests ensure that +ldb behaves correctly in those circumstances. + +""" + +import os +from unittest import TestCase +import sys +import ldb +import shutil + + +TDB_PREFIX = "tdb://" +MDB_PREFIX = "mdb://" + + +def tempdir(): + import tempfile + try: + dir_prefix = os.path.join(os.environ["SELFTEST_PREFIX"], "tmp") + except KeyError: + dir_prefix = None + return tempfile.mkdtemp(dir=dir_prefix) + + +def contains(result, dn): + if result is None: + return False + + for r in result: + if str(r["dn"]) == dn: + return True + return False + + +class LdbBaseTest(TestCase): + def setUp(self): + super(LdbBaseTest, self).setUp() + try: + if self.prefix is None: + self.prefix = TDB_PREFIX + except AttributeError: + self.prefix = TDB_PREFIX + + def tearDown(self): + super(LdbBaseTest, self).tearDown() + + def url(self): + return self.prefix + self.filename + + def flags(self): + if self.prefix == MDB_PREFIX: + return ldb.FLG_NOSYNC + else: + return 0 + + +class MaxIndexKeyLengthTests(LdbBaseTest): + def checkGuids(self, key, guids): + # + # This check relies on the current implementation where the indexes + # are in the same database as the data. + # + # It checks that the index record exists, unless guids is None then + # the record must not exist. And the it contains the expected guid + # entries. + # + # The caller needs to provide the GUID's in the expected order + # + res = self.l.search( + base=key, + scope=ldb.SCOPE_BASE) + if guids is None: + self.assertEqual(len(res), 0) + return + self.assertEqual(len(res), 1) + + # The GUID index format has only one value + index = res[0]["@IDX"][0] + self.assertEqual(len(guids), len(index)) + self.assertEqual(guids, index) + + def tearDown(self): + shutil.rmtree(self.testdir) + super(MaxIndexKeyLengthTests, self).tearDown() + + # Ensure the LDB is closed now, so we close the FD + del(self.l) + + def setUp(self): + super(MaxIndexKeyLengthTests, self).setUp() + self.testdir = tempdir() + self.filename = os.path.join(self.testdir, "key_len_test.ldb") + # Note that the maximum key length is set to 54 + # This accounts for the 4 bytes added by the dn formatting + # a leading dn=, and a trailing zero terminator + # + self.l = ldb.Ldb(self.url(), + options=[ + "modules:rdn_name", + "max_key_len_for_self_test:54"]) + self.l.add({"dn": "@ATTRIBUTES", + "uniqueThing": "UNIQUE_INDEX"}) + self.l.add({"dn": "@INDEXLIST", + "@IDXATTR": [ + b"uniqueThing", + b"notUnique", + b"base64____lt", + b"base64_____eq", + b"base64______gt"], + "@IDXONE": [b"1"], + "@IDXGUID": [b"objectUUID"], + "@IDX_DN_GUID": [b"GUID"]}) + + # Add a value to a unique index that exceeds the maximum key length + # This should be rejected. + def test_add_long_unique_add(self): + try: + self.l.add({"dn": "OU=UNIQUE_MAX_LEN,DC=SAMBA,DC=ORG", + "objectUUID": b"0123456789abcdef", + "uniqueThing": "01234567890123456789012345678901"}) + # index key will be + # "@INDEX:UNIQUETHING:01234567890123456789012345678901" + self.fail("Should have failed on long index key") + + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_CONSTRAINT_VIOLATION) + + # Test that DN's longer the maximum key length can be added + # and that duplicate DN's are rejected correctly + def test_add_long_dn_add(self): + # + # For all entries the DN index key gets truncated to + # @INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA + # + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG", + "objectUUID": b"0123456789abcdef"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcdef") + + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM", + "objectUUID": b"0123456789abcde0"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcde0" + b"0123456789abcdef") + + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV", + "objectUUID": b"0123456789abcde1"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcde0" + b"0123456789abcde1" + b"0123456789abcdef") + + # Key is equal to max length does not get inserted into the truncated + # key namespace + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + "objectUUID": b"0123456789abcde5"}) + self.checkGuids( + "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcde5") + + # This key should not get truncated, as it's one character less than + # max, and will not be in the truncate name space + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXX,DC=SAMBA", + "objectUUID": b"0123456789abcde7"}) + self.checkGuids( + "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcde7") + + try: + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG", + "objectUUID": b"0123456789abcde2"}) + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS) + + try: + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM", + "objectUUID": b"0123456789abcde3"}) + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS) + + try: + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV", + "objectUUID": b"0123456789abcde4"}) + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS) + + try: + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + "objectUUID": b"0123456789abcde6"}) + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS) + + try: + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXX,DC=SAMBA", + "objectUUID": b"0123456789abcde8"}) + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS) + + def test_rename_truncated_dn_keys(self): + # For all entries the DN index key gets truncated to + # 0 1 2 3 4 5 + # 12345678901234567890123456789012345678901234567890 + # @INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG", + "objectUUID": b"0123456789abcdef"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcdef") + + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM", + "objectUUID": b"0123456789abcde0"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcde0" + b"0123456789abcdef") + + # Non conflicting rename, should succeed + self.l.rename("OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG", + "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV") + # Index should be unchanged. + self.checkGuids( + "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcde0" + b"0123456789abcdef") + + # Conflicting rename should fail + try: + self.l.rename("OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM", + "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS) + + def test_delete_truncated_dn_keys(self): + # + # For all entries the DN index key gets truncated to + # 0 1 2 3 4 5 + # 12345678901234567890123456789012345678901234567890 + # @INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA + # + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG", + "objectUUID": b"0123456789abcdef"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcdef") + + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV", + "objectUUID": b"0123456789abcde1"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcde1" + b"0123456789abcdef") + + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + "objectUUID": b"0123456789abcde5"}) + self.checkGuids( + "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcde5") + + # Try to delete a non existent DN with a truncated key + try: + self.l.delete("OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM") + except ldb.LdbError as err: + enum = err.args[0] + self.assertEqual(enum, ldb.ERR_NO_SUCH_OBJECT) + # Ensure that non of the other truncated DN's got deleted + res = self.l.search( + base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG") + self.assertEqual(len(res), 1) + + res = self.l.search( + base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV") + self.assertEqual(len(res), 1) + + # Ensure that the non truncated DN did not get deleted + res = self.l.search( + base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA") + self.assertEqual(len(res), 1) + + # Check the indexes are correct + self.checkGuids( + "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcde1" + b"0123456789abcdef") + self.checkGuids( + "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcde5") + + # delete an existing entry + self.l.delete("OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG") + + # Ensure it got deleted + res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG") + self.assertEqual(len(res), 0) + + # Ensure that non of the other truncated DN's got deleted + res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV") + self.assertEqual(len(res), 1) + + # Ensure the non truncated entry did not get deleted. + res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA") + self.assertEqual(len(res), 1) + + # Check the indexes are correct + self.checkGuids( + "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcde1") + self.checkGuids( + "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcde5") + + # delete an existing entry + self.l.delete("OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV") + + # Ensure it got deleted + res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXX,DC=SAMBA,DC=GOV") + self.assertEqual(len(res), 0) + + # Ensure that non of the non truncated DN's got deleted + res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA") + self.assertEqual(len(res), 1) + # Check the indexes are correct + self.checkGuids( + "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + None) + self.checkGuids( + "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcde5") + + # delete an existing entry + self.l.delete("OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA") + + # Ensure it got deleted + res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBAxxx") + self.assertEqual(len(res), 0) + self.checkGuids( + "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + None) + + def test_search_truncated_dn_keys(self): + # + # For all entries the DN index key gets truncated to + # 0 1 2 3 4 5 + # 12345678901234567890123456789012345678901234567890 + # @INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA + # + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG", + "objectUUID": b"0123456789abcdef"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcdef") + + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV", + "objectUUID": b"0123456789abcde1"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcde1" + b"0123456789abcdef") + + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + "objectUUID": b"0123456789abcde5"}) + self.checkGuids( + "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcde5") + + res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG") + self.assertEqual(len(res), 1) + + res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV") + self.assertEqual(len(res), 1) + + res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA") + self.assertEqual(len(res), 1) + + res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM") + self.assertEqual(len(res), 0) + + res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXX,DC=SAMBA,DC=GOV") + self.assertEqual(len(res), 0) + + # Non existent, key one less than truncation limit + res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXX,DC=SAMBA") + self.assertEqual(len(res), 0) + + def test_search_dn_filter_truncated_dn_keys(self): + # + # For all entries the DN index key gets truncated to + # 0 1 2 3 4 5 + # 12345678901234567890123456789012345678901234567890 + # @INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA + # + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG", + "objectUUID": b"0123456789abcdef"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcdef") + + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV", + "objectUUID": b"0123456789abcde1"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcde1" + b"0123456789abcdef") + + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + "objectUUID": b"0123456789abcde5"}) + self.checkGuids( + "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcde5") + + res = self.l.search( + expression="dn=OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG") + self.assertEqual(len(res), 1) + + res = self.l.search( + expression="dn=OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV") + self.assertEqual(len(res), 1) + + res = self.l.search( + expression="dn=OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA") + self.assertEqual(len(res), 1) + + res = self.l.search( + expression="dn=OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM") + self.assertEqual(len(res), 0) + + res = self.l.search( + expression="dn=OU=A_LONG_DNXXXXXXXXXXXX,DC=SAMBA,DC=GOV") + self.assertEqual(len(res), 0) + + # Non existent, key one less than truncation limit + res = self.l.search( + expression="dn=OU=A_LONG_DNXXXXXXXXXXXXXX,DC=SAMBA") + self.assertEqual(len(res), 0) + + def test_search_one_level_truncated_dn_keys(self): + # + # Except for the base DN's + # all entries the DN index key gets truncated to + # 0 1 2 3 4 5 + # 12345678901234567890123456789012345678901234567890 + # @INDEX:@IDXDN:OU=??,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA + # The base DN-s truncate to + # @INDEX:@IDXDN:OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR + # + self.l.add({"dn": "OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR1", + "objectUUID": b"0123456789abcdef"}) + self.l.add({"dn": "OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2", + "objectUUID": b"0123456789abcd1f"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR", + b"0123456789abcd1f" + b"0123456789abcdef") + + self.l.add({"dn": "OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR1", + "objectUUID": b"0123456789abcde1"}) + self.l.add({"dn": "OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2", + "objectUUID": b"0123456789abcd11"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA", + b"0123456789abcd11" + b"0123456789abcde1") + + self.l.add({"dn": "OU=02,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR1", + "objectUUID": b"0123456789abcde2"}) + self.l.add({"dn": "OU=02,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2", + "objectUUID": b"0123456789abcdf2"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=02,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA", + b"0123456789abcde2" + b"0123456789abcdf2") + + self.l.add({"dn": "OU=03,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR1", + "objectUUID": b"0123456789abcde3"}) + self.l.add({"dn": "OU=03,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2", + "objectUUID": b"0123456789abcd13"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=03,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA", + b"0123456789abcd13" + b"0123456789abcde3") + + # This key is not truncated as it's the max_key_len + self.l.add({"dn": "OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA", + "objectUUID": b"0123456789abcde7"}) + self.checkGuids( + "@INDEX:@IDXDN:OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA", + b"0123456789abcde7") + + res = self.l.search(base="OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR1", + scope=ldb.SCOPE_ONELEVEL) + self.assertEqual(len(res), 3) + self.assertTrue( + contains(res, "OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR1")) + self.assertTrue( + contains(res, "OU=02,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR1")) + self.assertTrue( + contains(res, "OU=03,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR1")) + + res = self.l.search(base="OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2", + scope=ldb.SCOPE_ONELEVEL) + self.assertEqual(len(res), 3) + self.assertTrue( + contains(res, "OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2")) + self.assertTrue( + contains(res, "OU=02,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2")) + self.assertTrue( + contains(res, "OU=03,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2")) + + res = self.l.search(base="OU=A_LONG_DN_ONE_LVLX,DC=SAMBA", + scope=ldb.SCOPE_ONELEVEL) + self.assertEqual(len(res), 1) + self.assertTrue( + contains(res, "OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA")) + + def test_search_sub_tree_truncated_dn_keys(self): + # + # Except for the base DN's + # all entries the DN index key gets truncated to + # 0 1 2 3 4 5 + # 12345678901234567890123456789012345678901234567890 + # @INDEX:@IDXDN:OU=??,OU=A_LONG_DN_SUB_TREE,DC=SAMBA + # The base DN-s truncate to + # @INDEX:@IDXDN:OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR + # + self.l.add({"dn": "OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1", + "objectUUID": b"0123456789abcdef"}) + self.l.add({"dn": "OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2", + "objectUUID": b"0123456789abcde4"}) + self.l.add({"dn": "OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR3", + "objectUUID": b"0123456789abcde8"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR", + b"0123456789abcde4" + b"0123456789abcde8" + b"0123456789abcdef") + + self.l.add({"dn": "OU=01,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1", + "objectUUID": b"0123456789abcde1"}) + self.l.add({"dn": "OU=01,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2", + "objectUUID": b"0123456789abcde5"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=01,OU=A_LONG_DN_SUB_TREE,DC=SAMBA", + b"0123456789abcde1" + b"0123456789abcde5") + + self.l.add({"dn": "OU=02,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1", + "objectUUID": b"0123456789abcde2"}) + self.l.add({"dn": "OU=02,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2", + "objectUUID": b"0123456789abcde6"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=02,OU=A_LONG_DN_SUB_TREE,DC=SAMBA", + b"0123456789abcde2" + b"0123456789abcde6") + + self.l.add({"dn": "OU=03,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1", + "objectUUID": b"0123456789abcde3"}) + + self.l.add({"dn": "OU=03,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2", + "objectUUID": b"0123456789abcde7"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=03,OU=A_LONG_DN_SUB_TREE,DC=SAMBA", + b"0123456789abcde3" + b"0123456789abcde7") + + self.l.add({"dn": "OU=04,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR4", + "objectUUID": b"0123456789abcde9"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=04,OU=A_LONG_DN_SUB_TREE,DC=SAMBA", + b"0123456789abcde9") + + res = self.l.search(base="OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1", + scope=ldb.SCOPE_SUBTREE) + self.assertEqual(len(res), 4) + self.assertTrue( + contains(res, "OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1")) + self.assertTrue( + contains(res, "OU=01,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1")) + self.assertTrue( + contains(res, "OU=02,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1")) + self.assertTrue( + contains(res, "OU=03,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1")) + + res = self.l.search(base="OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2", + scope=ldb.SCOPE_SUBTREE) + self.assertEqual(len(res), 4) + self.assertTrue( + contains(res, "OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2")) + self.assertTrue( + contains(res, "OU=01,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2")) + self.assertTrue( + contains(res, "OU=02,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2")) + self.assertTrue( + contains(res, "OU=03,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2")) + + res = self.l.search(base="OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR3", + scope=ldb.SCOPE_SUBTREE) + self.assertEqual(len(res), 1) + self.assertTrue( + contains(res, "OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR3")) + + res = self.l.search(base="OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR4", + scope=ldb.SCOPE_SUBTREE) + self.assertEqual(len(res), 1) + self.assertTrue( + contains(res, "OU=04,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR4")) + + def test_search_base_truncated_dn_keys(self): + # + # For all entries the DN index key gets truncated to + # 0 1 2 3 4 5 + # 12345678901234567890123456789012345678901234567890 + # @INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA + # + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG", + "objectUUID": b"0123456789abcdef"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcdef") + + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV", + "objectUUID": b"0123456789abcde1"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcde1" + b"0123456789abcdef") + + self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + "objectUUID": b"0123456789abcde5"}) + self.checkGuids( + "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + b"0123456789abcde5") + + res = self.l.search( + base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_BASE) + self.assertEqual(len(res), 1) + + res = self.l.search( + base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV", + scope=ldb.SCOPE_BASE) + self.assertEqual(len(res), 1) + + res = self.l.search( + base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA", + scope=ldb.SCOPE_BASE) + self.assertEqual(len(res), 1) + + res = self.l.search( + base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM", + scope=ldb.SCOPE_BASE) + self.assertEqual(len(res), 0) + + res = self.l.search( + base="OU=A_LONG_DNXXXXXXXXXXXX,DC=SAMBA,DC=GOV", + scope=ldb.SCOPE_BASE) + self.assertEqual(len(res), 0) + + # Non existent, key one less than truncation limit + res = self.l.search( + base="OU=A_LONG_DNXXXXXXXXXXXXXX,DC=SAMBA", + scope=ldb.SCOPE_BASE) + self.assertEqual(len(res), 0) + + # + # Test non unique index searched with truncated keys + # + def test_index_truncated_keys(self): + # 0 1 2 3 4 5 + # 12345678901234567890123456789012345678901234567890 + # @INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + eq_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + gt_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + lt_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + # > than max length and differs in values that will be truncated + gt_max_b = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab" + + # Add two entries with the same value, key length = max so no + # truncation. + self.l.add({"dn": "OU=01,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG", + "notUnique": eq_max, + "objectUUID": b"0123456789abcde0"}) + self.checkGuids( + "@INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + b"0123456789abcde0") + + self.l.add({"dn": "OU=02,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG", + "notUnique": eq_max, + "objectUUID": b"0123456789abcde1"}) + self.checkGuids( + "@INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + b"0123456789abcde0" + b"0123456789abcde1") + + # + # An entry outside the tree + # + self.l.add({"dn": "OU=10,OU=SEARCH_NON_UNIQUE01,DC=SAMBA,DC=ORG", + "notUnique": eq_max, + "objectUUID": b"0123456789abcd11"}) + self.checkGuids( + "@INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + b"0123456789abcd11" + b"0123456789abcde0" + b"0123456789abcde1") + + # Key longer than max so should get truncated to same key as + # the previous two entries + self.l.add({"dn": "OU=03,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG", + "notUnique": gt_max, + "objectUUID": b"0123456789abcde2"}) + # But in the truncated key space + self.checkGuids( + "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + b"0123456789abcde2") + + # Key longer than max so should get truncated to same key as + # the previous entries but differs in the chars after max length + self.l.add({"dn": "OU=23,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG", + "notUnique": gt_max_b, + "objectUUID": b"0123456789abcd22"}) + # But in the truncated key space + self.checkGuids( + "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + b"0123456789abcd22" + b"0123456789abcde2") + # + # An entry outside the tree + # + self.l.add({"dn": "OU=11,OU=SEARCH_NON_UNIQUE01,DC=SAMBA,DC=ORG", + "notUnique": gt_max, + "objectUUID": b"0123456789abcd12"}) + self.checkGuids( + "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + b"0123456789abcd12" + b"0123456789abcd22" + b"0123456789abcde2") + + # Key shorter than max + # + self.l.add({"dn": "OU=04,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG", + "notUnique": lt_max, + "objectUUID": b"0123456789abcde3"}) + self.checkGuids( + "@INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + b"0123456789abcde3") + # + # An entry outside the tree + # + self.l.add({"dn": "OU=12,OU=SEARCH_NON_UNIQUE01,DC=SAMBA,DC=ORG", + "notUnique": lt_max, + "objectUUID": b"0123456789abcd13"}) + self.checkGuids( + "@INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + b"0123456789abcd13" + b"0123456789abcde3") + + # + # search for target is max value not truncated + # should return ou's 01, 02 + # + expression = "(notUnique=" + eq_max.decode('ascii') + ")" + res = self.l.search(base="OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression=expression) + self.assertEqual(len(res), 2) + self.assertTrue( + contains(res, "OU=01,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG")) + self.assertTrue( + contains(res, "OU=02,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG")) + # + # search for target is max value not truncated + # search one level up the tree, scope is ONE_LEVEL + # So should get no matches + # + expression = "(notUnique=" + eq_max.decode('ascii') + ")" + res = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression=expression) + self.assertEqual(len(res), 0) + # + # search for target is max value not truncated + # search one level up the tree, scope is SUBTREE + # So should get 3 matches + # + res = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression=expression) + self.assertEqual(len(res), 3) + self.assertTrue( + contains(res, "OU=01,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG")) + self.assertTrue( + contains(res, "OU=02,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG")) + self.assertTrue( + contains(res, "OU=10,OU=SEARCH_NON_UNIQUE01,DC=SAMBA,DC=ORG")) + # + # search for target is max value + 1 so truncated + # should return ou 23 as it's gt_max_b being searched for + # + expression = "(notUnique=" + gt_max_b.decode('ascii') + ")" + res = self.l.search(base="OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression=expression) + self.assertEqual(len(res), 1) + self.assertTrue( + contains(res, "OU=23,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG")) + + # + # search for target is max value + 1 so truncated + # should return ou 03 as it's gt_max being searched for + # + expression = "(notUnique=" + gt_max.decode('ascii') + ")" + res = self.l.search(base="OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression=expression) + self.assertEqual(len(res), 1) + self.assertTrue( + contains(res, "OU=03,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG")) + + # + # scope one level and one level up one level up should get no matches + # + res = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression=expression) + self.assertEqual(len(res), 0) + # + # scope sub tree and one level up one level up should get 2 matches + # + res = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression=expression) + self.assertEqual(len(res), 2) + self.assertTrue( + contains(res, "OU=03,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG")) + self.assertTrue( + contains(res, "OU=11,OU=SEARCH_NON_UNIQUE01,DC=SAMBA,DC=ORG")) + + # + # search for target is max value - 1 so not truncated + # should return ou 04 + # + expression = "(notUnique=" + lt_max.decode('ascii') + ")" + res = self.l.search(base="OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression=expression) + self.assertEqual(len(res), 1) + self.assertTrue( + contains(res, "OU=04,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG")) + + # + # scope one level and one level up one level up should get no matches + # + res = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression=expression) + self.assertEqual(len(res), 0) + + # + # scope sub tree and one level up one level up should get 2 matches + # + res = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression=expression) + self.assertEqual(len(res), 2) + self.assertTrue( + contains(res, "OU=04,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG")) + self.assertTrue( + contains(res, "OU=12,OU=SEARCH_NON_UNIQUE01,DC=SAMBA,DC=ORG")) + + # + # Test index key truncation for base64 encoded values + # + def test_index_truncated_base64_encoded_keys(self): + value = b"aaaaaaaaaaaaaaaaaaaa\x02" + # base64 encodes to "YWFhYWFhYWFhYWFhYWFhYWFhYWEC" + + # One less than max key length + self.l.add({"dn": "OU=01,OU=BASE64,DC=SAMBA,DC=ORG", + "base64____lt": value, + "objectUUID": b"0123456789abcde0"}) + self.checkGuids( + "@INDEX:BASE64____LT::YWFhYWFhYWFhYWFhYWFhYWFhYWEC", + b"0123456789abcde0") + + # Equal max key length + self.l.add({"dn": "OU=02,OU=BASE64,DC=SAMBA,DC=ORG", + "base64_____eq": value, + "objectUUID": b"0123456789abcde1"}) + self.checkGuids( + "@INDEX:BASE64_____EQ::YWFhYWFhYWFhYWFhYWFhYWFhYWEC", + b"0123456789abcde1") + + # One greater than max key length + self.l.add({"dn": "OU=03,OU=BASE64,DC=SAMBA,DC=ORG", + "base64______gt": value, + "objectUUID": b"0123456789abcde2"}) + self.checkGuids( + "@INDEX#BASE64______GT##YWFhYWFhYWFhYWFhYWFhYWFhYWE", + b"0123456789abcde2") + # + # Test adding to non unique index with identical multivalued index + # attributes + # + + def test_index_multi_valued_identical_keys(self): + # 0 1 2 3 4 5 + # 12345678901234567890123456789012345678901234567890 + # @INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + as_eq_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + bs_eq_max = b"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + + try: + self.l.add({"dn": "OU=01,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG", + "notUnique": [bs_eq_max, as_eq_max, as_eq_max], + "objectUUID": b"0123456789abcde0"}) + self.fail("Exception not thrown") + except ldb.LdbError as e: + code = e.args[0] + self.assertEqual(ldb.ERR_ATTRIBUTE_OR_VALUE_EXISTS, code) + + # + # Test non unique index with multivalued index attributes + # searched with non truncated keys + # + def test_search_index_multi_valued_truncated_keys(self): + # 0 1 2 3 4 5 + # 12345678901234567890123456789012345678901234567890 + # @INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + aa_gt_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ab_gt_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab" + bb_gt_max = b"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + + self.l.add({"dn": "OU=01,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG", + "notUnique": [aa_gt_max, ab_gt_max, bb_gt_max], + "objectUUID": b"0123456789abcde0"}) + self.checkGuids( + "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + b"0123456789abcde0" + b"0123456789abcde0") + self.checkGuids( + "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + b"0123456789abcde0") + + expression = "(notUnique=" + aa_gt_max.decode('ascii') + ")" + res = self.l.search(base="OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression=expression) + self.assertEqual(len(res), 1) + self.assertTrue( + contains(res, "OU=01,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG")) + + expression = "(notUnique=" + ab_gt_max.decode('ascii') + ")" + res = self.l.search(base="OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression=expression) + self.assertEqual(len(res), 1) + self.assertTrue( + contains(res, "OU=01,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG")) + + expression = "(notUnique=" + bb_gt_max.decode('ascii') + ")" + res = self.l.search(base="OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_ONELEVEL, + expression=expression) + self.assertEqual(len(res), 1) + self.assertTrue( + contains(res, "OU=01,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG")) + + # + # Test deletion of records with non unique index with multivalued index + # attributes + # replicate this to test modify with modify flags i.e. DELETE, REPLACE + # + def test_delete_index_multi_valued_truncated_keys(self): + # 0 1 2 3 4 5 + # 12345678901234567890123456789012345678901234567890 + # @INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + aa_gt_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ab_gt_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab" + bb_gt_max = b"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + cc_gt_max = b"cccccccccccccccccccccccccccccccccc" + + self.l.add({"dn": "OU=01,OU=DELETE_NON_UNIQUE,DC=SAMBA,DC=ORG", + "notUnique": [aa_gt_max, ab_gt_max, bb_gt_max], + "objectUUID": b"0123456789abcde0"}) + self.l.add({"dn": "OU=02,OU=DELETE_NON_UNIQUE,DC=SAMBA,DC=ORG", + "notUnique": [aa_gt_max, ab_gt_max, cc_gt_max], + "objectUUID": b"0123456789abcde1"}) + self.checkGuids( + "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + b"0123456789abcde0" + b"0123456789abcde0" + + b"0123456789abcde1" + b"0123456789abcde1") + self.checkGuids( + "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + b"0123456789abcde0") + self.checkGuids( + "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc", + b"0123456789abcde1") + + res = self.l.search( + base="DC=SAMBA,DC=ORG", + expression="(notUnique=" + aa_gt_max.decode("ascii") + ")") + self.assertEqual(2, len(res)) + self.assertTrue( + contains(res, "OU=01,OU=DELETE_NON_UNIQUE,DC=SAMBA,DC=ORG")) + self.assertTrue( + contains(res, "OU=02,OU=DELETE_NON_UNIQUE,DC=SAMBA,DC=ORG")) + + self.l.delete("OU=02,OU=DELETE_NON_UNIQUE,DC=SAMBA,DC=ORG") + self.checkGuids( + "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + b"0123456789abcde0" + b"0123456789abcde0") + self.checkGuids( + "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + b"0123456789abcde0") + self.checkGuids( + "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc", + None) + + self.l.delete("OU=01,OU=DELETE_NON_UNIQUE,DC=SAMBA,DC=ORG") + self.checkGuids( + "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + None) + self.checkGuids( + "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + None) + self.checkGuids( + "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc", + None) + + # + # Test modification of records with non unique index with multivalued index + # attributes + # + def test_modify_index_multi_valued_truncated_keys(self): + # 0 1 2 3 4 5 + # 12345678901234567890123456789012345678901234567890 + # @INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + aa_gt_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ab_gt_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab" + bb_gt_max = b"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + cc_gt_max = b"cccccccccccccccccccccccccccccccccc" + + self.l.add({"dn": "OU=01,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG", + "notUnique": [aa_gt_max, ab_gt_max, bb_gt_max], + "objectUUID": b"0123456789abcde0"}) + self.l.add({"dn": "OU=02,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG", + "notUnique": [aa_gt_max, ab_gt_max, cc_gt_max], + "objectUUID": b"0123456789abcde1"}) + self.checkGuids( + "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + b"0123456789abcde0" + b"0123456789abcde0" + + b"0123456789abcde1" + b"0123456789abcde1") + self.checkGuids( + "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + b"0123456789abcde0") + self.checkGuids( + "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc", + b"0123456789abcde1") + + res = self.l.search( + base="DC=SAMBA,DC=ORG", + expression="(notUnique=" + aa_gt_max.decode("ascii") + ")") + self.assertEqual(2, len(res)) + self.assertTrue( + contains(res, "OU=01,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG")) + self.assertTrue( + contains(res, "OU=02,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG")) + + # + # Modify that does not change the indexed attribute + # + msg = ldb.Message() + msg.dn = ldb.Dn(self.l, "OU=01,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG") + msg["notUnique"] = ldb.MessageElement( + [aa_gt_max, ab_gt_max, bb_gt_max], + ldb.FLAG_MOD_REPLACE, + "notUnique") + self.l.modify(msg) + # + # As the modify is replacing the attribute with the same contents + # there should be no changes to the indexes. + # + self.checkGuids( + "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + b"0123456789abcde0" + b"0123456789abcde0" + + b"0123456789abcde1" + b"0123456789abcde1") + self.checkGuids( + "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + b"0123456789abcde0") + self.checkGuids( + "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc", + b"0123456789abcde1") + + # + # Modify that removes a value from the indexed attribute + # + msg = ldb.Message() + msg.dn = ldb.Dn(self.l, "OU=01,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG") + msg["notUnique"] = ldb.MessageElement( + [aa_gt_max, bb_gt_max], + ldb.FLAG_MOD_REPLACE, + "notUnique") + self.l.modify(msg) + + self.checkGuids( + "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + b"0123456789abcde0" + + b"0123456789abcde1" + b"0123456789abcde1") + self.checkGuids( + "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + b"0123456789abcde0") + self.checkGuids( + "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc", + b"0123456789abcde1") + + # + # Modify that does a constrained delete the indexed attribute + # + msg = ldb.Message() + msg.dn = ldb.Dn(self.l, "OU=02,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG") + msg["notUnique"] = ldb.MessageElement( + [ab_gt_max], + ldb.FLAG_MOD_DELETE, + "notUnique") + self.l.modify(msg) + + self.checkGuids( + "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + b"0123456789abcde0" + b"0123456789abcde1") + self.checkGuids( + "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + b"0123456789abcde0") + self.checkGuids( + "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc", + b"0123456789abcde1") + + # + # Modify that does an unconstrained delete the indexed attribute + # + msg = ldb.Message() + msg.dn = ldb.Dn(self.l, "OU=02,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG") + msg["notUnique"] = ldb.MessageElement( + [], + ldb.FLAG_MOD_DELETE, + "notUnique") + self.l.modify(msg) + + self.checkGuids( + "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + b"0123456789abcde0") + self.checkGuids( + "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + b"0123456789abcde0") + self.checkGuids( + "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc", + None) + + # + # Modify that adds a value to the indexed attribute + # + msg = ldb.Message() + msg.dn = ldb.Dn(self.l, "OU=02,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG") + msg["notUnique"] = ldb.MessageElement( + [cc_gt_max], + ldb.FLAG_MOD_ADD, + "notUnique") + self.l.modify(msg) + + self.checkGuids( + "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + b"0123456789abcde0") + self.checkGuids( + "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + b"0123456789abcde0") + self.checkGuids( + "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc", + b"0123456789abcde1") + + # + # Modify that adds a values to the indexed attribute + # + msg = ldb.Message() + msg.dn = ldb.Dn(self.l, "OU=02,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG") + msg["notUnique"] = ldb.MessageElement( + [aa_gt_max, ab_gt_max], + ldb.FLAG_MOD_ADD, + "notUnique") + self.l.modify(msg) + + self.checkGuids( + "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + b"0123456789abcde0" + + b"0123456789abcde1" + b"0123456789abcde1") + self.checkGuids( + "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + b"0123456789abcde0") + self.checkGuids( + "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc", + b"0123456789abcde1") + + # + # Test Sub tree searches when checkBaseOnSearch is enabled and the + # DN indexes are truncated and collide. + # + def test_check_base_on_search_truncated_dn_keys(self): + # + # Except for the base DN's + # all entries the DN index key gets truncated to + # 0 1 2 3 4 5 + # 12345678901234567890123456789012345678901234567890 + # @INDEX:@IDXDN:OU=??,OU=CHECK_BASE_DN_XXXX,DC=SAMBA + # The base DN-s truncate to + # @INDEX:@IDXDN:OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR + # + checkbaseonsearch = {"dn": "@OPTIONS", + "checkBaseOnSearch": b"TRUE"} + self.l.add(checkbaseonsearch) + + self.l.add({"dn": "OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR1", + "objectUUID": b"0123456789abcdef"}) + self.l.add({"dn": "OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR2", + "objectUUID": b"0123456789abcdee"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR", + b"0123456789abcdee" + b"0123456789abcdef") + + self.l.add({"dn": "OU=01,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR1", + "objectUUID": b"0123456789abcdec"}) + self.l.add({"dn": "OU=01,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR2", + "objectUUID": b"0123456789abcdeb"}) + self.l.add({"dn": "OU=01,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR3", + "objectUUID": b"0123456789abcded"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=01,OU=CHECK_BASE_DN_XXXX,DC=SAMBA", + b"0123456789abcdeb" + b"0123456789abcdec" + b"0123456789abcded") + + self.l.add({"dn": "OU=02,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR1", + "objectUUID": b"0123456789abcde0"}) + self.l.add({"dn": "OU=02,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR2", + "objectUUID": b"0123456789abcde1"}) + self.l.add({"dn": "OU=02,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR3", + "objectUUID": b"0123456789abcde2"}) + self.checkGuids( + "@INDEX#@IDXDN#OU=02,OU=CHECK_BASE_DN_XXXX,DC=SAMBA", + b"0123456789abcde0" + b"0123456789abcde1" + b"0123456789abcde2") + + res = self.l.search(base="OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR1", + scope=ldb.SCOPE_SUBTREE) + self.assertEqual(len(res), 3) + self.assertTrue( + contains(res, "OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR1")) + self.assertTrue( + contains(res, "OU=01,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR1")) + self.assertTrue( + contains(res, "OU=02,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR1")) + + res = self.l.search(base="OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR2", + scope=ldb.SCOPE_SUBTREE) + self.assertEqual(len(res), 3) + self.assertTrue( + contains(res, "OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR2")) + self.assertTrue( + contains(res, "OU=01,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR2")) + self.assertTrue( + contains(res, "OU=02,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR2")) + + try: + res = self.l.search(base="OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR3", + scope=ldb.SCOPE_SUBTREE) + self.fail("Expected exception no thrown") + except ldb.LdbError as e: + code = e.args[0] + self.assertEqual(ldb.ERR_NO_SUCH_OBJECT, code) + + +# Run the index truncation tests against an lmdb backend +class MaxIndexKeyLengthTestsLmdb(MaxIndexKeyLengthTests): + + def setUp(self): + if os.environ.get('HAVE_LMDB', '1') == '0': + self.skipTest("No lmdb backend") + self.prefix = MDB_PREFIX + super(MaxIndexKeyLengthTestsLmdb, self).setUp() + + def tearDown(self): + super(MaxIndexKeyLengthTestsLmdb, self).tearDown() + + +class OrderedIntegerRangeTests(LdbBaseTest): + + def tearDown(self): + shutil.rmtree(self.testdir) + super(OrderedIntegerRangeTests, self).tearDown() + + # Ensure the LDB is closed now, so we close the FD + del(self.l) + + def setUp(self): + super(OrderedIntegerRangeTests, self).setUp() + self.testdir = tempdir() + self.filename = os.path.join(self.testdir, "ordered_integer_test.ldb") + + self.l = ldb.Ldb(self.url(), + options=self.options()) + self.l.add({"dn": "@ATTRIBUTES", + "int64attr": "ORDERED_INTEGER"}) + self.l.add({"dn": "@INDEXLIST", + "@IDXATTR": [b"int64attr"], + "@IDXONE": [b"1"], + "@IDXGUID": [b"objectUUID"], + "@IDX_DN_GUID": [b"GUID"]}) + + def options(self): + if self.prefix == MDB_PREFIX: + return ['modules:rdn_name', + 'disable_full_db_scan_for_self_test:1'] + else: + return ['modules:rdn_name'] + + def test_comparison_expression(self): + int64_max = 2**63-1 + int64_min = -2**63 + test_nums = list(range(-5, 5)) + test_nums += list(range(int64_max-5, int64_max+1)) + test_nums += list(range(int64_min, int64_min+5)) + test_nums = sorted(test_nums) + + for (i, num) in enumerate(test_nums): + ouuid = 0x0123456789abcdef + i + ouuid_s = bytes(('0' + hex(ouuid)[2:]).encode()) + self.l.add({"dn": "OU=COMPTESTOU{},DC=SAMBA,DC=ORG".format(i), + "objectUUID": ouuid_s, + "int64attr": str(num)}) + + def assert_int64_expr(expr, py_expr=None): + res = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(int64attr%s)" % (expr)) + + if not py_expr: + py_expr = expr + expect = [n for n in test_nums if eval(str(n) + py_expr)] + vals = sorted([int(r.get("int64attr")[0]) for r in res]) + self.assertEqual(len(res), len(expect)) + self.assertEqual(set(vals), set(expect)) + self.assertEqual(expect, vals) + + assert_int64_expr(">=-2") + assert_int64_expr("<=2") + assert_int64_expr(">=" + str(int64_min)) + assert_int64_expr("<=" + str(int64_min)) + assert_int64_expr("<=" + str(int64_min+1)) + assert_int64_expr("<=" + str(int64_max)) + assert_int64_expr(">=" + str(int64_max)) + assert_int64_expr(">=" + str(int64_max-1)) + assert_int64_expr("=10", "==10") + + def test_comparison_expression_duplicates(self): + int64_max = 2**63-1 + int64_min = -2**63 + test_nums = list(range(-5, 5)) * 3 + test_nums += list(range(-20, 20, 5)) * 2 + test_nums += list(range(-50, 50, 15)) + test_nums = sorted(test_nums) + + for (i, num) in enumerate(test_nums): + ouuid = 0x0123456789abcdef + i + ouuid_s = bytes(('0' + hex(ouuid)[2:]).encode()) + self.l.add({"dn": "OU=COMPTESTOU{},DC=SAMBA,DC=ORG".format(i), + "objectUUID": ouuid_s, + "int64attr": str(num)}) + + def assert_int64_expr(expr, py_expr=None): + res = self.l.search(base="DC=SAMBA,DC=ORG", + scope=ldb.SCOPE_SUBTREE, + expression="(int64attr%s)" % (expr)) + + if not py_expr: + py_expr = expr + expect = [n for n in test_nums if eval(str(n) + py_expr)] + vals = sorted([int(r.get("int64attr")[0]) for r in res]) + self.assertEqual(len(res), len(expect)) + self.assertEqual(set(vals), set(expect)) + self.assertEqual(expect, vals) + + assert_int64_expr(">=-2") + assert_int64_expr("<=2") + assert_int64_expr(">=" + str(int64_min)) + assert_int64_expr("<=" + str(int64_min)) + assert_int64_expr("<=" + str(int64_min+1)) + assert_int64_expr("<=" + str(int64_max)) + assert_int64_expr(">=" + str(int64_max)) + assert_int64_expr(">=" + str(int64_max-1)) + assert_int64_expr("=-5", "==-5") + assert_int64_expr("=5", "==5") + + +# Run the ordered integer range tests against an lmdb backend +class OrderedIntegerRangeTestsLmdb(OrderedIntegerRangeTests): + + def setUp(self): + if os.environ.get('HAVE_LMDB', '1') == '0': + self.skipTest("No lmdb backend") + self.prefix = MDB_PREFIX + super(OrderedIntegerRangeTestsLmdb, self).setUp() + + def tearDown(self): + super(OrderedIntegerRangeTestsLmdb, self).tearDown() + + +# Run the index truncation tests against an lmdb backend +class RejectSubDBIndex(LdbBaseTest): + + def setUp(self): + if os.environ.get('HAVE_LMDB', '1') == '0': + self.skipTest("No lmdb backend") + self.prefix = MDB_PREFIX + super(RejectSubDBIndex, self).setUp() + self.testdir = tempdir() + self.filename = os.path.join(self.testdir, + "reject_subidx_test.ldb") + self.l = ldb.Ldb(self.url(), + options=[ + "modules:rdn_name"]) + + def tearDown(self): + super(RejectSubDBIndex, self).tearDown() + + def test_try_subdb_index(self): + try: + self.l.add({"dn": "@INDEXLIST", + "@IDX_LMDB_SUBDB": [b"1"], + "@IDXONE": [b"1"], + "@IDXGUID": [b"objectUUID"], + "@IDX_DN_GUID": [b"GUID"], + }) + except ldb.LdbError as e: + code = e.args[0] + string = e.args[1] + self.assertEqual(ldb.ERR_OPERATIONS_ERROR, code) + self.assertIn("sub-database index", string) + + +if __name__ == '__main__': + import unittest + unittest.TestProgram() diff --git a/lib/ldb/tests/python/repack.py b/lib/ldb/tests/python/repack.py new file mode 100644 index 0000000..0844cd2 --- /dev/null +++ b/lib/ldb/tests/python/repack.py @@ -0,0 +1,204 @@ +import os +from unittest import TestCase +import shutil +from subprocess import check_output +import ldb + +TDB_PREFIX = "tdb://" +MDB_PREFIX = "mdb://" + +def tempdir(): + import tempfile + try: + dir_prefix = os.path.join(os.environ["SELFTEST_PREFIX"], "tmp") + except KeyError: + dir_prefix = None + return tempfile.mkdtemp(dir=dir_prefix) + + +# Check enabling and disabling GUID indexing works and that the database is +# repacked at version 2 if GUID indexing is enabled, or version 1 if disabled. +class GUIDIndexAndPackFormatTests(TestCase): + prefix = TDB_PREFIX + + def setup_newdb(self): + self.testdir = tempdir() + self.filename = os.path.join(self.testdir, + "guidpackformattest.ldb") + url = self.prefix + self.filename + self.l = ldb.Ldb(url, options=["modules:"]) + + self.num_recs_added = 0 + + #guidindexpackv1.ldb is a pre-made database packed with version 1 format + #but with GUID indexing enabled, which is not allowed, so Samba should + #repack the database on the first transaction. + def setup_premade_v1_db(self): + db_name = "guidindexpackv1.ldb" + this_file_dir = os.path.dirname(os.path.abspath(__file__)) + db_path = os.path.join(this_file_dir, "../", db_name) + self.testdir = tempdir() + self.filename = os.path.join(self.testdir, db_name) + + shutil.copy(db_path, self.filename) + + url = self.prefix + self.filename + self.l = ldb.Ldb(url, options=["modules:"]) + self.num_recs_added = 10 + + def tearDown(self): + if hasattr(self, 'testdir'): + shutil.rmtree(self.testdir) + + def add_one_rec(self): + ouuid = 0x0123456789abcdef + self.num_recs_added + ouuid_s = '0' + hex(ouuid)[2:] + dn = "OU=GUIDPFTEST{},DC=SAMBA,DC=ORG".format(self.num_recs_added) + rec = {"dn": dn, "objectUUID": ouuid_s, "distinguishedName": dn} + self.l.add(rec) + self.num_recs_added += 1 + + # Turn GUID back into a str for easier comparisons + return rec + + def set_guid_indexing(self, enable=True): + modmsg = ldb.Message() + modmsg.dn = ldb.Dn(self.l, '@INDEXLIST') + + attrs = {"@IDXGUID": [b"objectUUID"], + "@IDX_DN_GUID": [b"GUID"]} + for attr, val in attrs.items(): + replace = ldb.FLAG_MOD_REPLACE + el = val if enable else [] + el = ldb.MessageElement(elements=el, flags=replace, name=attr) + modmsg.add(el) + + self.l.modify(modmsg) + + # Parse out the comments above each record that ldbdump produces + # containing pack format version and KV level key for each record. + # Return all GUID index keys and the set of all unique pack formats. + def ldbdump_guid_keys_pack_formats(self): + dump = check_output(["bin/ldbdump", "-i", self.filename]) + dump = dump.decode("utf-8") + dump = dump.split("\n") + + comments = [s for s in dump if s.startswith("#")] + + guid_key_tag = "# key: GUID=" + guid_keys = {c[len(guid_key_tag):] for c in comments + if c.startswith(guid_key_tag)} + + pack_format_tag = "# pack format: " + pack_formats = {c[len(pack_format_tag):] for c in comments + if c.startswith(pack_format_tag)} + pack_formats = [int(s, 16) for s in pack_formats] + + return guid_keys, pack_formats + + # Put the whole database in a dict so we can easily check the database + # hasn't changed + def get_database(self): + recs = self.l.search(base="", scope=ldb.SCOPE_SUBTREE, expression="") + db = dict() + for r in recs: + dn = str(r.dn) + self.assertNotIn(dn, db) + db[dn] = dict() + for k in r.keys(): + k = str(k) + db[dn][k] = str(r.get(k)) + return db + + # Toggle GUID indexing on and off a few times, and check that when GUID + # indexing is enabled, the database is repacked to pack format V2, and + # when GUID indexing is disabled again, the database is repacked with + # pack format V1. + def toggle_guidindex_check_pack(self): + expect_db = self.get_database() + + for enable in [False, False, True, False, True, True, False]: + pf = ldb.PACKING_FORMAT_V2 if enable else ldb.PACKING_FORMAT + + self.set_guid_indexing(enable=enable) + + guid_keys, pack_formats = self.ldbdump_guid_keys_pack_formats() + num_guid_keys = self.num_recs_added if enable else 0 + self.assertEqual(len(guid_keys), num_guid_keys) + self.assertEqual(pack_formats, [pf]) + self.assertEqual(self.get_database(), expect_db) + + rec = self.add_one_rec() + expect_db[rec['dn']] = rec + + guid_keys, pack_formats = self.ldbdump_guid_keys_pack_formats() + num_guid_keys = self.num_recs_added if enable else 0 + self.assertEqual(len(guid_keys), num_guid_keys) + self.assertEqual(pack_formats, [pf]) + self.assertEqual(self.get_database(), expect_db) + + # Check a newly created database is initially packed at V1, then is + # repacked at V2 when GUID indexing is enabled. + def test_repack(self): + self.setup_newdb() + + guid_keys, pack_formats = self.ldbdump_guid_keys_pack_formats() + self.assertEqual(len(guid_keys), 0) + self.assertEqual(pack_formats, [ldb.PACKING_FORMAT]) + self.assertEqual(self.get_database(), {}) + + self.l.add({"dn": "@ATTRIBUTES"}) + + guid_keys, pack_formats = self.ldbdump_guid_keys_pack_formats() + self.assertEqual(len(guid_keys), 0) + self.assertEqual(pack_formats, [ldb.PACKING_FORMAT]) + self.assertEqual(self.get_database(), {}) + + self.l.add({"dn": "@INDEXLIST", + "@IDXONE": [b"1"], + "@IDXGUID": [b"objectUUID"], + "@IDX_DN_GUID": [b"GUID"]}) + + guid_keys, pack_formats = self.ldbdump_guid_keys_pack_formats() + self.assertEqual(len(guid_keys), 0) + self.assertEqual(pack_formats, [ldb.PACKING_FORMAT_V2]) + self.assertEqual(self.get_database(), {}) + + rec = self.add_one_rec() + expect_db = {rec["dn"]: rec} + + guid_keys, pack_formats = self.ldbdump_guid_keys_pack_formats() + self.assertEqual(len(guid_keys), 1) + self.assertEqual(pack_formats, [ldb.PACKING_FORMAT_V2]) + self.assertEqual(self.get_database(), expect_db) + + self.toggle_guidindex_check_pack() + + # Check a database with V1 format with GUID indexing enabled is repacked + # with version 2 format. + def test_guid_indexed_v1_db(self): + self.setup_premade_v1_db() + + expect_db = self.get_database() + + guid_keys, pack_formats = self.ldbdump_guid_keys_pack_formats() + self.assertEqual(len(guid_keys), self.num_recs_added) + self.assertEqual(pack_formats, [ldb.PACKING_FORMAT]) + self.assertEqual(self.get_database(), expect_db) + + rec = self.add_one_rec() + expect_db[rec['dn']] = rec + + guid_keys, pack_formats = self.ldbdump_guid_keys_pack_formats() + self.assertEqual(len(guid_keys), self.num_recs_added) + self.assertEqual(pack_formats, [ldb.PACKING_FORMAT_V2]) + self.assertEqual(self.get_database(), expect_db) + + self.toggle_guidindex_check_pack() + + +if __name__ == '__main__': + import unittest + + + unittest.TestProgram() diff --git a/lib/ldb/tests/samba4.png b/lib/ldb/tests/samba4.png new file mode 100644 index 0000000..c809688 Binary files /dev/null and b/lib/ldb/tests/samba4.png differ diff --git a/lib/ldb/tests/sample_module.c b/lib/ldb/tests/sample_module.c new file mode 100644 index 0000000..6ba9ed2 --- /dev/null +++ b/lib/ldb/tests/sample_module.c @@ -0,0 +1,122 @@ +/* + Unix SMB/CIFS implementation. + Samba utility functions + Copyright (C) Jelmer Vernooij 2007 + + ** NOTE! The following LGPL license applies to the ldb + ** library. This does NOT imply that all of Samba is released + ** under the LGPL + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see . +*/ + +#include "replace.h" +#include "ldb_module.h" + +static int sample_add_callback(struct ldb_request *down_req, + struct ldb_reply *ares) +{ + struct ldb_request *req = + talloc_get_type_abort(down_req->context, + struct ldb_request); + + if (ares == NULL) { + return ldb_module_done(req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + if (ares->type == LDB_REPLY_REFERRAL) { + return ldb_module_send_referral(req, ares->referral); + } + + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + return ldb_module_done(req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + return ldb_module_done(req, ares->controls, + ares->response, LDB_SUCCESS); +} + +static int sample_add(struct ldb_module *mod, struct ldb_request *req) +{ + struct ldb_context *ldb = ldb_module_get_ctx(mod); + struct ldb_control *control = NULL; + struct ldb_message *msg = NULL; + struct ldb_request *down_req = NULL; + int ret; + + /* check if there's a relax control */ + control = ldb_request_get_control(req, LDB_CONTROL_RELAX_OID); + if (control != NULL) { + return LDB_ERR_UNWILLING_TO_PERFORM; + } + + msg = ldb_msg_copy_shallow(req, req->op.add.message); + if (msg == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_msg_add_fmt(msg, "touchedBy", "sample"); + if (ret != LDB_SUCCESS) { + return ret; + } + + ret = ldb_build_add_req(&down_req, ldb, req, + msg, + req->controls, + req, sample_add_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + + talloc_steal(down_req, msg); + + /* go on with the call chain */ + return ldb_next_request(mod, down_req); +} + +static int sample_modify(struct ldb_module *mod, struct ldb_request *req) +{ + struct ldb_control *control; + + /* check if there's a relax control */ + control = ldb_request_get_control(req, LDB_CONTROL_RELAX_OID); + if (control != NULL) { + return LDB_ERR_UNWILLING_TO_PERFORM; + } + + /* not found go on */ + return ldb_next_request(mod, req); +} + + +static struct ldb_module_ops ldb_sample_module_ops = { + .name = "sample", + .add = sample_add, + .del = sample_modify, + .modify = sample_modify, +}; + +int ldb_sample_init(const char *version) +{ + LDB_MODULE_CHECK_VERSION(version); + return ldb_register_module(&ldb_sample_module_ops); +} diff --git a/lib/ldb/tests/schema-tests/schema-add-test.ldif b/lib/ldb/tests/schema-tests/schema-add-test.ldif new file mode 100644 index 0000000..472ab48 --- /dev/null +++ b/lib/ldb/tests/schema-tests/schema-add-test.ldif @@ -0,0 +1,66 @@ +dn: CN=Users,DC=schema,DC=test +objectClass: top +objectClass: container +cn: Users +description: Default container for upgraded user accounts +instanceType: 4 +whenCreated: 20050116175504.0Z +whenChanged: 20050116175504.0Z +uSNCreated: 1 +uSNChanged: 1 +showInAdvancedViewOnly: FALSE +name: Users +objectGUID: b847056a-9934-d87b-8a1a-99fabe0863c8 +systemFlags: 0x8c000000 +objectCategory: CN=Container,CN=Schema,CN=Configuration,DC=schema,DC=test +isCriticalSystemObject: TRUE +nTSecurityDescriptor: foo + +dn: CN=Administrator,CN=Users,DC=schema,DC=test +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: Administrator +description: Built-in account for administering the computer/domain +instanceType: 4 +whenCreated: 20050116175504.0Z +whenChanged: 20050116175504.0Z +uSNCreated: 1 +memberOf: CN=Group Policy Creator Owners,CN=Users,DC=schema,DC=test +memberOf: CN=Domain Admins,CN=Users,DC=schema,DC=test +memberOf: CN=Enterprise Admins,CN=Users,DC=schema,DC=test +memberOf: CN=Schema Admins,CN=Users,DC=schema,DC=test +memberOf: CN=Administrators,CN=Builtin,DC=schema,DC=test +uSNChanged: 1 +name: Administrator +objectGUID: 6c02f98c-46c6-aa38-5f13-a510cac04e6c +userAccountControl: 0x10200 +badPwdCount: 0 +codePage: 0 +countryCode: 0 +badPasswordTime: 0 +lastLogoff: 0 +lastLogon: 0 +pwdLastSet: 0 +primaryGroupID: 513 +objectSid: S-1-5-21-43662522-77495566-38969261-500 +adminCount: 1 +accountExpires: 9223372036854775807 +logonCount: 0 +sAMAccountName: Administrator +sAMAccountType: 0x30000000 +objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=schema,DC=test +isCriticalSystemObject: TRUE +unicodePwd: samba +nTSecurityDescriptor: foo + +dn: CN=Test,CN=Users,DC=schema,DC=test +objectClass: top +objectClass: test +cn: Test +description: This is a test +objectCategory: CN=Test,CN=Schema,CN=Configuration,DC=schema,DC=test +nTSecurityDescriptor: foo +instanceType: 4 + diff --git a/lib/ldb/tests/schema-tests/schema-mod-test-1.ldif b/lib/ldb/tests/schema-tests/schema-mod-test-1.ldif new file mode 100644 index 0000000..b976724 --- /dev/null +++ b/lib/ldb/tests/schema-tests/schema-mod-test-1.ldif @@ -0,0 +1,5 @@ +dn: CN=Test,CN=Users,DC=schema,DC=test +changetype: modify +replace: description +description: this test must not fail + diff --git a/lib/ldb/tests/schema-tests/schema-mod-test-2.ldif b/lib/ldb/tests/schema-tests/schema-mod-test-2.ldif new file mode 100644 index 0000000..fa193af --- /dev/null +++ b/lib/ldb/tests/schema-tests/schema-mod-test-2.ldif @@ -0,0 +1,5 @@ +dn: CN=Test,CN=Users,DC=schema,DC=test +changetype: modify +delete: description +# this test must not fail + diff --git a/lib/ldb/tests/schema-tests/schema-mod-test-3.ldif b/lib/ldb/tests/schema-tests/schema-mod-test-3.ldif new file mode 100644 index 0000000..8ab7798 --- /dev/null +++ b/lib/ldb/tests/schema-tests/schema-mod-test-3.ldif @@ -0,0 +1,5 @@ +dn: CN=Test,CN=Users,DC=schema,DC=test +changetype: modify +add: description +description: this test must not fail + diff --git a/lib/ldb/tests/schema-tests/schema-mod-test-4.ldif b/lib/ldb/tests/schema-tests/schema-mod-test-4.ldif new file mode 100644 index 0000000..cbf0e60 --- /dev/null +++ b/lib/ldb/tests/schema-tests/schema-mod-test-4.ldif @@ -0,0 +1,5 @@ +dn: CN=Test,CN=Users,DC=schema,DC=test +changetype: modify +add: foo +foo: this test must fail + diff --git a/lib/ldb/tests/schema-tests/schema-mod-test-5.ldif b/lib/ldb/tests/schema-tests/schema-mod-test-5.ldif new file mode 100644 index 0000000..bc64e9e --- /dev/null +++ b/lib/ldb/tests/schema-tests/schema-mod-test-5.ldif @@ -0,0 +1,5 @@ +dn: CN=Test,CN=Users,DC=schema,DC=test +changetype: modify +delete: nTSecurityDescriptor +# this test must fail + diff --git a/lib/ldb/tests/schema-tests/schema.ldif b/lib/ldb/tests/schema-tests/schema.ldif new file mode 100644 index 0000000..4ab1932 --- /dev/null +++ b/lib/ldb/tests/schema-tests/schema.ldif @@ -0,0 +1,100 @@ +dn: @INDEXLIST +@IDXATTR: name +@IDXATTR: sAMAccountName +@IDXATTR: objectSid +@IDXATTR: objectClass +@IDXATTR: member +@IDXATTR: uidNumber +@IDXATTR: gidNumber +@IDXATTR: unixName +@IDXATTR: privilege +@IDXATTR: lDAPDisplayName + +dn: @ATTRIBUTES +realm: CASE_INSENSITIVE +userPrincipalName: CASE_INSENSITIVE +servicePrincipalName: CASE_INSENSITIVE +name: CASE_INSENSITIVE +dn: CASE_INSENSITIVE +sAMAccountName: CASE_INSENSITIVE +objectClass: CASE_INSENSITIVE +unicodePwd: HIDDEN +ntPwdHash: HIDDEN +ntPwdHistory: HIDDEN +lmPwdHash: HIDDEN +lmPwdHistory: HIDDEN +createTimestamp: HIDDEN +modifyTimestamp: HIDDEN + +dn: @MODULES +@LIST: timestamps,schema + +dn: CN=Top,CN=Schema,CN=Configuration,DC=schema,DC=test +objectClass: top +objectClass: classSchema +lDAPDisplayName: top +cn: Top +uSNCreated: 1 +uSNChanged: 1 +subClassOf: top +systemMustContain: objectClass +systemMayContain: structuralObjectClass +systemMayContain: createTimeStamp +systemMayContain: modifyTimeStamp +systemMayContain: creatorsName +systemMayContain: modifiersName +systemMayContain: hasSubordinates +systemMayContain: subschemaSubentry +systemMayContain: collectiveSubentry +systemMayContain: entryUUID +systemMayContain: entryCSN +systemMayContain: namingCSN +systemMayContain: superiorUUID +systemMayContain: contextCSN +systemMayContain: whenCreated +systemMayContain: whenChanged +systemMayContain: uSNCreated +systemMayContain: uSNChanged +systemMayContain: distinguishedName +systemMayContain: name +systemMayContain: cn +systemMayContain: userPassword +systemMayContain: labeledURI + +dn: CN=Class-Schema,CN=Schema,CN=Configuration,DC=schema,DC=test +objectClass: top +objectClass: classSchema +lDAPDisplayName: classSchema +cn: Class-Schema +uSNCreated: 2 +uSNChanged: 2 +lDAPDisplayName: classSchema +subClassOf: top +systemMustContain: cn +systemMustContain: subClassOf +systemMayContain: systemPossSuperiors +systemMayContain: systemOnly +systemMayContain: systemMustContain +systemMayContain: systemMayContain +systemMayContain: systemAuxiliaryClass +systemMayContain: possSuperiors +systemMayContain: mustContain +systemMayContain: mayContain +systemMayContain: lDAPDisplayName +systemMayContain: auxiliaryClass + +dn: CN=Attribute-Schema,CN=Schema,CN=Configuration,DC=schema,DC=test +objectClass: top +objectClass: classSchema +cn: Attribute-Schema +uSNCreated: 3 +uSNChanged: 3 +lDAPDisplayName: attributeSchema +subClassOf: top +systemMustContain: oMSyntax +systemMustContain: lDAPDisplayName +systemMustContain: isSingleValued +systemMustContain: cn +systemMustContain: attributeSyntax +systemMustContain: attributeID + diff --git a/lib/ldb/tests/slapd.conf b/lib/ldb/tests/slapd.conf new file mode 100644 index 0000000..fa2789d --- /dev/null +++ b/lib/ldb/tests/slapd.conf @@ -0,0 +1,26 @@ +loglevel 0 + +include tests/schema/core.schema +include tests/schema/cosine.schema +include tests/schema/inetorgperson.schema +include tests/schema/openldap.schema +include tests/schema/nis.schema + + +pidfile tests/tmp/slapd.pid +argsfile tests/tmp/slapd.args + +access to * by * write + +allow update_anon bind_anon_dn + +include tests/tmp/modules.conf + +defaultsearchbase "o=University of Michigan,c=TEST" + +backend bdb +database bdb +suffix "o=University of Michigan,c=TEST" +directory tests/tmp/db +index objectClass eq +index uid eq diff --git a/lib/ldb/tests/start_slapd.sh b/lib/ldb/tests/start_slapd.sh new file mode 100755 index 0000000..4a4a35e --- /dev/null +++ b/lib/ldb/tests/start_slapd.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +if [ -z "$LDBDIR" ]; then + LDBDIR=$(dirname $0)/.. + export LDBDIR +fi + +mkdir -p $LDBDIR/tests/tmp/db + +# running slapd in the background (with &) means it stays in the same process group, so it can be +# killed by timelimit +slapd -d0 -f $LDBDIR/tests/slapd.conf -h "$($LDBDIR/tests/ldapi_url.sh)" $* & + +sleep 2 diff --git a/lib/ldb/tests/test-attribs.ldif b/lib/ldb/tests/test-attribs.ldif new file mode 100644 index 0000000..79508c4 --- /dev/null +++ b/lib/ldb/tests/test-attribs.ldif @@ -0,0 +1,6 @@ +dn: @ATTRIBUTES +uid: CASE_INSENSITIVE +cn: CASE_INSENSITIVE +ou: CASE_INSENSITIVE +dn: CASE_INSENSITIVE + diff --git a/lib/ldb/tests/test-config.ldif b/lib/ldb/tests/test-config.ldif new file mode 100644 index 0000000..7926a9e --- /dev/null +++ b/lib/ldb/tests/test-config.ldif @@ -0,0 +1,67 @@ +############################## +# global configuration options +dn: cn=Global,cn=Config,cn=Samba +objectclass: globalconfig +LocalConfigCn: cn=%U,cn=Config,cn=Samba +LocalConfigCn;1: cn=%U,cn=Config,cn=Samba +LocalConfigCn;2: cn=%I,cn=Config,cn=Samba +LocalConfigCn;3: cn=%M,cn=Config,cn=Samba + +############# +dn: cn=Protocol,cn=Global,cn=Config,cn=Samba +maxXmit: 7000 + +################################ +dn: cn=Volker,cn=Config,cn=Samba +Workgroup: VNET3 +UnixCharset: UTF8 +Security: user +Interfaces: vmnet* eth* +NetbiosName: blu +GuestAccount: tridge + +################################# +dn: cn=Volker,cn=Config,cn=Samba +Workgroup: VNET3 +UnixCharset: UTF8 +Security: user +Interfaces: vmnet* eth* +NetbiosName: blu +GuestAccount: tridge +Include: cn=%U,cn=MyConfig,cn=Config,cn=Samba + +#### ((objectClass=fileshare)(cn=test)) +############################## +# [test] share +dn: cn=test,cn=Shares,cn=Config,cn=Samba +objectclass: fileshare +cn: test +Comment: a test share +Path: /home/tridge/samba4/prefix/test +ReadOnly: no + +##################################### +# [msdn] a remote proxy share, stored +# on \\msdn\test +dn: cn=msdn,cn=Shares,cn=Config,cn=Samba +objectclass: fileshare +cn: msdn +NtvfsHandler: cifs +ReadOnly: no +_CifsServer: msdn +_CifsUser: administrator +_CifsPassword: penguin +_CifsDomain: winxp +_CifsShare: test + + +############################## +# [VisualC] share +dn: cn=visualc,cn=Shares,cn=Config,cn=Samba +objectclass: fileshare +cn: VisualC +Comment: VisualC development +Path: /home/tridge/VisualC +ReadOnly: no +NtvfsHandler: simple + diff --git a/lib/ldb/tests/test-controls.sh b/lib/ldb/tests/test-controls.sh new file mode 100755 index 0000000..328ed29 --- /dev/null +++ b/lib/ldb/tests/test-controls.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +if [ -n "$TEST_DATA_PREFIX" ]; then + LDB_URL="$TEST_DATA_PREFIX/tdbtest.ldb" +else + LDB_URL="tdbtest.ldb" +fi +export LDB_URL + +PATH=bin:$PATH +export PATH + +rm -f $LDB_URL* + +echo "LDB_URL: $LDB_URL" +cat </dev/null 2>&1 && exit 1 +dn: dc=foobar +dc: foobar +someThing: someThingElse +EOF + +cat </dev/null 2>&1 && exit 1 +dn: dc=bar +changetype: modify +replace someThing +someThing: someThingElseBetter +EOF + +$VALGRIND ldbsearch --controls "bypassoperational:0" >/dev/null 2>&1 || exit 1 diff --git a/lib/ldb/tests/test-default-config.ldif b/lib/ldb/tests/test-default-config.ldif new file mode 100644 index 0000000..87b7bcd --- /dev/null +++ b/lib/ldb/tests/test-default-config.ldif @@ -0,0 +1,17 @@ +############################## +# global configuration options +dn: cn=Global,cn=DefaultConfig,cn=Samba +objectclass: globalconfig +Workgroup: WORKGROUP +UnixCharset: UTF8 +Security: user +NetbiosName: blu +GuestAccount: nobody + +############################## +# [_default_] share +dn: cn=_default_,cn=Shares,cn=DefaultConfig,cn=Samba +objectclass: fileshare +cn: _default_ +Path: /tmp +ReadOnly: yes diff --git a/lib/ldb/tests/test-dup-2.ldif b/lib/ldb/tests/test-dup-2.ldif new file mode 100644 index 0000000..a426101 --- /dev/null +++ b/lib/ldb/tests/test-dup-2.ldif @@ -0,0 +1,6 @@ +dn: cn=Sentinel,ou=Alumni Association,ou=People,o=University of Michigan,c=TEST +objectclass: OpenLDAPperson +cn: Sentinel +sn: USER +uid: USER, Sentinel + diff --git a/lib/ldb/tests/test-dup.ldif b/lib/ldb/tests/test-dup.ldif new file mode 100644 index 0000000..b35420b --- /dev/null +++ b/lib/ldb/tests/test-dup.ldif @@ -0,0 +1,13 @@ +dn: cn=Fred Bassett,ou=Alumni Association,ou=People,o=University of Michigan,c=TEST +objectclass: OpenLDAPperson +cn: Fred Bassett +sn: Bassett +uid: Bassett, Fred + +dn: cn=Sentinel,ou=Alumni Association,ou=People,o=University of Michigan,c=TEST +objectclass: OpenLDAPperson +cn: Sentinel +sn: USER +uid: USER, Sentinel + + diff --git a/lib/ldb/tests/test-extended.sh b/lib/ldb/tests/test-extended.sh new file mode 100755 index 0000000..f4dabd6 --- /dev/null +++ b/lib/ldb/tests/test-extended.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +echo "Running extended search tests" + +mv $LDB_URL $LDB_URL.1 + +cat </dev/null && { + echo "Should have failed to add again - gave $?" + exit 1 +} + +echo "Adding LDIF with one already-existing user again - should fail" +$VALGRIND ldbadd $LDBDIR/tests/test-dup.ldif 2>/dev/null && { + echo "Should have failed to add again - gave $?" + exit 1 +} + +echo "Adding again - should succeed (as previous failed)" +$VALGRIND ldbadd $LDBDIR/tests/test-dup-2.ldif || exit 1 + +echo "Modifying elements" +$VALGRIND ldbmodify $LDBDIR/tests/test-modify.ldif || exit 1 + +echo "Modify LDIF with one un-met constraint - should fail" +$VALGRIND ldbadd $LDBDIR/tests/test-modify-unmet.ldif 2>/dev/null && { + echo "Should have failed to modify - gave $?" + exit 1 +} + +echo "Modify LDIF with after failure of un-met constraint - should also fail" +$VALGRIND ldbadd $LDBDIR/tests/test-modify-unmet-2.ldif 2>/dev/null && { + echo "Should have failed to modify - gave $?" + exit 1 +} + +echo "Showing modified record" +$VALGRIND ldbsearch '(uid=uham)' || exit 1 + +echo "Rename entry with ldbmodify - modrdn" +$VALGRIND ldbmodify $LDBDIR/tests/test-modify-modrdn.ldif || exit 1 + +echo "Rename entry with ldbrename" +OLDDN="cn=Ursula Hampster,ou=Alumni Association,ou=People,o=University of Michigan,c=TEST" +NEWDN="cn=Hampster Ursula,ou=Alumni Association,ou=People,o=University of Michigan,c=TEST" +$VALGRIND ldbrename "$OLDDN" "$NEWDN" || exit 1 + +echo "Showing renamed record" +$VALGRIND ldbsearch '(uid=uham)' || exit 1 + +echo "Starting ldbtest" +$VALGRIND ldbtest --num-records 100 --num-searches 10 || exit 1 + +if [ $LDB_SPECIALS = 1 ]; then + echo "Adding index" + $VALGRIND ldbadd $LDBDIR/tests/test-index.ldif || exit 1 +fi + +echo "Adding bad attributes - should fail" +$VALGRIND ldbadd $LDBDIR/tests/test-wrong_attributes.ldif && { + echo "Should fhave failed - gave $?" + exit 1 +} + +echo "Testing indexed search" +$VALGRIND ldbsearch '(uid=uham)' || exit 1 +$VALGRIND ldbsearch '(&(objectclass=person)(objectclass=person)(objectclass=top))' || exit 1 +$VALGRIND ldbsearch '(&(uid=uham)(uid=uham))' || exit 1 +$VALGRIND ldbsearch '(|(uid=uham)(uid=uham))' || exit 1 +$VALGRIND ldbsearch '(|(uid=uham)(uid=uham)(objectclass=OpenLDAPperson))' || exit 1 +$VALGRIND ldbsearch '(&(uid=uham)(uid=uham)(!(objectclass=xxx)))' || exit 1 +$VALGRIND ldbsearch '(&(objectclass=person)(uid=uham)(!(uid=uhamxx)))' uid \* \+ dn || exit 1 +$VALGRIND ldbsearch '(&(uid=uham)(uid=uha*)(title=*))' uid || exit 1 + +echo "Testing invalid search expression" +$VALGRIND ldbsearch '(&(uid=uham)(title=foo\blah))' uid && exit 1 + +# note that the "((" is treated as an attribute not an expression +# this matches the openldap ldapsearch behaviour of looking for a '=' +# to see if the first argument is an expression or not +$VALGRIND ldbsearch '((' uid || exit 1 +$VALGRIND ldbsearch '(objectclass=)' uid || exit 1 +$VALGRIND ldbsearch -b 'cn=Hampster Ursula,ou=Alumni Association,ou=People,o=University of Michigan,c=TEST' --scope=base "" sn || exit 1 + +echo "Test wildcard match" +$VALGRIND ldbadd $LDBDIR/tests/test-wildcard.ldif || exit 1 +$VALGRIND ldbsearch '(cn=test*multi)' || exit 1 +$VALGRIND ldbsearch '(cn=*test*multi*)' || exit 1 +$VALGRIND ldbsearch '(cn=*test_multi)' || exit 1 +$VALGRIND ldbsearch '(cn=test_multi*)' || exit 1 +$VALGRIND ldbsearch '(cn=test*multi*test*multi)' || exit 1 +$VALGRIND ldbsearch '(cn=test*multi*test*multi*multi_*)' || exit 1 + +echo "Starting ldbtest indexed" +$VALGRIND ldbtest --num-records 100 --num-searches 500 || exit 1 + +echo "Testing one level search" +count=$($VALGRIND ldbsearch -b 'ou=Groups,o=University of Michigan,c=TEST' --scope=one 'objectclass=*' none | grep '^dn' | wc -l) +if [ $count != 3 ]; then + echo returned $count records - expected 3 + exit 1 +fi + +echo "Testing binary file attribute value" +$VALGRIND ldbmodify $LDBDIR/tests/photo.ldif || exit 1 +count=$($VALGRIND ldbsearch '(cn=Hampster Ursula)' jpegPhoto | grep '^dn' | wc -l) +if [ $count != 1 ]; then + echo returned $count records - expected 1 + exit 1 +fi + +echo "*TODO* Testing UTF8 upper lower case searches !!" + +echo "Testing compare" +count=$($VALGRIND ldbsearch '(cn>=t)' cn | grep '^dn' | wc -l) +if [ $count != 1 ]; then + # only "cn: test_multi_test_multi_test_multi" (comes after "t") + # upper-cased words come before "t" - hence excluded + echo returned $count records - expected 1 + exit 1 +fi +$VALGRIND ldbsearch '(cn>t)' cn && exit 1 # strictly greater should not work + +count=$($VALGRIND ldbsearch '(cn<=t)' cn | grep '^dn' | wc -l) +if [ $count != 18 ]; then + # everything except "cn: test_multi_test_multi_test_multi" (comes after "t") + # upper-cased letters come before "t" - hence included + echo returned $count records - expected 18 + exit 1 +fi +$VALGRIND ldbsearch '(cn + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include + +#include +#include "ldb_private.h" + +static void test_ldb_dn_add_child_fmt(void **state) +{ + struct ldb_context *ldb = ldb_init(NULL, NULL); + + struct ldb_dn *dn = ldb_dn_new(ldb, ldb, "dc=samba,dc=org"); + + assert_true(ldb_dn_add_child_fmt(dn, + "DC=X")); + + assert_string_equal("DC=X,dc=samba,dc=org", + ldb_dn_get_linearized(dn)); + + assert_string_equal("DC=X,DC=SAMBA,DC=ORG", + ldb_dn_get_casefold(dn)); + +} + +static void test_ldb_dn_add_child_fmt2(void **state) +{ + struct ldb_context *ldb = ldb_init(NULL, NULL); + + struct ldb_dn *dn = ldb_dn_new(ldb, ldb, "dc=samba,dc=org"); + + assert_true(ldb_dn_add_child_fmt(dn, + "DC=X,DC=Y")); + + assert_string_equal("DC=X,DC=Y,dc=samba,dc=org", + ldb_dn_get_linearized(dn)); + + assert_string_equal("DC=X,DC=Y,DC=SAMBA,DC=ORG", + ldb_dn_get_casefold(dn)); + + assert_int_equal(4, + ldb_dn_get_comp_num(dn)); + +} + +static void test_ldb_dn_add_child_val(void **state) +{ + struct ldb_context *ldb = ldb_init(NULL, NULL); + + struct ldb_dn *dn = ldb_dn_new(ldb, ldb, "dc=samba,dc=org"); + struct ldb_val name = {.data = discard_const("X"), + .length = 1 + }; + + assert_true(ldb_dn_add_child_val(dn, + "DC", name)); + + assert_string_equal("DC=X,dc=samba,dc=org", + ldb_dn_get_linearized(dn)); + + assert_string_equal("DC=X,DC=SAMBA,DC=ORG", + ldb_dn_get_casefold(dn)); + +} + +static void test_ldb_dn_add_child_val2(void **state) +{ + struct ldb_context *ldb = ldb_init(NULL, NULL); + + struct ldb_dn *dn = ldb_dn_new(ldb, ldb, "dc=samba,dc=org"); + + struct ldb_val name = {.data = discard_const("X,DC=Y"), + .length = 6 + }; + + assert_true(ldb_dn_add_child_val(dn, + "DC", name)); + + assert_string_equal("DC=X\\,DC\\3DY,dc=samba,dc=org", + ldb_dn_get_linearized(dn)); + + assert_string_equal("DC=X\\,DC\\3DY,DC=SAMBA,DC=ORG", + ldb_dn_get_casefold(dn)); + + assert_int_equal(3, + ldb_dn_get_comp_num(dn)); + +} + +struct explode_test { + const char *strdn; + int comp_num; + int ext_comp_num; + bool special; + bool invalid; + const char *linearized; + const char *ext_linearized_1; + bool explode_result; +}; + +static int extended_dn_read_ID(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + + /* Allow to check we can cope with validity checks */ + if (in->length != 4) { + return -1; + } + + *out = *in; + out->data = talloc_memdup(mem_ctx, in->data, in->length); + if (out->data == NULL) { + return -1; + } + + return 0; +} + +/* write out (reused for both HEX and clear for now) */ +static int extended_dn_write_ID(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + *out = *in; + + out->data = talloc_memdup(mem_ctx, in->data, in->length); + if (out->data == NULL) { + return -1; + } + return 0; +} + + +static void test_ldb_dn_explode(void **state) +{ + size_t i; + struct ldb_context *ldb = ldb_init(NULL, NULL); + struct explode_test tests[] = { + {"A=B", 1, 0, false, false, "A=B", "A=B", true}, + {"", 0, 0, false, false, "", "", true}, + {" ", -1, -1, false, false, " ", " ", false}, + {"<>", 0, 0, false, false, "", NULL, true}, + {"<", 0, 0, false, false, "", NULL, true}, + {"<><", 0, 0, false, false, "", NULL, true}, + {"<><>", 0, 0, false, false, "", NULL, true}, + {"A=B,C=D", 2, 0, false, false, "A=B,C=D", "A=B,C=D", true}, + {"A=B,C=D", -1, -1, false, false, "", NULL, false}, + {";A=B,C=D", -1, -1, false, false, "A=B,C=D", NULL, false}, + {";A=B,C=D", -1, -1, false, true, "A=B,C=D", NULL, false}, + {";A=B,C=D", 2, 1, false, false, "A=B,C=D", ";A=B,C=D", true}, + {"x=🔥", 1, 0, false, false, "x=🔥", "x=🔥", true}, + {"@FOO", 0, 0, true, false, "@FOO", "@FOO", true}, + }; + + struct ldb_dn_extended_syntax syntax = { + .name = "ID", + .read_fn = extended_dn_read_ID, + .write_clear_fn = extended_dn_write_ID, + .write_hex_fn = extended_dn_write_ID + }; + + ldb_dn_extended_add_syntax(ldb, 0, &syntax); + + for (i = 0; i < ARRAY_SIZE(tests); i++) { + bool result; + const char *linear; + const char *ext_linear; + struct ldb_dn *dn = ldb_dn_new(ldb, ldb, tests[i].strdn); + + /* + * special, invalid, linear, and ext_linear are set before + * explode + */ + linear = ldb_dn_get_linearized(dn); + assert_true((linear == NULL) == (tests[i].linearized == NULL)); + assert_string_equal(linear, + tests[i].linearized); + + ext_linear = ldb_dn_get_extended_linearized(ldb, dn, 1); + assert_true((ext_linear == NULL) == + (tests[i].ext_linearized_1 == NULL)); + + if (tests[i].ext_linearized_1 != NULL) { + assert_string_equal(ext_linear, + tests[i].ext_linearized_1); + } + assert_true(ldb_dn_is_special(dn) == tests[i].special); + assert_true(ldb_dn_is_valid(dn) != tests[i].invalid); + + /* comp nums are set by explode */ + result = ldb_dn_validate(dn); + print_error("test %zu «%s»: res %i lin «%s» ext «%s»\n", + i, tests[i].strdn, result, linear, ext_linear); + + assert_true(result == tests[i].explode_result); + assert_int_equal(ldb_dn_get_comp_num(dn), + tests[i].comp_num); + assert_int_equal(ldb_dn_get_extended_comp_num(dn), + tests[i].ext_comp_num); + } +} + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_ldb_dn_add_child_fmt), + cmocka_unit_test(test_ldb_dn_add_child_fmt2), + cmocka_unit_test(test_ldb_dn_add_child_val), + cmocka_unit_test(test_ldb_dn_add_child_val2), + cmocka_unit_test(test_ldb_dn_explode), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/ldb/tests/test_ldb_qsort.c b/lib/ldb/tests/test_ldb_qsort.c new file mode 100644 index 0000000..663cf0e --- /dev/null +++ b/lib/ldb/tests/test_ldb_qsort.c @@ -0,0 +1,65 @@ +/* + * Unix SMB/CIFS implementation. + * + * Copyright (C) 2018 Andreas Schneider + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include + +#include + +static int cmp_integer(int *a, int *b, void *opaque) +{ + if (a == NULL || b == NULL) { + return 0; + } + + if (*a > *b) { + return 1; + } + + if (*a < *b) { + return -1; + } + + return 0; +} + +static void test_ldb_qsort(void **state) +{ + int a[6] = { 6, 3, 2, 7, 9, 4 }; + + ldb_qsort(a, 6, sizeof(int), NULL, (ldb_qsort_cmp_fn_t)cmp_integer); + + assert_int_equal(a[0], 2); + assert_int_equal(a[1], 3); + assert_int_equal(a[2], 4); + assert_int_equal(a[3], 6); + assert_int_equal(a[4], 7); + assert_int_equal(a[5], 9); +} + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_ldb_qsort), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/ldb/tests/testdata.txt b/lib/ldb/tests/testdata.txt new file mode 100644 index 0000000..dadb9f0 --- /dev/null +++ b/lib/ldb/tests/testdata.txt @@ -0,0 +1,8 @@ +foo=bar5 +(&(|(a=b)(c=d))(e=f)) +(&(|(a=b)(c=d)(g=h))(e=f)) +name=firstname lastname +(&(sid=S-1-2-3)(name = fred bloggs)) +(&(|(a=b)(c=d))(g=f)) +(&(sid=S-1-2-3)(!(name = fred bloggs))) +(&(!(|(a=b)(c=d))(g=f))) diff --git a/lib/ldb/tests/testsearch.txt b/lib/ldb/tests/testsearch.txt new file mode 100644 index 0000000..c573863 --- /dev/null +++ b/lib/ldb/tests/testsearch.txt @@ -0,0 +1,5 @@ +(blah=foo) +(objectclass=person) +(dn=*) +(&(objectclass=person)(objectclass=person)) +(&(objectclass=person)(objectclass=personx)) -- cgit v1.2.3