/* * 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(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(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(el->values[0].data, "test_mod_cn"); assert_string_equal(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(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(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(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(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 deliberatly 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 opend 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 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); }