From 99909a5a52528c74ff6ecf11c1b9b4097e0573a4 Mon Sep 17 00:00:00 2001 From: Stephen Gallagher Date: Mon, 16 Nov 2009 09:36:06 -0500 Subject: [PATCH 2/2] Add force_lowercase_names option for domains Since POSIX usernames must be case-sensitive, but remote servers do not, it is possible for entries on the server to have different capitalization in different uses. For those systems where this is the case, SSSD will now offer an option to force all user and group names to lowercase, which should eliminate inconsistencies. --- server/Makefile.am | 1 + server/confdb/confdb.c | 7 + server/confdb/confdb.h | 2 + server/config/SSSDConfig.py | 1 + server/config/SSSDConfigTest.py | 2 + server/config/etc/sssd.api.conf | 2 +- server/db/sysdb_ops.c | 69 +++++++++ server/tests/sysdb-tests.c | 310 ++++++++++++++++++++++++++++++++++++++- server/util/memory.c | 22 +++ server/util/util.h | 2 + 10 files changed, 416 insertions(+), 2 deletions(-) diff --git a/server/Makefile.am b/server/Makefile.am index 08c0295..d880d99 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -729,6 +729,7 @@ install-exec-hook: installsssddirs clean-local: cd $(srcdir)/config; $(PYTHON) setup.py clean --all + rm -rf $(builddir)/tests_sysdb CLEANFILES = *.X */*.X */*/*.X diff --git a/server/confdb/confdb.c b/server/confdb/confdb.c index 5dd8669..1fd85d8 100644 --- a/server/confdb/confdb.c +++ b/server/confdb/confdb.c @@ -989,6 +989,13 @@ static int confdb_get_domain_internal(struct confdb_ctx *cdb, goto done; } + ret = get_entry_as_bool(res->msgs[0], &domain->force_lowercase, + CONFDB_DOMAIN_FORCE_LOWERCASE_NAMES, 0); + if(ret != EOK) { + DEBUG(0, ("Invalid value for %s\n", CONFDB_DOMAIN_FORCE_LOWERCASE_NAMES)); + goto done; + } + *_domain = domain; ret = EOK; diff --git a/server/confdb/confdb.h b/server/confdb/confdb.h index 0a64f9e..b5cf12c 100644 --- a/server/confdb/confdb.h +++ b/server/confdb/confdb.h @@ -86,6 +86,7 @@ #define CONFDB_DOMAIN_MPG "magic_private_groups" #define CONFDB_DOMAIN_FQ "use_fully_qualified_names" #define CONFDB_DOMAIN_ENTRY_CACHE_TIMEOUT "entry_cache_timeout" +#define CONFDB_DOMAIN_FORCE_LOWERCASE_NAMES "force_lowercase_names" /* Local Provider */ #define CONFDB_LOCAL_DEFAULT_SHELL "default_shell" @@ -122,6 +123,7 @@ struct sss_domain_info { bool cache_credentials; bool legacy_passwords; + bool force_lowercase; struct sss_domain_info *next; }; diff --git a/server/config/SSSDConfig.py b/server/config/SSSDConfig.py index 1fa6d4c..b0e488a 100644 --- a/server/config/SSSDConfig.py +++ b/server/config/SSSDConfig.py @@ -75,6 +75,7 @@ option_strings = { 'store_legacy_passwords' : _('Store password hashes'), 'use_fully_qualified_names' : _('Display users/groups in fully-qualified form'), 'entry_cache_timeout' : _('Entry cache timeout length (seconds)'), + 'force_lowercase_names' : _('Force user and group names in the cache to be lowercase'), # [provider/ipa] 'ipa_domain' : _('IPA domain'), diff --git a/server/config/SSSDConfigTest.py b/server/config/SSSDConfigTest.py index 944f86f..4b6569e 100644 --- a/server/config/SSSDConfigTest.py +++ b/server/config/SSSDConfigTest.py @@ -412,6 +412,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase): 'cache_credentials', 'store_legacy_passwords', 'use_fully_qualified_names', + 'force_lowercase_names', 'entry_cache_timeout', 'id_provider', 'auth_provider', @@ -631,6 +632,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase): 'cache_credentials', 'store_legacy_passwords', 'use_fully_qualified_names', + 'force_lowercase_names', 'entry_cache_timeout', 'id_provider', 'auth_provider', diff --git a/server/config/etc/sssd.api.conf b/server/config/etc/sssd.api.conf index e8b266b..2f5b8db 100644 --- a/server/config/etc/sssd.api.conf +++ b/server/config/etc/sssd.api.conf @@ -50,4 +50,4 @@ cache_credentials = bool, None, false store_legacy_passwords = bool, None, false use_fully_qualified_names = bool, None, false entry_cache_timeout = int, None - +force_lowercase_names = bool, None, false diff --git a/server/db/sysdb_ops.c b/server/db/sysdb_ops.c index 7e232a4..7d3e8d1 100644 --- a/server/db/sysdb_ops.c +++ b/server/db/sysdb_ops.c @@ -1737,6 +1737,10 @@ struct tevent_req *sysdb_add_user_send(TALLOC_CTX *mem_ctx, state->attrs = attrs; state->cache_timeout = cache_timeout; + if (domain->force_lowercase) { + state->name = make_lowercase(state, name); + } + if (handle->ctx->mpg) { if (gid != 0) { DEBUG(0, ("Cannot add user with arbitrary GID in MPG domain!\n")); @@ -2182,6 +2186,10 @@ struct tevent_req *sysdb_add_group_send(TALLOC_CTX *mem_ctx, state->gid = gid; state->attrs = attrs; + if (domain->force_lowercase) { + state->name = make_lowercase(state, name); + } + if (domain->id_max != 0 && gid != 0 && (gid < domain->id_min || gid > domain->id_max)) { DEBUG(2, ("Supplied gid [%d] is not in the allowed range [%d-%d].\n", @@ -2488,6 +2496,13 @@ struct tevent_req *sysdb_mod_group_member_send(TALLOC_CTX *mem_ctx, ERROR_OUT(ret, EINVAL, fail); } + if (handle->ctx->domain->force_lowercase) { + dn = make_lowercase(state, dn); + if(!dn) { + ERROR_OUT(ret, ENOMEM, fail); + } + } + ret = ldb_msg_add_fmt(msg, SYSDB_MEMBER, "%s", dn); if (ret != LDB_SUCCESS) { ERROR_OUT(ret, EINVAL, fail); @@ -2579,6 +2594,10 @@ struct tevent_req *sysdb_store_user_send(TALLOC_CTX *mem_ctx, state->attrs = attrs; state->cache_timeout = cache_timeout; + if(domain->force_lowercase) { + state->name = make_lowercase(state, name); + } + if (pwd && (domain->legacy_passwords || !*pwd)) { ret = sysdb_attrs_add_string(state->attrs, SYSDB_PWD, pwd); if (ret) goto fail; @@ -2805,6 +2824,9 @@ struct tevent_req *sysdb_store_group_send(TALLOC_CTX *mem_ctx, static const char *src_attrs[] = { SYSDB_NAME, SYSDB_GIDNUM, SYSDB_ORIG_MODSTAMP, NULL }; int ret; + uint32_t num_users; + uint32_t num_groups; + uint32_t i; req = tevent_req_create(mem_ctx, &state, struct sysdb_store_group_state); if (!req) return NULL; @@ -2819,6 +2841,53 @@ struct tevent_req *sysdb_store_group_send(TALLOC_CTX *mem_ctx, state->attrs = attrs; state->cache_timeout = cache_timeout; + if (domain->force_lowercase) { + state->name = make_lowercase(state, name); + + /* Lower-case the member names as well */ + if (member_users && member_users[0] != NULL) { + num_users = 0; + while(member_users[num_users] != NULL) { + num_users++; + } + + state->member_users = talloc_array(state, const char *, + num_users+1); + if (!state->member_users) { + ERROR_OUT(ret, ENOMEM, fail); + } + + i = 0; + while (i < num_users) { + state->member_users[i] = make_lowercase(state->member_users, + member_users[i]); + i++; + } + state->member_users[i] = NULL; + } + + if (member_groups && member_groups[0] != NULL) { + num_groups = 0; + while(member_groups[num_groups] != NULL) { + num_groups++; + } + + state->member_groups = talloc_array(state, const char *, + num_groups+1); + if (!state->member_groups) { + ERROR_OUT(ret, ENOMEM, fail); + } + + i = 0; + while (i < num_groups) { + state->member_groups[i] = make_lowercase(state->member_groups, + member_groups[i]); + i++; + } + state->member_groups[i] = NULL; + } + } + subreq = sysdb_search_group_by_name_send(state, ev, NULL, handle, domain, name, src_attrs); if (!subreq) { diff --git a/server/tests/sysdb-tests.c b/server/tests/sysdb-tests.c index f1d3ae4..2e22f14 100644 --- a/server/tests/sysdb-tests.c +++ b/server/tests/sysdb-tests.c @@ -155,6 +155,7 @@ struct test_data { uid_t uid; gid_t gid; const char *shell; + const char *member; bool finished; int error; @@ -616,6 +617,39 @@ static void test_getgrent(void *pvt, int error, struct ldb_result *res) } } +static void test_get_group_member(void *pvt, int error, struct ldb_result *res) +{ + struct test_data *data = talloc_get_type(pvt, struct test_data); + data->finished = true; + + if (error != EOK) { + data->error = error; + return; + } + + switch (res->count) { + case 0: + data->error = ENOENT; + break; + + case 1: + data->error = EOK; + data->gid = ldb_msg_find_attr_as_uint(res->msgs[0], SYSDB_GIDNUM, 0); + data->member = NULL; + break; + + default: + /* The first message is always the group, subsequent messages + * are the members. In these tests, we never have more than + * one member. + */ + data->gid = ldb_msg_find_attr_as_uint(res->msgs[0], SYSDB_GIDNUM, 0); + data->member = ldb_msg_find_attr_as_string(res->msgs[1], "name", NULL); + data->error = EOK; + break; + } +} + static void test_getgrgid(void *pvt, int error, struct ldb_result *res) { struct test_data *data = talloc_get_type(pvt, struct test_data); @@ -777,7 +811,12 @@ static void test_add_group_member(struct tevent_req *req) return test_return(data, ret); } - username = talloc_asprintf(data, "testuser%d", data->uid); + if (data->ctx->domain->force_lowercase) { + username = talloc_asprintf(data, "TESTUSER%d", data->uid); + } + else { + username = talloc_asprintf(data, "testuser%d", data->uid); + } if (username == NULL) { test_return(data, ENOMEM); } @@ -1357,6 +1396,49 @@ START_TEST (test_sysdb_add_user) } END_TEST +START_TEST (test_sysdb_add_user_force_lc) +{ + struct sysdb_test_ctx *test_ctx; + struct test_data *data; + struct tevent_req *subreq; + int ret; + + /* Setup */ + ret = setup_sysdb_tests(&test_ctx); + if (ret != EOK) { + fail("Could not set up the test"); + return; + } + + test_ctx->domain->force_lowercase = true; + + data = talloc_zero(test_ctx, struct test_data); + data->ctx = test_ctx; + data->ev = test_ctx->ev; + data->uid = _i; + data->gid = _i; + data->username = talloc_asprintf(data, "TESTUSER%d", _i); + + subreq = sysdb_transaction_send(data, data->ev, test_ctx->sysdb); + if (!subreq) { + ret = ENOMEM; + } + + if (ret == EOK) { + tevent_req_set_callback(subreq, test_add_user, data); + + ret = test_loop(data); + } + + + fail_if(ret != EOK, "Could not add user %s", data->username); + + test_ctx->domain->force_lowercase = false; + + talloc_free(test_ctx); +} +END_TEST + START_TEST (test_sysdb_add_group) { struct sysdb_test_ctx *test_ctx; @@ -1394,6 +1476,48 @@ START_TEST (test_sysdb_add_group) } END_TEST +START_TEST (test_sysdb_add_group_force_lc) +{ + struct sysdb_test_ctx *test_ctx; + struct test_data *data; + struct tevent_req *subreq; + int ret; + + /* Setup */ + ret = setup_sysdb_tests(&test_ctx); + if (ret != EOK) { + fail("Could not set up the test"); + return; + } + + test_ctx->domain->force_lowercase = true; + + data = talloc_zero(test_ctx, struct test_data); + data->ctx = test_ctx; + data->ev = test_ctx->ev; + data->uid = _i; + data->gid = _i; + data->groupname = talloc_asprintf(data, "TESTGROUP%d", _i); + + subreq = sysdb_transaction_send(data, data->ev, test_ctx->sysdb); + if (!subreq) { + ret = ENOMEM; + } + + if (ret == EOK) { + tevent_req_set_callback(subreq, test_add_group, data); + + ret = test_loop(data); + } + + fail_if(ret != EOK, "Could not add group %s", data->groupname); + + test_ctx->domain->force_lowercase = false; + + talloc_free(test_ctx); +} +END_TEST + START_TEST (test_sysdb_getpwnam) { struct sysdb_test_ctx *test_ctx; @@ -1453,6 +1577,69 @@ done: } END_TEST +START_TEST (test_sysdb_getpwnam_force_lc) +{ + struct sysdb_test_ctx *test_ctx; + struct test_data *data; + struct test_data *data_uc; + int ret; + + /* Setup */ + ret = setup_sysdb_tests(&test_ctx); + if (ret != EOK) { + fail("Could not set up the test"); + return; + } + + test_ctx->domain->force_lowercase = true; + + data = talloc_zero(test_ctx, struct test_data); + data->ctx = test_ctx; + data->username = talloc_asprintf(data, "testuser%d", _i); + + ret = sysdb_getpwnam(test_ctx, + test_ctx->sysdb, + data->ctx->domain, + data->username, + test_getpwent, + data); + if (ret == EOK) { + ret = test_loop(data); + } + + if (ret) { + fail("sysdb_getpwnam failed for username %s (%d: %s)", + data->username, ret, strerror(ret)); + goto done; + } + fail_unless(data->uid == _i, + "Did not find the expected UID"); + + /* Search for the user with the wrong case */ + data_uc = talloc_zero(test_ctx, struct test_data); + data_uc->ctx = test_ctx; + data_uc->username = talloc_asprintf(data_uc, "TESTUSER%d", _i); + + ret = sysdb_getpwnam(test_ctx, + test_ctx->sysdb, + data_uc->ctx->domain, + data_uc->username, + test_getpwent, + data_uc); + if (ret == EOK) { + ret = test_loop(data_uc); + } + + fail_unless(ret == ENOENT, + "The upper-case username search should fail. "); + + test_ctx->domain->force_lowercase = false; + +done: + talloc_free(test_ctx); +} +END_TEST + START_TEST (test_sysdb_getgrnam) { struct sysdb_test_ctx *test_ctx; @@ -1512,6 +1699,54 @@ done: } END_TEST +START_TEST (test_sysdb_verify_group_member) +{ + struct sysdb_test_ctx *test_ctx; + struct test_data *data; + int ret; + + /* Setup */ + ret = setup_sysdb_tests(&test_ctx); + if (ret != EOK) { + fail("Could not set up the test"); + return; + } + + data = talloc_zero(test_ctx, struct test_data); + data->ctx = test_ctx; + data->groupname = talloc_asprintf(data, "testgroup%d", _i); + data->username = talloc_asprintf(data, "testuser%d", _i-1000); + + ret = sysdb_getgrnam(test_ctx, + test_ctx->sysdb, + data->ctx->domain, + data->groupname, + test_get_group_member, + data); + if (ret == EOK) { + ret = test_loop(data); + } + + if (ret) { + fail("sysdb_getgrnam failed for groupname %s (%d: %s)", + data->groupname, ret, strerror(ret)); + goto done; + } + fail_unless(data->gid == _i, + "Did not find the expected GID (found %d expected %d)", + data->gid, _i); + + fail_if(data->member == NULL, "No members returned"); + fail_unless(strcmp(data->member, data->username) == 0, + "Member username did not match. " + "Got [%s], expected [%s]", + data->member, data->username); + +done: + talloc_free(test_ctx); +} +END_TEST + START_TEST (test_sysdb_getgrgid) { struct sysdb_test_ctx *test_ctx; @@ -1802,6 +2037,46 @@ START_TEST (test_sysdb_add_group_member) } END_TEST +START_TEST (test_sysdb_add_group_member_force_lc) +{ + struct sysdb_test_ctx *test_ctx; + struct test_data *data; + struct tevent_req *req; + int ret; + + /* Setup */ + ret = setup_sysdb_tests(&test_ctx); + if (ret != EOK) { + fail("Could not set up the test"); + return; + } + + test_ctx->domain->force_lowercase = 1; + + data = talloc_zero(test_ctx, struct test_data); + data->ctx = test_ctx; + data->ev = test_ctx->ev; + data->groupname = talloc_asprintf(data, "testgroup%d", _i); + data->uid = _i - 1000; /* the UID of user to add */ + + req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb); + if (!req) { + ret = ENOMEM; + } + + if (ret == EOK) { + tevent_req_set_callback(req, test_add_group_member, data); + + ret = test_loop(data); + } + + fail_if(ret != EOK, "Could not modify group %s", data->groupname); + + test_ctx->domain->force_lowercase = 0; + talloc_free(test_ctx); +} +END_TEST + START_TEST (test_sysdb_remove_group_member) { struct sysdb_test_ctx *test_ctx; @@ -2457,6 +2732,9 @@ Suite *create_sysdb_suite(void) /* Add some members to the groups */ tcase_add_loop_test(tc_sysdb, test_sysdb_add_group_member, 28010, 28020); + /* Verify the group memberships */ + tcase_add_loop_test(tc_sysdb, test_sysdb_verify_group_member, 28010, 28020); + /* ASQ search test */ tcase_add_loop_test(tc_sysdb, test_sysdb_prepare_asq_test_user, 28011, 28020); tcase_add_test(tc_sysdb, test_sysdb_asq_search); @@ -2491,6 +2769,36 @@ Suite *create_sysdb_suite(void) tcase_add_test(tc_sysdb, test_sysdb_attrs_replace_name); + + /* ==== Force lowercase name tests ==== */ + + /* Create new users */ + tcase_add_loop_test(tc_sysdb, test_sysdb_add_user_force_lc,27050,27060); + + /* Verify the users were added */ + tcase_add_loop_test(tc_sysdb, test_sysdb_getpwnam, 27050, 27060); + + /* Create new groups */ + tcase_add_loop_test(tc_sysdb, test_sysdb_add_group_force_lc, 28050, 28060); + + /* Verify the groups were added */ + tcase_add_loop_test(tc_sysdb, test_sysdb_getgrnam, 28050, 28060); + + /* Add some group members */ + tcase_add_loop_test(tc_sysdb, test_sysdb_add_group_member_force_lc, 28050, 28060); + + /* Verify group members */ + tcase_add_loop_test(tc_sysdb, test_sysdb_verify_group_member, 28050, 28060); + + /* Remove group members */ + tcase_add_loop_test(tc_sysdb, test_sysdb_remove_group_member, 28050, 28060); + + /* Remove groups */ + tcase_add_loop_test(tc_sysdb, test_sysdb_remove_local_group, 28050, 28060); + + /* Remove users */ + tcase_add_loop_test(tc_sysdb, test_sysdb_remove_local_user, 27050, 27060); + /* Add all test cases to the test suite */ suite_add_tcase(s, tc_sysdb); diff --git a/server/util/memory.c b/server/util/memory.c index a2c8b54..9a8a241 100644 --- a/server/util/memory.c +++ b/server/util/memory.c @@ -18,6 +18,7 @@ along with this program. If not, see . */ +#include #include "talloc.h" #include "util/util.h" @@ -65,3 +66,24 @@ void *sss_mem_attach(TALLOC_CTX *mem_ctx, return h; } + +const char *make_lowercase(TALLOC_CTX *mem_ctx, const char *str) +{ + int len, i; + char *retval; + + len = strlen(str); + retval = talloc_array(mem_ctx, char, len+1); + if (!retval) { + return NULL; + } + + i = 0; + while (i < len) { + retval[i] = tolower(str[i]); + i++; + } + retval[len] = '\0'; + + return (const char *)retval; +} diff --git a/server/util/util.h b/server/util/util.h index 661bceb..d5fdac2 100644 --- a/server/util/util.h +++ b/server/util/util.h @@ -193,6 +193,8 @@ void *sss_mem_attach(TALLOC_CTX *mem_ctx, int password_destructor(void *memctx); +const char *make_lowercase(TALLOC_CTX *mem_ctx, const char *str); + /* from usertools.c */ char *get_username_from_uid(TALLOC_CTX *mem_ctx, uid_t uid); -- 1.6.2.5