From ce43f710c9638fbbeae077559cd7514370a10c0c Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Nov 02 2016 10:30:20 +0000 Subject: PAM: add pam_response_filter option Currently the main use-case for this new option is to not set the KRB5CCNAME environment varible for services like 'sudo-i'. Resolves https://fedorahosted.org/sssd/ticket/2296 Reviewed-by: Jakub Hrozek --- diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h index 011792f..2a1e581 100644 --- a/src/confdb/confdb.h +++ b/src/confdb/confdb.h @@ -115,6 +115,7 @@ #define CONFDB_PAM_FAILED_LOGIN_DELAY "offline_failed_login_delay" #define CONFDB_DEFAULT_PAM_FAILED_LOGIN_DELAY 5 #define CONFDB_PAM_VERBOSITY "pam_verbosity" +#define CONFDB_PAM_RESPONSE_FILTER "pam_response_filter" #define CONFDB_PAM_ID_TIMEOUT "pam_id_timeout" #define CONFDB_PAM_PWD_EXPIRATION_WARNING "pam_pwd_expiration_warning" #define CONFDB_PAM_TRUSTED_USERS "pam_trusted_users" diff --git a/src/config/SSSDConfig/__init__.py.in b/src/config/SSSDConfig/__init__.py.in index cde1964..381ff95 100644 --- a/src/config/SSSDConfig/__init__.py.in +++ b/src/config/SSSDConfig/__init__.py.in @@ -88,6 +88,7 @@ option_strings = { 'offline_failed_login_attempts' : _('How many failed logins attempts are allowed when offline'), 'offline_failed_login_delay' : _('How long (minutes) to deny login after offline_failed_login_attempts has been reached'), 'pam_verbosity' : _('What kind of messages are displayed to the user during authentication'), + 'pam_response_filter' : _('Filter PAM responses send the pam_sss'), 'pam_id_timeout' : _('How many seconds to keep identity information cached for PAM requests'), 'pam_pwd_expiration_warning' : _('How many days before password expiration a warning should be displayed'), 'pam_trusted_users' : _('List of trusted uids or user\'s name'), diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini index b6316be..ec716b5 100644 --- a/src/config/cfg_rules.ini +++ b/src/config/cfg_rules.ini @@ -99,6 +99,7 @@ option = offline_credentials_expiration option = offline_failed_login_attempts option = offline_failed_login_delay option = pam_verbosity +option = pam_response_filter option = pam_id_timeout option = pam_pwd_expiration_warning option = get_domains_timeout diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf index 567d52e..be24bce 100644 --- a/src/config/etc/sssd.api.conf +++ b/src/config/etc/sssd.api.conf @@ -58,6 +58,7 @@ offline_credentials_expiration = int, None, false offline_failed_login_attempts = int, None, false offline_failed_login_delay = int, None, false pam_verbosity = int, None, false +pam_response_filter = str, None, false pam_id_timeout = int, None, false pam_pwd_expiration_warning = int, None, false get_domains_timeout = int, None, false diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml index 8b862eb..71ace52 100644 --- a/src/man/sssd.conf.5.xml +++ b/src/man/sssd.conf.5.xml @@ -975,6 +975,51 @@ fallback_homedir = /home/%u + + + pam_response_filter (integer) + + + A comma separated list of strings which allows to + remove (filter) data send by the PAM responder to + pam_sss PAM module. There are different kind of + responses send to pam_sss e.g. messages displayed to + the user or environment variables which should be + set by pam_sss. + + + While messages already can be controlled with the + help of the pam_verbosity option this option allows + to filter out other kind of responses as well. + + + Currently the following filters are supported: + + ENV + Do not sent any environment + variables to any service. + + ENV:var_name + Do not sent environment + variable var_name to any + service. + + ENV:var_name:service + Do not sent environment + variable var_name to + service. + + + + + Default: not set + + + Example: ENV:KRB5CCNAME:sudo-i + + + + pam_id_timeout (integer) diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h index 8437d08..75045d0 100644 --- a/src/responder/pam/pamsrv.h +++ b/src/responder/pam/pamsrv.h @@ -101,5 +101,6 @@ pam_set_last_online_auth_with_curr_token(struct sss_domain_info *domain, uint64_t value); errno_t filter_responses(struct confdb_ctx *cdb, - struct response_data *resp_list); + struct response_data *resp_list, + struct pam_data *pd); #endif /* __PAMSRV_H__ */ diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c index d2ac2f7..b7a52b3 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -470,14 +470,89 @@ fail: return ret; } +static errno_t filter_responses_env(struct response_data *resp, + struct pam_data *pd, + char * const *pam_filter_opts) +{ + size_t c; + const char *var_name; + size_t var_name_len; + const char *service; + + if (pam_filter_opts == NULL) { + return EOK; + } + + for (c = 0; pam_filter_opts[c] != NULL; c++) { + if (strncmp(pam_filter_opts[c], "ENV", 3) != 0) { + continue; + } + + var_name = NULL; + var_name_len = 0; + service = NULL; + if (pam_filter_opts[c][3] != '\0') { + if (pam_filter_opts[c][3] != ':') { + /* Neither plain ENV nor ENV:, ignored */ + continue; + } + + var_name = pam_filter_opts[c] + 4; + /* check if there is a second ':' in the option and use the following + * data, if any, as service name. */ + service = strchr(var_name, ':'); + if (service == NULL) { + var_name_len = strlen(var_name); + } else { + var_name_len = service - var_name; + + service++; + /* handle empty service name "ENV:var:" */ + if (*service == '\0') { + service = NULL; + } + } + } + /* handle empty var name "ENV:" or "ENV::service" */ + if (var_name_len == 0) { + var_name = NULL; + } + + DEBUG(SSSDBG_TRACE_ALL, + "Found PAM ENV filter for variable [%.*s] and service [%s].\n", + (int) var_name_len, var_name, service); + + if (service != NULL && pd->service != NULL + && strcmp(service, pd->service) != 0) { + /* current service does not match the filter */ + continue; + } + + if (var_name == NULL) { + /* All environment variables should be filtered */ + resp->do_not_send_to_client = true; + continue; + } + + if (resp->len > var_name_len && resp->data[var_name_len] == '=' + && memcmp(resp->data, var_name, var_name_len) == 0) { + resp->do_not_send_to_client = true; + } + } + + return EOK; +} + errno_t filter_responses(struct confdb_ctx *cdb, - struct response_data *resp_list) + struct response_data *resp_list, + struct pam_data *pd) { int ret; struct response_data *resp; uint32_t user_info_type; - int64_t expire_date; - int pam_verbosity; + int64_t expire_date = 0; + int pam_verbosity = DEFAULT_PAM_VERBOSITY; + char **pam_filter_opts = NULL; ret = confdb_get_int(cdb, CONFDB_PAM_CONF_ENTRY, CONFDB_PAM_VERBOSITY, DEFAULT_PAM_VERBOSITY, @@ -488,12 +563,22 @@ errno_t filter_responses(struct confdb_ctx *cdb, pam_verbosity = DEFAULT_PAM_VERBOSITY; } + ret = confdb_get_string_as_list(cdb, pd, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_RESPONSE_FILTER, + &pam_filter_opts); + if (ret != EOK) { + DEBUG(SSSDBG_CONF_SETTINGS, "[%s] not available, not fatal.\n", + CONFDB_PAM_RESPONSE_FILTER); + pam_filter_opts = NULL; + } + resp = resp_list; while(resp != NULL) { if (resp->type == SSS_PAM_USER_INFO) { if (resp->len < sizeof(uint32_t)) { DEBUG(SSSDBG_CRIT_FAILURE, "User info entry is too short.\n"); - return EINVAL; + ret = EINVAL; + goto done; } if (pam_verbosity == PAM_VERBOSITY_NO_MESSAGES) { @@ -511,7 +596,8 @@ errno_t filter_responses(struct confdb_ctx *cdb, DEBUG(SSSDBG_CRIT_FAILURE, "User info offline auth entry is " "too short.\n"); - return EINVAL; + ret = EINVAL; + goto done; } memcpy(&expire_date, resp->data + sizeof(uint32_t), sizeof(int64_t)); @@ -528,6 +614,13 @@ errno_t filter_responses(struct confdb_ctx *cdb, "User info type [%d] not filtered.\n", user_info_type); } + } else if (resp->type == SSS_PAM_ENV_ITEM) { + resp->do_not_send_to_client = false; + ret = filter_responses_env(resp, pd, pam_filter_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "filter_responses_env failed.\n"); + goto done; + } } else if (resp->type & SSS_SERVER_INFO) { resp->do_not_send_to_client = true; } @@ -535,7 +628,11 @@ errno_t filter_responses(struct confdb_ctx *cdb, resp = resp->next; } - return EOK; + ret = EOK; +done: + talloc_free(pam_filter_opts); + + return ret; } static void pam_reply_delay(struct tevent_context *ev, struct tevent_timer *te, @@ -782,7 +879,7 @@ static void pam_reply(struct pam_auth_req *preq) inform_user(pd, pam_account_locked_message); } - ret = filter_responses(pctx->rctx->cdb, pd->resp_list); + ret = filter_responses(pctx->rctx->cdb, pd->resp_list, pd); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "filter_responses failed, not fatal.\n"); } diff --git a/src/tests/cmocka/test_pam_srv.c b/src/tests/cmocka/test_pam_srv.c index 41d1772..3b8327e 100644 --- a/src/tests/cmocka/test_pam_srv.c +++ b/src/tests/cmocka/test_pam_srv.c @@ -1766,9 +1766,11 @@ void test_filter_response(void **state) struct pam_data *pd; uint8_t offline_auth_data[(sizeof(uint32_t) + sizeof(int64_t))]; uint32_t info_type; + char *env; struct sss_test_conf_param pam_params[] = { { CONFDB_PAM_VERBOSITY, "1" }, + { CONFDB_PAM_RESPONSE_FILTER, NULL }, { NULL, NULL }, /* Sentinel */ }; @@ -1778,6 +1780,15 @@ void test_filter_response(void **state) pd = talloc_zero(pam_test_ctx, struct pam_data); assert_non_null(pd); + pd->service = discard_const("MyService"); + + env = talloc_asprintf(pd, "%s=%s", "MyEnv", "abcdef"); + assert_non_null(env); + + ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, + strlen(env) + 1, (uint8_t *) env); + assert_int_equal(ret, EOK); + info_type = SSS_PAM_USER_INFO_OFFLINE_AUTH; memset(offline_auth_data, 0, sizeof(offline_auth_data)); memcpy(offline_auth_data, &info_type, sizeof(uint32_t)); @@ -1785,27 +1796,151 @@ void test_filter_response(void **state) sizeof(offline_auth_data), offline_auth_data); assert_int_equal(ret, EOK); - ret = filter_responses(pam_test_ctx->rctx->cdb, pd->resp_list); + /* pd->resp_list points to the SSS_PAM_USER_INFO and pd->resp_list->next + * to the SSS_PAM_ENV_ITEM message. */ + + + /* Test CONFDB_PAM_VERBOSITY option */ + ret = filter_responses(pam_test_ctx->rctx->cdb, pd->resp_list, pd); assert_int_equal(ret, EOK); assert_true(pd->resp_list->do_not_send_to_client); + assert_false(pd->resp_list->next->do_not_send_to_client); + + /* SSS_PAM_USER_INFO_OFFLINE_AUTH message will only be shown with + * pam_verbosity 2 or above if cache password never expires. */ + pam_params[0].value = "2"; + ret = add_pam_params(pam_params, pam_test_ctx->rctx->cdb); + assert_int_equal(ret, EOK); + + ret = filter_responses(pam_test_ctx->rctx->cdb, pd->resp_list, pd); + assert_int_equal(ret, EOK); + assert_false(pd->resp_list->do_not_send_to_client); + assert_false(pd->resp_list->next->do_not_send_to_client); pam_params[0].value = "0"; ret = add_pam_params(pam_params, pam_test_ctx->rctx->cdb); assert_int_equal(ret, EOK); - ret = filter_responses(pam_test_ctx->rctx->cdb, pd->resp_list); + ret = filter_responses(pam_test_ctx->rctx->cdb, pd->resp_list, pd); assert_int_equal(ret, EOK); assert_true(pd->resp_list->do_not_send_to_client); + assert_false(pd->resp_list->next->do_not_send_to_client); - /* SSS_PAM_USER_INFO_OFFLINE_AUTH message will only be shown with - * pam_verbosity 2 or above if cache password never expires. */ - pam_params[0].value = "2"; + /* Test CONFDB_PAM_RESPONSE_FILTER option */ + pam_params[1].value = "NoSuchOption"; ret = add_pam_params(pam_params, pam_test_ctx->rctx->cdb); assert_int_equal(ret, EOK); - ret = filter_responses(pam_test_ctx->rctx->cdb, pd->resp_list); + ret = filter_responses(pam_test_ctx->rctx->cdb, pd->resp_list, pd); assert_int_equal(ret, EOK); - assert_false(pd->resp_list->do_not_send_to_client); + assert_true(pd->resp_list->do_not_send_to_client); + assert_false(pd->resp_list->next->do_not_send_to_client); + + pam_params[1].value = "ENV"; /* filter all environment variables */ + /* for all services */ + ret = add_pam_params(pam_params, pam_test_ctx->rctx->cdb); + assert_int_equal(ret, EOK); + + ret = filter_responses(pam_test_ctx->rctx->cdb, pd->resp_list, pd); + assert_int_equal(ret, EOK); + assert_true(pd->resp_list->do_not_send_to_client); + assert_true(pd->resp_list->next->do_not_send_to_client); + + pam_params[1].value = "ENV:"; /* filter all environment variables */ + ret = add_pam_params(pam_params, pam_test_ctx->rctx->cdb); + assert_int_equal(ret, EOK); + + ret = filter_responses(pam_test_ctx->rctx->cdb, pd->resp_list, pd); + assert_int_equal(ret, EOK); + assert_true(pd->resp_list->do_not_send_to_client); + assert_true(pd->resp_list->next->do_not_send_to_client); + + pam_params[1].value = "ENV::"; /* filter all environment variables */ + ret = add_pam_params(pam_params, pam_test_ctx->rctx->cdb); + assert_int_equal(ret, EOK); + + ret = filter_responses(pam_test_ctx->rctx->cdb, pd->resp_list, pd); + assert_int_equal(ret, EOK); + assert_true(pd->resp_list->do_not_send_to_client); + assert_true(pd->resp_list->next->do_not_send_to_client); + + pam_params[1].value = "ENV:abc:"; /* variable name does not match */ + ret = add_pam_params(pam_params, pam_test_ctx->rctx->cdb); + assert_int_equal(ret, EOK); + + ret = filter_responses(pam_test_ctx->rctx->cdb, pd->resp_list, pd); + assert_int_equal(ret, EOK); + assert_true(pd->resp_list->do_not_send_to_client); + assert_false(pd->resp_list->next->do_not_send_to_client); + + pam_params[1].value = "ENV:abc:MyService"; /* variable name does not match */ + ret = add_pam_params(pam_params, pam_test_ctx->rctx->cdb); + assert_int_equal(ret, EOK); + + ret = filter_responses(pam_test_ctx->rctx->cdb, pd->resp_list, pd); + assert_int_equal(ret, EOK); + assert_true(pd->resp_list->do_not_send_to_client); + assert_false(pd->resp_list->next->do_not_send_to_client); + + pam_params[1].value = "ENV::abc"; /* service name does not match */ + ret = add_pam_params(pam_params, pam_test_ctx->rctx->cdb); + assert_int_equal(ret, EOK); + + ret = filter_responses(pam_test_ctx->rctx->cdb, pd->resp_list, pd); + assert_int_equal(ret, EOK); + assert_true(pd->resp_list->do_not_send_to_client); + assert_false(pd->resp_list->next->do_not_send_to_client); + + /* service name does not match */ + pam_params[1].value = "ENV:MyEnv:abc"; + ret = add_pam_params(pam_params, pam_test_ctx->rctx->cdb); + assert_int_equal(ret, EOK); + + ret = filter_responses(pam_test_ctx->rctx->cdb, pd->resp_list, pd); + assert_int_equal(ret, EOK); + assert_true(pd->resp_list->do_not_send_to_client); + assert_false(pd->resp_list->next->do_not_send_to_client); + + pam_params[1].value = "ENV:MyEnv"; /* match */ + ret = add_pam_params(pam_params, pam_test_ctx->rctx->cdb); + assert_int_equal(ret, EOK); + + ret = filter_responses(pam_test_ctx->rctx->cdb, pd->resp_list, pd); + assert_int_equal(ret, EOK); + assert_true(pd->resp_list->do_not_send_to_client); + assert_true(pd->resp_list->next->do_not_send_to_client); + + pam_params[1].value = "ENV:MyEnv:"; /* match */ + ret = add_pam_params(pam_params, pam_test_ctx->rctx->cdb); + assert_int_equal(ret, EOK); + + ret = filter_responses(pam_test_ctx->rctx->cdb, pd->resp_list, pd); + assert_int_equal(ret, EOK); + assert_true(pd->resp_list->do_not_send_to_client); + assert_true(pd->resp_list->next->do_not_send_to_client); + + pam_params[1].value = "ENV:MyEnv:MyService"; /* match */ + ret = add_pam_params(pam_params, pam_test_ctx->rctx->cdb); + assert_int_equal(ret, EOK); + + ret = filter_responses(pam_test_ctx->rctx->cdb, pd->resp_list, pd); + assert_int_equal(ret, EOK); + assert_true(pd->resp_list->do_not_send_to_client); + assert_true(pd->resp_list->next->do_not_send_to_client); + + /* multiple rules with a match */ + pam_params[1].value = "ENV:abc:def, " + "ENV:MyEnv:MyService, " + "ENV:stu:xyz"; + ret = add_pam_params(pam_params, pam_test_ctx->rctx->cdb); + assert_int_equal(ret, EOK); + + ret = filter_responses(pam_test_ctx->rctx->cdb, pd->resp_list, pd); + assert_int_equal(ret, EOK); + assert_true(pd->resp_list->do_not_send_to_client); + assert_true(pd->resp_list->next->do_not_send_to_client); + + talloc_free(pd); } int main(int argc, const char *argv[])