From 51cdde0ce897c62a0e29653e896e3e6d43585228 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Thu, 20 Oct 2016 18:40:01 +0200
Subject: [PATCH 16/39] 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 <jhrozek@redhat.com>
(cherry picked from commit ce43f710c9638fbbeae077559cd7514370a10c0c)
(cherry picked from commit 74711db46029415cc9590bb0e3f9cc662dac1d0c)
---
src/confdb/confdb.h | 1 +
src/config/SSSDConfig/__init__.py.in | 1 +
src/config/cfg_rules.ini | 1 +
src/config/etc/sssd.api.conf | 1 +
src/man/sssd.conf.5.xml | 45 +++++++++++
src/responder/pam/pamsrv.h | 3 +-
src/responder/pam/pamsrv_cmd.c | 111 ++++++++++++++++++++++++--
src/tests/cmocka/test_pam_srv.c | 149 +++++++++++++++++++++++++++++++++--
8 files changed, 297 insertions(+), 15 deletions(-)
diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index 011792fba..2a1e58184 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 cde196478..381ff9596 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 b6316be8c..ec716b558 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 567d52efe..be24bcea0 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 8b862eb0c..71ace5208 100644
--- a/src/man/sssd.conf.5.xml
+++ b/src/man/sssd.conf.5.xml
@@ -975,6 +975,51 @@ fallback_homedir = /home/%u
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>pam_response_filter (integer)</term>
+ <listitem>
+ <para>
+ 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.
+ </para>
+ <para>
+ 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.
+ </para>
+ <para>
+ Currently the following filters are supported:
+ <variablelist>
+ <varlistentry><term>ENV</term>
+ <listitem><para>Do not sent any environment
+ variables to any service.</para></listitem>
+ </varlistentry>
+ <varlistentry><term>ENV:var_name</term>
+ <listitem><para>Do not sent environment
+ variable var_name to any
+ service.</para></listitem>
+ </varlistentry>
+ <varlistentry><term>ENV:var_name:service</term>
+ <listitem><para>Do not sent environment
+ variable var_name to
+ service.</para></listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ <para>
+ Default: not set
+ </para>
+ <para>
+ Example: ENV:KRB5CCNAME:sudo-i
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term>pam_id_timeout (integer)</term>
<listitem>
diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h
index 8437d082e..75045d039 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 b3690d763..0c2e6941c 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 41d177233..3b8327eb3 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[])
--
2.11.0