URL: https://github.com/SSSD/sssd/pull/792 Author: sumit-bose Title: #792: PAM: add initial prompting configuration Action: synchronized
To pull the PR as Git branch: git remote add ghsssd https://github.com/SSSD/sssd git fetch ghsssd pull/792/head:pr792 git checkout pr792
From 8fe2527e4b8176bca7366d590045c4cd4de0bf2e Mon Sep 17 00:00:00 2001 From: Sumit Bose <sb...@redhat.com> Date: Wed, 27 Mar 2019 09:02:27 +0100 Subject: [PATCH 1/5] pam: introduce prompt_config struct prompt_config is the internal struct to control the prompting of pam_sss. To make it easy to change internal details when more options are added it should be opaque and only accessed by getters and setter. Related to https://pagure.io/SSSD/sssd/issue/3264 --- Makefile.am | 17 + src/sss_client/pam_sss_prompt_config.c | 547 +++++++++++++++++++++++++ src/sss_client/sss_cli.h | 29 ++ src/tests/cmocka/test_prompt_config.c | 215 ++++++++++ 4 files changed, 808 insertions(+) create mode 100644 src/sss_client/pam_sss_prompt_config.c create mode 100644 src/tests/cmocka/test_prompt_config.c diff --git a/Makefile.am b/Makefile.am index 5317516668..72866cdb0c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -241,6 +241,7 @@ if HAVE_CMOCKA test-negcache \ negcache_2-tests \ test-authtok \ + test_prompt_config \ sss_nss_idmap-tests \ deskprofile_utils-tests \ dyndns-tests \ @@ -2910,6 +2911,21 @@ test_authtok_LDADD = \ libsss_debug.la \ $(NULL) +test_prompt_config_SOURCES = \ + src/tests/cmocka/test_prompt_config.c \ + src/sss_client/pam_sss_prompt_config.c \ + $(NULL) +test_prompt_config_CFLAGS = \ + $(AM_CFLAGS) \ + $(POPT_CFLAGS) \ + $(NULL) +test_prompt_config_LDADD = \ + $(CMOCKA_LIBS) \ + $(POPT_LIBS) \ + libsss_debug.la \ + $(TALLOC_LIBS) \ + $(NULL) + sss_nss_idmap_tests_SOURCES = \ src/tests/cmocka/sss_nss_idmap-tests.c sss_nss_idmap_tests_CFLAGS = \ @@ -4089,6 +4105,7 @@ endif pamlib_LTLIBRARIES = pam_sss.la pam_sss_la_SOURCES = \ src/sss_client/pam_sss.c \ + src/sss_client/pam_sss_prompt_config.c \ src/sss_client/pam_message.c \ src/sss_client/common.c \ src/sss_client/sss_cli.h \ diff --git a/src/sss_client/pam_sss_prompt_config.c b/src/sss_client/pam_sss_prompt_config.c new file mode 100644 index 0000000000..35094b4068 --- /dev/null +++ b/src/sss_client/pam_sss_prompt_config.c @@ -0,0 +1,547 @@ +/* + Authors: + Sumit Bose <sb...@redhat.com> + + Copyright (C) 2019 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" +#include <stdlib.h> +#include <errno.h> + +#include "sss_cli.h" + +#include <libintl.h> +#define _(STRING) dgettext (PACKAGE, STRING) + +struct prompt_config_password { + char *prompt; +}; + +struct prompt_config_2fa { + char *prompt_1st; + char *prompt_2nd; +}; + +struct prompt_config_2fa_single { + char *prompt; +}; + +struct prompt_config_sc_pin { + char *prompt; /* Currently not used */ +}; + +struct prompt_config { + enum prompt_config_type type; + union { + struct prompt_config_password password; + struct prompt_config_2fa two_fa; + struct prompt_config_2fa_single two_fa_single; + struct prompt_config_sc_pin sc_pin; + } data; +}; + +enum prompt_config_type pc_get_type(struct prompt_config *pc) +{ + if (pc != NULL && pc->type > PC_TYPE_INVALID && pc->type < PC_TYPE_LAST) { + return pc->type; + } + return PC_TYPE_INVALID; +} + +const char *pc_get_password_prompt(struct prompt_config *pc) +{ + if (pc != NULL && pc_get_type(pc) == PC_TYPE_PASSWORD) { + return pc->data.password.prompt; + } + return NULL; +} + +const char *pc_get_2fa_1st_prompt(struct prompt_config *pc) +{ + if (pc != NULL && pc_get_type(pc) == PC_TYPE_2FA) { + return pc->data.two_fa.prompt_1st; + } + return NULL; +} + +const char *pc_get_2fa_2nd_prompt(struct prompt_config *pc) +{ + if (pc != NULL && pc_get_type(pc) == PC_TYPE_2FA) { + return pc->data.two_fa.prompt_2nd; + } + return NULL; +} + +const char *pc_get_2fa_single_prompt(struct prompt_config *pc) +{ + if (pc != NULL && pc_get_type(pc) == PC_TYPE_2FA_SINGLE) { + return pc->data.two_fa_single.prompt; + } + return NULL; +} + +static void pc_free_password(struct prompt_config *pc) +{ + if (pc != NULL && pc_get_type(pc) == PC_TYPE_PASSWORD) { + free(pc->data.password.prompt); + } + return; +} + +static void pc_free_2fa(struct prompt_config *pc) +{ + if (pc != NULL && pc_get_type(pc) == PC_TYPE_2FA) { + free(pc->data.two_fa.prompt_1st); + free(pc->data.two_fa.prompt_2nd); + } + return; +} + +static void pc_free_2fa_single(struct prompt_config *pc) +{ + if (pc != NULL && pc_get_type(pc) == PC_TYPE_2FA_SINGLE) { + free(pc->data.two_fa_single.prompt); + } + return; +} + +static void pc_free_sc_pin(struct prompt_config *pc) +{ + if (pc != NULL && pc_get_type(pc) == PC_TYPE_SC_PIN) { + free(pc->data.sc_pin.prompt); + } + return; +} + +void pc_list_free(struct prompt_config **pc_list) +{ + size_t c; + + if (pc_list == NULL) { + return; + } + + for (c = 0; pc_list[c] != NULL; c++) { + switch (pc_list[c]->type) { + case PC_TYPE_PASSWORD: + pc_free_password(pc_list[c]); + break; + case PC_TYPE_2FA: + pc_free_2fa(pc_list[c]); + break; + case PC_TYPE_2FA_SINGLE: + pc_free_2fa_single(pc_list[c]); + break; + case PC_TYPE_SC_PIN: + pc_free_sc_pin(pc_list[c]); + break; + default: + return; + } + free(pc_list[c]); + } + free(pc_list); +} + +static errno_t pc_list_add_pc(struct prompt_config ***pc_list, + struct prompt_config *pc) +{ + size_t c = 0; + struct prompt_config **pcl; + + for (c = 0; *pc_list != NULL && (*pc_list)[c] != NULL; c++); /* just counting */ + + pcl = realloc(*pc_list, (c + 2) * sizeof(struct prompt_config *)); + if (pcl == NULL) { + return ENOMEM; + } + pcl[c] = pc; + pcl[c + 1] = NULL; + + *pc_list = pcl; + + return EOK; +} + +#define DEFAULT_PASSWORD_PROMPT _("Password: ") +#define DEFAULT_2FA_SINGLE_PROMPT _("Password + Token value: ") +#define DEFAULT_2FA_PROMPT_1ST _("First Factor: ") +#define DEFAULT_2FA_PROMPT_2ND _("Second Factor: ") + +errno_t pc_list_add_password(struct prompt_config ***pc_list, + const char *prompt) +{ + struct prompt_config *pc; + int ret; + + if (pc_list == NULL) { + return EINVAL; + } + + pc = calloc(1, sizeof(struct prompt_config)); + if (pc == NULL) { + return ENOMEM; + } + + pc->type = PC_TYPE_PASSWORD; + pc->data.password.prompt = strdup(prompt != NULL ? prompt + : DEFAULT_PASSWORD_PROMPT); + if (pc->data.password.prompt == NULL) { + ret = ENOMEM; + goto done; + } + + ret = pc_list_add_pc(pc_list, pc); + if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + free(pc->data.password.prompt); + free(pc); + } + + return ret; +} + +errno_t pc_list_add_2fa(struct prompt_config ***pc_list, + const char *prompt_1st, const char *prompt_2nd) +{ + struct prompt_config *pc; + int ret; + + if (pc_list == NULL) { + return EINVAL; + } + + pc = calloc(1, sizeof(struct prompt_config)); + if (pc == NULL) { + return ENOMEM; + } + + pc->type = PC_TYPE_2FA; + pc->data.two_fa.prompt_1st = strdup(prompt_1st != NULL ? prompt_1st + : DEFAULT_2FA_PROMPT_1ST); + if (pc->data.two_fa.prompt_1st == NULL) { + ret = ENOMEM; + goto done; + } + pc->data.two_fa.prompt_2nd = strdup(prompt_2nd != NULL ? prompt_2nd + : DEFAULT_2FA_PROMPT_2ND); + if (pc->data.two_fa.prompt_2nd == NULL) { + ret = ENOMEM; + goto done; + } + + ret = pc_list_add_pc(pc_list, pc); + if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + free(pc->data.two_fa.prompt_1st); + free(pc->data.two_fa.prompt_2nd); + free(pc); + } + + return ret; +} + +errno_t pc_list_add_2fa_single(struct prompt_config ***pc_list, + const char *prompt) +{ + struct prompt_config *pc; + int ret; + + if (pc_list == NULL) { + return EINVAL; + } + + pc = calloc(1, sizeof(struct prompt_config)); + if (pc == NULL) { + return ENOMEM; + } + + pc->type = PC_TYPE_2FA_SINGLE; + pc->data.two_fa_single.prompt = strdup(prompt != NULL ? prompt + : DEFAULT_2FA_SINGLE_PROMPT); + if (pc->data.two_fa_single.prompt == NULL) { + ret = ENOMEM; + goto done; + } + + ret = pc_list_add_pc(pc_list, pc); + if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + free(pc->data.two_fa_single.prompt); + free(pc); + } + + return ret; +} + +errno_t pam_get_response_prompt_config(struct prompt_config **pc_list, int *len, + uint8_t **data) +{ + size_t c; + size_t l = 0; + uint8_t *d = NULL; + uint32_t uint32_val; + size_t rp; + + if (pc_list == NULL || *pc_list == NULL) { + return ENOENT; + } + + l += sizeof(uint32_t); + for (c = 0; pc_list[c] != NULL; c++) { + l += sizeof(uint32_t); + switch (pc_list[c]->type) { + case PC_TYPE_PASSWORD: + l += sizeof(uint32_t); + l += strlen(pc_list[c]->data.password.prompt); + break; + case PC_TYPE_2FA: + l += sizeof(uint32_t); + l += strlen(pc_list[c]->data.two_fa.prompt_1st); + l += sizeof(uint32_t); + l += strlen(pc_list[c]->data.two_fa.prompt_2nd); + break; + case PC_TYPE_2FA_SINGLE: + l += sizeof(uint32_t); + l += strlen(pc_list[c]->data.two_fa_single.prompt); + break; + case PC_TYPE_SC_PIN: + break; + default: + return EINVAL; + } + } + + d = malloc(l * sizeof(uint8_t)); + if (d == NULL) { + return ENOMEM; + } + + rp = 0; + uint32_val = c; + SAFEALIGN_COPY_UINT32(&d[rp], &uint32_val, &rp); + + for (c = 0; pc_list[c] != NULL; c++) { + uint32_val = pc_list[c]->type; + SAFEALIGN_COPY_UINT32(&d[rp], &uint32_val, &rp); + + switch (pc_list[c]->type) { + case PC_TYPE_PASSWORD: + SAFEALIGN_SET_UINT32(&d[rp], + strlen(pc_list[c]->data.password.prompt), &rp); + safealign_memcpy(&d[rp], pc_list[c]->data.password.prompt, + strlen(pc_list[c]->data.password.prompt), &rp); + break; + case PC_TYPE_2FA: + SAFEALIGN_SET_UINT32(&d[rp], + strlen(pc_list[c]->data.two_fa.prompt_1st), + &rp); + safealign_memcpy(&d[rp], pc_list[c]->data.two_fa.prompt_1st, + strlen(pc_list[c]->data.two_fa.prompt_1st), &rp); + SAFEALIGN_SET_UINT32(&d[rp], + strlen(pc_list[c]->data.two_fa.prompt_2nd), + &rp); + safealign_memcpy(&d[rp], pc_list[c]->data.two_fa.prompt_2nd, + strlen(pc_list[c]->data.two_fa.prompt_2nd), &rp); + break; + case PC_TYPE_2FA_SINGLE: + SAFEALIGN_SET_UINT32(&d[rp], + strlen(pc_list[c]->data.two_fa_single.prompt), + &rp); + safealign_memcpy(&d[rp], pc_list[c]->data.two_fa_single.prompt, + strlen(pc_list[c]->data.two_fa_single.prompt), + &rp); + break; + case PC_TYPE_SC_PIN: + break; + default: + free(d); + return EINVAL; + } + } + + if (rp != l) { + free(d); + return EFAULT; + } + + *data = d; + *len = l; + + return EOK; +} + +errno_t pc_list_from_response(int size, uint8_t *buf, + struct prompt_config ***pc_list) +{ + int ret; + uint32_t count; + uint32_t type; + uint32_t l; + size_t rp; + size_t c; + struct prompt_config **pl = NULL; + char *str; + char *str2; + + if (buf == NULL || size < 3 * sizeof(uint32_t)) { + return EINVAL; + } + + rp = 0; + SAFEALIGN_COPY_UINT32_CHECK(&count, buf + rp, size, &rp); + + for (c = 0; c < count; c++) { + /* Since we already know size < 3 * sizeof(uint32_t) this check should + * be safe and without over- or underflow. */ + if (rp > size - sizeof(uint32_t)) { + ret = EINVAL; + goto done; + } + SAFEALIGN_COPY_UINT32(&type, buf + rp, &rp); + + switch (type) { + case PC_TYPE_PASSWORD: + if (rp > size - sizeof(uint32_t)) { + ret = EINVAL; + goto done; + } + SAFEALIGN_COPY_UINT32(&l, buf + rp, &rp); + + if (l > size || rp > size - l) { + ret = EINVAL; + goto done; + } + str = strndup((char *) buf + rp, l); + if (str == NULL) { + ret = ENOMEM; + goto done; + } + rp += l; + + ret = pc_list_add_password(&pl, str); + free(str); + if (ret != EOK) { + goto done; + } + break; + case PC_TYPE_2FA: + if (rp > size - sizeof(uint32_t)) { + ret = EINVAL; + goto done; + } + SAFEALIGN_COPY_UINT32(&l, buf + rp, &rp); + + if (l > size || rp > size - l) { + ret = EINVAL; + goto done; + } + str = strndup((char *) buf + rp, l); + if (str == NULL) { + ret = ENOMEM; + goto done; + } + rp += l; + + if (rp > size - sizeof(uint32_t)) { + free(str); + ret = EINVAL; + goto done; + } + SAFEALIGN_COPY_UINT32(&l, buf + rp, &rp); + + if (l > size || rp > size - l) { + free(str); + ret = EINVAL; + goto done; + } + str2 = strndup((char *) buf + rp, l); + if (str2 == NULL) { + free(str); + ret = ENOMEM; + goto done; + } + rp += l; + + ret = pc_list_add_2fa(&pl, str, str2); + free(str); + free(str2); + if (ret != EOK) { + goto done; + } + break; + case PC_TYPE_2FA_SINGLE: + if (rp > size - sizeof(uint32_t)) { + ret = EINVAL; + goto done; + } + SAFEALIGN_COPY_UINT32(&l, buf + rp, &rp); + + if (l > size || rp > size - l) { + ret = EINVAL; + goto done; + } + str = strndup((char *) buf + rp, l); + if (str == NULL) { + ret = ENOMEM; + goto done; + } + rp += l; + + ret = pc_list_add_2fa_single(&pl, str); + free(str); + if (ret != EOK) { + goto done; + } + break; + case PC_TYPE_SC_PIN: + break; + default: + ret = EINVAL; + goto done; + } + } + + *pc_list = pl; + + ret = EOK; + +done: + if (ret != EOK) { + pc_list_free(pl); + } + + return ret; +} diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h index 31b4e50f7a..599cf842de 100644 --- a/src/sss_client/sss_cli.h +++ b/src/sss_client/sss_cli.h @@ -573,6 +573,35 @@ enum user_info_type { * @} */ /* end of group sss_pam_cli */ + +enum prompt_config_type { + PC_TYPE_INVALID = 0, + PC_TYPE_PASSWORD, + PC_TYPE_2FA, + PC_TYPE_2FA_SINGLE, + PC_TYPE_SC_PIN, + PC_TYPE_LAST +}; + +struct prompt_config; + +enum prompt_config_type pc_get_type(struct prompt_config *pc); +const char *pc_get_password_prompt(struct prompt_config *pc); +const char *pc_get_2fa_1st_prompt(struct prompt_config *pc); +const char *pc_get_2fa_2nd_prompt(struct prompt_config *pc); +const char *pc_get_2fa_single_prompt(struct prompt_config *pc); +void pc_list_free(struct prompt_config **pc_list); +errno_t pc_list_add_password(struct prompt_config ***pc_list, + const char *prompt); +errno_t pc_list_add_2fa(struct prompt_config ***pc_list, + const char *prompt_1st, const char *prompt_2nd); +errno_t pc_list_add_2fa_single(struct prompt_config ***pc_list, + const char *prompt); +errno_t pam_get_response_prompt_config(struct prompt_config **pc_list, int *len, + uint8_t **data); +errno_t pc_list_from_response(int size, uint8_t *buf, + struct prompt_config ***pc_list); + enum sss_netgr_rep_type { SSS_NETGR_REP_TRIPLE = 1, SSS_NETGR_REP_GROUP diff --git a/src/tests/cmocka/test_prompt_config.c b/src/tests/cmocka/test_prompt_config.c new file mode 100644 index 0000000000..0b761ae4c3 --- /dev/null +++ b/src/tests/cmocka/test_prompt_config.c @@ -0,0 +1,215 @@ +/* + SSSD + + prompt config - Utilities tests + + Authors: + Sumit bose <sb...@redhat.com> + + Copyright (C) 2019 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <string.h> +#include <popt.h> + +#include "tests/cmocka/common_mock.h" + +#include "sss_client/sss_cli.h" + +void test_pc_list_add_password(void **state) +{ + int ret; + struct prompt_config **pc_list = NULL; + + ret = pc_list_add_password(&pc_list, "Hello"); + assert_int_equal(ret, EOK); + assert_non_null(pc_list); + assert_non_null(pc_list[0]); + assert_int_equal(PC_TYPE_PASSWORD, pc_get_type(pc_list[0])); + assert_string_equal("Hello", pc_get_password_prompt(pc_list[0])); + assert_null(pc_list[1]); + + ret = pc_list_add_password(&pc_list, "Hello2"); + assert_int_equal(ret, EOK); + assert_non_null(pc_list); + assert_non_null(pc_list[0]); + assert_int_equal(PC_TYPE_PASSWORD, pc_get_type(pc_list[0])); + assert_string_equal("Hello", pc_get_password_prompt(pc_list[0])); + assert_non_null(pc_list[1]); + assert_int_equal(PC_TYPE_PASSWORD, pc_get_type(pc_list[1])); + assert_string_equal("Hello2", pc_get_password_prompt(pc_list[1])); + assert_null(pc_list[2]); + + pc_list_free(pc_list); +} + +void test_pc_list_add_2fa_single(void **state) +{ + int ret; + struct prompt_config **pc_list = NULL; + + ret = pc_list_add_2fa_single(&pc_list, "Hello"); + assert_int_equal(ret, EOK); + assert_non_null(pc_list); + assert_non_null(pc_list[0]); + assert_int_equal(PC_TYPE_2FA_SINGLE, pc_get_type(pc_list[0])); + assert_string_equal("Hello", pc_get_2fa_single_prompt(pc_list[0])); + assert_null(pc_list[1]); + + ret = pc_list_add_2fa_single(&pc_list, "Hello2"); + assert_int_equal(ret, EOK); + assert_non_null(pc_list); + assert_non_null(pc_list[0]); + assert_int_equal(PC_TYPE_2FA_SINGLE, pc_get_type(pc_list[0])); + assert_string_equal("Hello", pc_get_2fa_single_prompt(pc_list[0])); + assert_non_null(pc_list[1]); + assert_int_equal(PC_TYPE_2FA_SINGLE, pc_get_type(pc_list[1])); + assert_string_equal("Hello2", pc_get_2fa_single_prompt(pc_list[1])); + assert_null(pc_list[2]); + + pc_list_free(pc_list); +} + +void test_pc_list_add_2fa(void **state) +{ + int ret; + struct prompt_config **pc_list = NULL; + + ret = pc_list_add_2fa(&pc_list, "Hello", "Good Bye"); + assert_int_equal(ret, EOK); + assert_non_null(pc_list); + assert_non_null(pc_list[0]); + assert_int_equal(PC_TYPE_2FA, pc_get_type(pc_list[0])); + assert_string_equal("Hello", pc_get_2fa_1st_prompt(pc_list[0])); + assert_string_equal("Good Bye", pc_get_2fa_2nd_prompt(pc_list[0])); + assert_null(pc_list[1]); + + pc_list_free(pc_list); +} + +void test_pam_get_response_prompt_config(void **state) +{ + int ret; + struct prompt_config **pc_list = NULL; + int len; + uint8_t *data; + + ret = pc_list_add_password(&pc_list, "password"); + assert_int_equal(ret, EOK); + + ret = pc_list_add_2fa(&pc_list, "first", "second"); + assert_int_equal(ret, EOK); + + ret = pc_list_add_2fa_single(&pc_list, "single"); + assert_int_equal(ret, EOK); + + ret = pam_get_response_prompt_config(pc_list, &len, &data); + pc_list_free(pc_list); + assert_int_equal(ret, EOK); + assert_int_equal(len, 57); + +#if __BYTE_ORDER == __LITTLE_ENDIAN + assert_memory_equal(data, "\3\0\0\0\1\0\0\0\10\0\0\0" "password\2\0\0\0\5\0\0\0" "first\6\0\0\0" "second\3\0\0\0\6\0\0\0" "single", len); +#else + assert_memory_equal(data, "\0\0\0\3\0\0\0\1\0\0\0\10" "password\0\0\0\2\0\0\0\5" "first\0\0\0\6" "second\0\0\0\3\0\0\0\6" "single", len); +#endif + + free(data); +} + +void test_pc_list_from_response(void **state) +{ + int ret; + struct prompt_config **pc_list = NULL; + int len; + uint8_t *data; + + ret = pc_list_add_password(&pc_list, "password"); + assert_int_equal(ret, EOK); + + ret = pc_list_add_2fa(&pc_list, "first", "second"); + assert_int_equal(ret, EOK); + + ret = pc_list_add_2fa_single(&pc_list, "single"); + assert_int_equal(ret, EOK); + + ret = pam_get_response_prompt_config(pc_list, &len, &data); + pc_list_free(pc_list); + assert_int_equal(ret, EOK); + assert_int_equal(len, 57); + + pc_list = NULL; + + ret = pc_list_from_response(len, data, &pc_list); + free(data); + assert_int_equal(ret, EOK); + assert_non_null(pc_list); + + assert_non_null(pc_list[0]); + assert_int_equal(PC_TYPE_PASSWORD, pc_get_type(pc_list[0])); + assert_string_equal("password", pc_get_password_prompt(pc_list[0])); + + assert_non_null(pc_list[1]); + assert_int_equal(PC_TYPE_2FA, pc_get_type(pc_list[1])); + assert_string_equal("first", pc_get_2fa_1st_prompt(pc_list[1])); + assert_string_equal("second", pc_get_2fa_2nd_prompt(pc_list[1])); + + assert_non_null(pc_list[2]); + assert_int_equal(PC_TYPE_2FA_SINGLE, pc_get_type(pc_list[2])); + assert_string_equal("single", pc_get_2fa_single_prompt(pc_list[2])); + + assert_null(pc_list[3]); + + pc_list_free(pc_list); +} + +int main(int argc, const char *argv[]) +{ + poptContext pc; + int opt; + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_DEBUG_OPTS + POPT_TABLEEND + }; + + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_pc_list_add_password), + cmocka_unit_test(test_pc_list_add_2fa_single), + cmocka_unit_test(test_pc_list_add_2fa), + cmocka_unit_test(test_pam_get_response_prompt_config), + cmocka_unit_test(test_pc_list_from_response), + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + poptFreeContext(pc); + + DEBUG_CLI_INIT(debug_level); + + return cmocka_run_group_tests(tests, NULL, NULL); +} From 8c45c826903db0924b61f19e0d7c658653088309 Mon Sep 17 00:00:00 2001 From: Sumit Bose <sb...@redhat.com> Date: Wed, 27 Mar 2019 09:04:53 +0100 Subject: [PATCH 2/5] authtok: add dedicated type for 2fa with single string Currently the password type is used to send two-factor authentication credentials entered in a single string to the backend, This is unreliable and only works properly if password authentication is not available for the user as well. To support 2FA credentials in a single string better a new authtok type is added. Related to https://pagure.io/SSSD/sssd/issue/3264 --- src/providers/krb5/krb5_auth.c | 1 + src/providers/krb5/krb5_child.c | 13 +++++++ src/providers/krb5/krb5_child_handler.c | 4 +++ src/responder/pam/pamsrv_cmd.c | 1 + src/sss_client/sss_cli.h | 3 ++ src/tests/cmocka/test_authtok.c | 45 +++++++++++++++++++++++++ src/util/authtok.c | 42 +++++++++++++++++++++++ src/util/authtok.h | 35 +++++++++++++++++++ 8 files changed, 144 insertions(+) diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c index d40d2afed5..9a92504349 100644 --- a/src/providers/krb5/krb5_auth.c +++ b/src/providers/krb5/krb5_auth.c @@ -495,6 +495,7 @@ struct tevent_req *krb5_auth_send(TALLOC_CTX *mem_ctx, case SSS_PAM_CHAUTHTOK: if (authtok_type != SSS_AUTHTOK_TYPE_PASSWORD && authtok_type != SSS_AUTHTOK_TYPE_2FA + && authtok_type != SSS_AUTHTOK_TYPE_2FA_SINGLE && authtok_type != SSS_AUTHTOK_TYPE_SC_PIN && authtok_type != SSS_AUTHTOK_TYPE_SC_KEYPAD) { /* handle empty password gracefully */ diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c index 7ad411914a..ae63b14d18 100644 --- a/src/providers/krb5/krb5_child.c +++ b/src/providers/krb5/krb5_child.c @@ -504,6 +504,15 @@ static krb5_error_code tokeninfo_matches(TALLOC_CTX *mem_ctx, return ret; } + return tokeninfo_matches_pwd(mem_ctx, ti, pwd, len, out_token, out_pin); + break; + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + ret = sss_authtok_get_2fa_single(auth_tok, &pwd, &len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_password failed.\n"); + return ret; + } + return tokeninfo_matches_pwd(mem_ctx, ti, pwd, len, out_token, out_pin); break; case SSS_AUTHTOK_TYPE_2FA: @@ -2108,6 +2117,7 @@ static errno_t tgt_req_child(struct krb5_req *kr) /* No password is needed for pre-auth or if we have 2FA or SC */ if (kr->pd->cmd != SSS_PAM_PREAUTH && sss_authtok_get_type(kr->pd->authtok) != SSS_AUTHTOK_TYPE_2FA + && sss_authtok_get_type(kr->pd->authtok) != SSS_AUTHTOK_TYPE_2FA_SINGLE && sss_authtok_get_type(kr->pd->authtok) != SSS_AUTHTOK_TYPE_SC_PIN && sss_authtok_get_type(kr->pd->authtok) != SSS_AUTHTOK_TYPE_SC_KEYPAD) { @@ -2366,6 +2376,9 @@ static errno_t unpack_authtok(struct sss_auth_token *tok, case SSS_AUTHTOK_TYPE_CCFILE: ret = sss_authtok_set_ccfile(tok, (char *)(buf + *p), 0); break; + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + ret = sss_authtok_set_2fa_single(tok, (char *)(buf + *p), 0); + break; case SSS_AUTHTOK_TYPE_2FA: case SSS_AUTHTOK_TYPE_SC_PIN: case SSS_AUTHTOK_TYPE_SC_KEYPAD: diff --git a/src/providers/krb5/krb5_child_handler.c b/src/providers/krb5/krb5_child_handler.c index 352ff980dd..b7fb54499e 100644 --- a/src/providers/krb5/krb5_child_handler.c +++ b/src/providers/krb5/krb5_child_handler.c @@ -79,6 +79,10 @@ static errno_t pack_authtok(struct io_buffer *buf, size_t *rp, ret = sss_authtok_get_ccfile(tok, &data, &len); auth_token_length = len + 1; break; + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + ret = sss_authtok_get_2fa_single(tok, &data, &len); + auth_token_length = len + 1; + break; case SSS_AUTHTOK_TYPE_2FA: case SSS_AUTHTOK_TYPE_SC_PIN: case SSS_AUTHTOK_TYPE_SC_KEYPAD: diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c index 3b4869ecea..6c04cb157d 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -160,6 +160,7 @@ static int extract_authtok_v2(struct sss_auth_token *tok, } break; case SSS_AUTHTOK_TYPE_2FA: + case SSS_AUTHTOK_TYPE_2FA_SINGLE: case SSS_AUTHTOK_TYPE_SC_PIN: case SSS_AUTHTOK_TYPE_SC_KEYPAD: ret = sss_authtok_set(tok, auth_token_type, diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h index 599cf842de..477e4d5cce 100644 --- a/src/sss_client/sss_cli.h +++ b/src/sss_client/sss_cli.h @@ -340,6 +340,9 @@ enum sss_authtok_type { * Smart Card authentication is used * and that the PIN will be entered * at the card reader. */ + SSS_AUTHTOK_TYPE_2FA_SINGLE = 0x0006, /**< Authentication token has two + * factors in a single string, it may + * or may no contain a trailing \\0 */ }; /** diff --git a/src/tests/cmocka/test_authtok.c b/src/tests/cmocka/test_authtok.c index 9422f96bcf..84e209783e 100644 --- a/src/tests/cmocka/test_authtok.c +++ b/src/tests/cmocka/test_authtok.c @@ -652,6 +652,49 @@ void test_sss_authtok_sc_pin(void **state) assert_int_equal(ret, EFAULT); } +/* Test when type has value SSS_AUTHTOK_TYPE_2FA_SINGLE */ +static void test_sss_authtok_2fa_single(void **state) +{ + size_t len; + errno_t ret; + char *data; + size_t ret_len; + const char *pwd; + struct test_state *ts; + enum sss_authtok_type type; + + ts = talloc_get_type_abort(*state, struct test_state); + data = talloc_strdup(ts, "1stfacto2ndfactor"); + assert_non_null(data); + + len = strlen(data) + 1; + type = SSS_AUTHTOK_TYPE_2FA_SINGLE; + ret = sss_authtok_set(ts->authtoken, type, (const uint8_t *)data, len); + + assert_int_equal(ret, EOK); + assert_int_equal(type, sss_authtok_get_type(ts->authtoken)); + assert_int_equal(len, sss_authtok_get_size(ts->authtoken)); + assert_string_equal(data, sss_authtok_get_data(ts->authtoken)); + + ret = sss_authtok_get_2fa_single(ts->authtoken, &pwd, &ret_len); + + assert_int_equal(ret, EOK); + assert_string_equal(data, pwd); + assert_int_equal(len - 1, ret_len); + + ret = sss_authtok_set_2fa_single(ts->authtoken, data, len); + assert_int_equal(ret, EOK); + + ret = sss_authtok_get_2fa_single(ts->authtoken, &pwd, &ret_len); + assert_int_equal(ret, EOK); + assert_string_equal(data, pwd); + assert_int_equal(len - 1, ret_len); + + talloc_free(data); + sss_authtok_set_empty(ts->authtoken); +} + + int main(int argc, const char *argv[]) { poptContext pc; @@ -687,6 +730,8 @@ int main(int argc, const char *argv[]) setup, teardown), cmocka_unit_test_setup_teardown(test_sss_authtok_sc_blobs, setup, teardown), + cmocka_unit_test_setup_teardown(test_sss_authtok_2fa_single, + setup, teardown), }; /* Set debug level to invalid value so we can decide if -d 0 was used. */ diff --git a/src/util/authtok.c b/src/util/authtok.c index c2f78be329..0cac245985 100644 --- a/src/util/authtok.c +++ b/src/util/authtok.c @@ -41,6 +41,7 @@ size_t sss_authtok_get_size(struct sss_auth_token *tok) case SSS_AUTHTOK_TYPE_2FA: case SSS_AUTHTOK_TYPE_SC_PIN: case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_2FA_SINGLE: return tok->length; case SSS_AUTHTOK_TYPE_EMPTY: return 0; @@ -76,6 +77,7 @@ errno_t sss_authtok_get_password(struct sss_auth_token *tok, case SSS_AUTHTOK_TYPE_2FA: case SSS_AUTHTOK_TYPE_SC_PIN: case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_2FA_SINGLE: return EACCES; } @@ -101,6 +103,33 @@ errno_t sss_authtok_get_ccfile(struct sss_auth_token *tok, case SSS_AUTHTOK_TYPE_2FA: case SSS_AUTHTOK_TYPE_SC_PIN: case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + return EACCES; + } + + return EINVAL; +} + +errno_t sss_authtok_get_2fa_single(struct sss_auth_token *tok, + const char **str, size_t *len) +{ + if (!tok) { + return EINVAL; + } + switch (tok->type) { + case SSS_AUTHTOK_TYPE_EMPTY: + return ENOENT; + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + *str = (const char *)tok->data; + if (len) { + *len = tok->length - 1; + } + return EOK; + case SSS_AUTHTOK_TYPE_PASSWORD: + case SSS_AUTHTOK_TYPE_2FA: + case SSS_AUTHTOK_TYPE_SC_PIN: + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_CCFILE: return EACCES; } @@ -151,6 +180,7 @@ void sss_authtok_set_empty(struct sss_auth_token *tok) case SSS_AUTHTOK_TYPE_PASSWORD: case SSS_AUTHTOK_TYPE_2FA: case SSS_AUTHTOK_TYPE_SC_PIN: + case SSS_AUTHTOK_TYPE_2FA_SINGLE: safezero(tok->data, tok->length); break; case SSS_AUTHTOK_TYPE_CCFILE: @@ -181,6 +211,15 @@ errno_t sss_authtok_set_ccfile(struct sss_auth_token *tok, "ccfile", ccfile, len); } +errno_t sss_authtok_set_2fa_single(struct sss_auth_token *tok, + const char *str, size_t len) +{ + sss_authtok_set_empty(tok); + + return sss_authtok_set_string(tok, SSS_AUTHTOK_TYPE_2FA_SINGLE, + "2fa_single", str, len); +} + static errno_t sss_authtok_set_2fa_from_blob(struct sss_auth_token *tok, const uint8_t *data, size_t len); @@ -199,6 +238,8 @@ errno_t sss_authtok_set(struct sss_auth_token *tok, return sss_authtok_set_sc_from_blob(tok, data, len); case SSS_AUTHTOK_TYPE_SC_KEYPAD: return sss_authtok_set_sc_from_blob(tok, data, len); + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + return sss_authtok_set_2fa_single(tok, (const char *) data, len); case SSS_AUTHTOK_TYPE_EMPTY: sss_authtok_set_empty(tok); return EOK; @@ -566,6 +607,7 @@ errno_t sss_authtok_get_sc_pin(struct sss_auth_token *tok, const char **_pin, case SSS_AUTHTOK_TYPE_CCFILE: case SSS_AUTHTOK_TYPE_2FA: case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_2FA_SINGLE: return EACCES; } diff --git a/src/util/authtok.h b/src/util/authtok.h index a55e89fd28..dae3ff6b17 100644 --- a/src/util/authtok.h +++ b/src/util/authtok.h @@ -348,4 +348,39 @@ errno_t sss_authtok_get_sc(struct sss_auth_token *tok, const char **_token_name, size_t *_token_name_len, const char **_module_name, size_t *_module_name_len, const char **_key_id, size_t *_key_id_len); + + +/** + * @brief Returns a const string if the auth token is of type + SSS_AUTHTOK_TYPE_2FA_SINGLE, otherwise it returns an error + * + * @param tok A pointer to an sss_auth_token + * @param pwd A pointer to a const char *, that will point to a null + * terminated string + * @param len The length of the credential string + * + * @return EOK on success + * ENOENT if the token is empty + * EACCESS if the token is not a password token + */ +errno_t sss_authtok_get_2fa_single(struct sss_auth_token *tok, + const char **str, size_t *len); + +/** + * @brief Set a 2FA credentials in a single strings into an auth token, + * replacing any previous data + * + * @param tok A pointer to an sss_auth_token structure to change, also + * used as a memory context to allocate the internal data. + * @param password A string where the two authentication factors are + * concatenated together + * @param len The length of the string or, if 0 is passed, + * then strlen(password) will be used internally. + * + * @return EOK on success + * ENOMEM on error + */ +errno_t sss_authtok_set_2fa_single(struct sss_auth_token *tok, + const char *str, size_t len); + #endif /* __AUTHTOK_H__ */ From 544f41fd400926c172c3399bb0587d383db44455 Mon Sep 17 00:00:00 2001 From: Sumit Bose <sb...@redhat.com> Date: Wed, 27 Mar 2019 09:48:42 +0100 Subject: [PATCH 3/5] pam_sss: use configured prompting If the responds of SSSD's PAM responder contains a prompt_config structure use the content to prompt the user for credentials. Related to https://pagure.io/SSSD/sssd/issue/3264 --- src/sss_client/pam_message.h | 2 + src/sss_client/pam_sss.c | 136 +++++++++++++++++++++++++++++------ src/sss_client/sss_cli.h | 3 + 3 files changed, 119 insertions(+), 22 deletions(-) diff --git a/src/sss_client/pam_message.h b/src/sss_client/pam_message.h index 50fedcd82d..e37e2f082b 100644 --- a/src/sss_client/pam_message.h +++ b/src/sss_client/pam_message.h @@ -65,6 +65,8 @@ struct pam_items { bool user_name_hint; struct cert_auth_info *cert_list; struct cert_auth_info *selected_cert; + + struct prompt_config **pc; }; int pack_message_v3(struct pam_items *pi, size_t *size, uint8_t **buffer); diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c index 9d51aefc61..6bcda23da5 100644 --- a/src/sss_client/pam_sss.c +++ b/src/sss_client/pam_sss.c @@ -197,6 +197,9 @@ static void overwrite_and_free_pam_items(struct pam_items *pi) free_cert_list(pi->cert_list); pi->cert_list = NULL; pi->selected_cert = NULL; + + pc_list_free(pi->pc); + pi->pc = NULL; } static int null_strcmp(const char *s1, const char *s2) { @@ -1155,6 +1158,16 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf, D(("Password prompting available.")); pi->password_prompting = true; break; + case SSS_PAM_PROMPT_CONFIG: + if (pi->pc == NULL) { + ret = pc_list_from_response(len, &buf[p], &pi->pc); + if (ret != EOK) { + D(("Failed to parse prompting data, using defaults")); + pc_list_free(pi->pc); + pi->pc = NULL; + } + } + break; default: D(("Unknown response type [%d]", type)); } @@ -1248,6 +1261,8 @@ static int get_pam_items(pam_handle_t *pamh, uint32_t flags, pi->cert_list = NULL; pi->selected_cert = NULL; + pi->pc = NULL; + pi->flags = flags; return PAM_SUCCESS; @@ -1563,6 +1578,37 @@ static int prompt_2fa(pam_handle_t *pamh, struct pam_items *pi, return ret; } +static int prompt_2fa_single(pam_handle_t *pamh, struct pam_items *pi, + const char *prompt) +{ + int ret; + char *answer = NULL; + + ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, prompt, NULL, &answer); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return ret; + } + + if (answer == NULL) { + pi->pam_authtok = NULL; + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; + pi->pam_authtok_size=0; + } else { + pi->pam_authtok = strdup(answer); + _pam_overwrite((void *)answer); + free(answer); + answer=NULL; + if (pi->pam_authtok == NULL) { + return PAM_BUF_ERR; + } + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_2FA_SINGLE; + pi->pam_authtok_size=strlen(pi->pam_authtok); + } + + return PAM_SUCCESS; +} + #define SC_PROMPT_FMT "PIN for %s" #ifndef discard_const @@ -2023,6 +2069,48 @@ static void eval_argv(pam_handle_t *pamh, int argc, const char **argv, return; } +static int prompt_by_config(pam_handle_t *pamh, struct pam_items *pi) +{ + size_t c; + int ret; + + if (pi->pc == NULL || *pi->pc == NULL) { + return EINVAL; + } + + for (c = 0; pi->pc[c] != NULL; c++) { + switch (pc_get_type(pi->pc[c])) { + case PC_TYPE_PASSWORD: + ret = prompt_password(pamh, pi, pc_get_password_prompt(pi->pc[c])); + break; + case PC_TYPE_2FA: + ret = prompt_2fa(pamh, pi, pc_get_2fa_1st_prompt(pi->pc[c]), + pc_get_2fa_2nd_prompt(pi->pc[c])); + break; + case PC_TYPE_2FA_SINGLE: + ret = prompt_2fa_single(pamh, pi, + pc_get_2fa_single_prompt(pi->pc[c])); + break; + case PC_TYPE_SC_PIN: + ret = prompt_sc_pin(pamh, pi); + /* Todo: add extra string option */ + break; + default: + ret = EINVAL; + } + + /* If not credential where given try the next type otherwise we are + * done. */ + if (ret == PAM_SUCCESS && pi->pam_authtok_size == 0) { + continue; + } + + break; + } + + return ret; +} + static int get_authtok_for_authentication(pam_handle_t *pamh, struct pam_items *pi, uint32_t flags) @@ -2041,30 +2129,34 @@ static int get_authtok_for_authentication(pam_handle_t *pamh, } pi->pam_authtok_size = strlen(pi->pam_authtok); } else { - if (flags & PAM_CLI_FLAGS_USE_2FA - || (pi->otp_vendor != NULL && pi->otp_token_id != NULL - && pi->otp_challenge != NULL)) { - if (pi->password_prompting) { - ret = prompt_2fa(pamh, pi, _("First Factor: "), - _("Second Factor (optional): ")); - } else { - ret = prompt_2fa(pamh, pi, _("First Factor: "), - _("Second Factor: ")); - } - } else if (pi->cert_list != NULL) { - if (pi->cert_list->next == NULL) { - /* Only one certificate */ - pi->selected_cert = pi->cert_list; - } else { - ret = prompt_multi_cert(pamh, pi); - if (ret != 0) { - D(("Failed to select certificate")); - return PAM_AUTHTOK_ERR; + if (pi->pc != NULL) { + ret = prompt_by_config(pamh, pi); + } else { + if (flags & PAM_CLI_FLAGS_USE_2FA + || (pi->otp_vendor != NULL && pi->otp_token_id != NULL + && pi->otp_challenge != NULL)) { + if (pi->password_prompting) { + ret = prompt_2fa(pamh, pi, _("First Factor: "), + _("Second Factor (optional): ")); + } else { + ret = prompt_2fa(pamh, pi, _("First Factor: "), + _("Second Factor: ")); } + } else if (pi->cert_list != NULL) { + if (pi->cert_list->next == NULL) { + /* Only one certificate */ + pi->selected_cert = pi->cert_list; + } else { + ret = prompt_multi_cert(pamh, pi); + if (ret != 0) { + D(("Failed to select certificate")); + return PAM_AUTHTOK_ERR; + } + } + ret = prompt_sc_pin(pamh, pi); + } else { + ret = prompt_password(pamh, pi, _("Password: ")); } - ret = prompt_sc_pin(pamh, pi); - } else { - ret = prompt_password(pamh, pi, _("Password: ")); } if (ret != PAM_SUCCESS) { D(("failed to get password from user")); diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h index 477e4d5cce..0b41d31309 100644 --- a/src/sss_client/sss_cli.h +++ b/src/sss_client/sss_cli.h @@ -481,6 +481,9 @@ enum response_type { SSS_PAM_CERT_INFO_WITH_HINT, /**< Same as SSS_PAM_CERT_INFO but user name * might be missing and should be prompted * for. */ + SSS_PAM_PROMPT_CONFIG, /**< Contains data which controls which credentials + * are expected and how the user is prompted for + * them. */ }; /** From 4833c00ac4f3f811b7bcbbb1c5ac584365fce947 Mon Sep 17 00:00:00 2001 From: Sumit Bose <sb...@redhat.com> Date: Wed, 27 Mar 2019 21:05:06 +0100 Subject: [PATCH 4/5] PAM: add initial prompting configuration Add new section for sssd.conf to allow more flexible prompting during authentication. Related to https://pagure.io/SSSD/sssd/issue/3264 --- Makefile.am | 7 + src/confdb/confdb.h | 9 + src/man/sssd.conf.5.xml | 66 ++++++ src/responder/pam/pam_prompting_config.c | 275 +++++++++++++++++++++++ src/responder/pam/pamsrv.c | 16 +- src/responder/pam/pamsrv.h | 6 + src/responder/pam/pamsrv_cmd.c | 8 + 7 files changed, 386 insertions(+), 1 deletion(-) create mode 100644 src/responder/pam/pam_prompting_config.c diff --git a/Makefile.am b/Makefile.am index 72866cdb0c..17d6446073 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1589,8 +1589,13 @@ sssd_pam_SOURCES = \ src/responder/pam/pamsrv_cmd.c \ src/responder/pam/pamsrv_p11.c \ src/responder/pam/pamsrv_dp.c \ + src/responder/pam/pam_prompting_config.c \ + src/sss_client/pam_sss_prompt_config.c \ src/responder/pam/pam_helpers.c \ $(SSSD_RESPONDER_OBJ) +sssd_pam_CFLAGS = \ + $(AM_CFLAGS) \ + $(NULL) sssd_pam_LDADD = \ $(LIBADD_DL) \ $(TDB_LIBS) \ @@ -2721,6 +2726,8 @@ pam_srv_tests_SOURCES = \ src/responder/pam/pam_helpers.c \ src/responder/pam/pamsrv_dp.c \ src/responder/pam/pam_LOCAL_domain.c \ + src/responder/pam/pam_prompting_config.c \ + src/sss_client/pam_sss_prompt_config.c \ $(NULL) pam_srv_tests_CFLAGS = \ -U SSSD_LIBEXEC_PATH -DSSSD_LIBEXEC_PATH=\"$(abs_builddir)\" \ diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h index 338656b094..7c4ee2d7ab 100644 --- a/src/confdb/confdb.h +++ b/src/confdb/confdb.h @@ -275,6 +275,15 @@ #define CONFDB_CERTMAP_DOMAINS "domains" #define CONFDB_CERTMAP_PRIORITY "priority" +/* Prompting */ +#define CONFDB_PC_CONF_ENTRY "config/prompting" +#define CONFDB_PC_TYPE_PASSWORD "password" +#define CONFDB_PC_PASSWORD_PROMPT "password_prompt" +#define CONFDB_PC_TYPE_2FA "2fa" +#define CONFDB_PC_2FA_SINGLE_PROMPT "single_prompt" +#define CONFDB_PC_2FA_1ST_PROMPT "first_prompt" +#define CONFDB_PC_2FA_2ND_PROMPT "second_prompt" +#define CONFDB_PC_TYPE_CERT_AUTH "cert_auth" struct confdb_ctx; struct config_file_ctx; diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml index 699c5ffff1..c12fef7314 100644 --- a/src/man/sssd.conf.5.xml +++ b/src/man/sssd.conf.5.xml @@ -3579,6 +3579,72 @@ ldap_user_extra_attrs = phone:telephoneNumber </para> </refsect1> + <refsect1 id='prompting_configuration'> + <title>PROMPTING CONFIGURATION SECTION</title> + <para> + If a special file + (<filename>/var/lib/sss/pubconf/pam_preauth_available</filename>) + exists SSSD's PAM module pam_sss will ask SSSD to figure out which + authentication methods are available for the user trying to log in. + Based on the results pam_sss will prompt the user for appropriate + credentials. + </para> + <para> + With the growing number of authentication methods and the + possibility that there are multiple ones for a single user the + heuristic used by pam_sss to select the prompting might not be + suitable for all use cases. To following options should provide a + better flexibility here. + </para> + <para> + Each supported authentication method has it's own configuration + sub-section under <quote>[prompting/...]</quote>. Currently there + are: + <variablelist> + <varlistentry> + <term>[prompting/password]</term> + <listitem> + <para>to configure password prompting, allowed options are: + <variablelist><varlistentry><term>password_prompt</term> + <listitem><para>to change the string of the password + prompt</para></listitem></varlistentry></variablelist> + </para> + </listitem> + </varlistentry> + </variablelist> + <variablelist> + <varlistentry> + <term>[prompting/2fa]</term> + <listitem> + <para>to configure two-factor authentication prompting, + allowed options are: + <variablelist><varlistentry><term>first_prompt</term> + <listitem><para>to change the string of the prompt for + the first factor </para></listitem> + </varlistentry> + <varlistentry><term>second_prompt</term> + <listitem><para>to change the string of the prompt for + the second factor </para></listitem> + </varlistentry> + <varlistentry><term>single_prompt</term> + <listitem><para>boolean value, if True there will be + only a single prompt using the value of first_prompt + where it is expected that both factor are entered as a + single string</para></listitem> + </varlistentry> + </variablelist> + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + <para> + It is possible to add a sub-section for specific PAM services like + e.g. <quote>[prompting/password/sshd]</quote> to individual change + the prompting for this service. + </para> + </refsect1> + <refsect1 id='example'> <title>EXAMPLES</title> <para> diff --git a/src/responder/pam/pam_prompting_config.c b/src/responder/pam/pam_prompting_config.c new file mode 100644 index 0000000000..dc10e501c4 --- /dev/null +++ b/src/responder/pam/pam_prompting_config.c @@ -0,0 +1,275 @@ +/* + SSSD + + PAM Responder - helpers for PAM prompting configuration + + Copyright (C) Sumit Bose <sb...@redhat.com> 2019 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "util/sss_pam_data.h" +#include "confdb/confdb.h" +#include "sss_client/sss_cli.h" +#include "responder/pam/pamsrv.h" + +typedef errno_t (pam_set_prompting_fn_t)(TALLOC_CTX *, struct confdb_ctx *, + const char *, + struct prompt_config ***); + + +static errno_t pam_set_password_prompting_options(TALLOC_CTX *tmp_ctx, + struct confdb_ctx *cdb, + const char *section, + struct prompt_config ***pc_list) +{ + int ret; + char *value = NULL; + + ret = confdb_get_string(cdb, tmp_ctx, section, CONFDB_PC_PASSWORD_PROMPT, + NULL, &value); + if (ret == EOK && value != NULL) { + ret = pc_list_add_password(pc_list, value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pc_list_add_password failed.\n"); + } + return ret; + } + + return ENOENT; +} + +static errno_t pam_set_2fa_prompting_options(TALLOC_CTX *tmp_ctx, + struct confdb_ctx *cdb, + const char *section, + struct prompt_config ***pc_list) +{ + bool single_2fa_prompt = false; + char *first_prompt = NULL; + char *second_prompt = NULL; + int ret; + + + ret = confdb_get_bool(cdb, section, CONFDB_PC_2FA_SINGLE_PROMPT, false, + &single_2fa_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_bool failed, using defaults"); + } + ret = confdb_get_string(cdb, tmp_ctx, section, CONFDB_PC_2FA_1ST_PROMPT, + NULL, &first_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_string failed, using defaults"); + } + + if (single_2fa_prompt) { + ret = pc_list_add_2fa_single(pc_list, first_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pc_list_add_2fa_single failed.\n"); + } + return ret; + } else { + ret = confdb_get_string(cdb, tmp_ctx, section, CONFDB_PC_2FA_2ND_PROMPT, + NULL, &second_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "confdb_get_string failed, using defaults"); + } + + ret = pc_list_add_2fa(pc_list, first_prompt, second_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pc_list_add_2fa failed.\n"); + } + return ret; + } + + return ENOENT; +} + +static errno_t pam_set_prompting_options(struct confdb_ctx *cdb, + const char *service_name, + char **sections, + int num_sections, + const char *section_path, + pam_set_prompting_fn_t *setter, + struct prompt_config ***pc_list) +{ + char *dummy; + size_t c; + bool global = false; + bool specific = false; + char *section = NULL; + int ret; + char *last; + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + + dummy = talloc_asprintf(tmp_ctx, "%s/%s", section_path, + service_name); + for (c = 0; c < num_sections; c++) { + if (strcmp(sections[c], CONFDB_PC_TYPE_PASSWORD) == 0) { + global = true; + } + if (dummy != NULL && strcmp(sections[c], dummy) == 0) { + specific = true; + } + } + + section = talloc_asprintf(tmp_ctx, "%s/%s", CONFDB_PC_CONF_ENTRY, dummy); + if (section == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = ENOENT; + if (specific) { + ret = setter(tmp_ctx, cdb, section, pc_list); + } + if (global && ret == ENOENT) { + last = strrchr(section, '/'); + if (last != NULL) { + *last = '\0'; + ret = setter(tmp_ctx, cdb, section, pc_list); + } + } + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "setter failed.\n"); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t pam_eval_prompting_config(struct pam_ctx *pctx, struct pam_data *pd) +{ + int ret; + struct response_data *resp; + bool password_auth = false; + bool otp_auth = false; + bool cert_auth = false; + struct prompt_config **pc_list = NULL; + int resp_len; + uint8_t *resp_data = NULL; + + if (pctx->num_prompting_config_sections == 0) { + DEBUG(SSSDBG_TRACE_ALL, "No prompting configuration found.\n"); + return EOK; + } + + resp = pd->resp_list; + while (resp != NULL) { + switch (resp->type) { + case SSS_PAM_OTP_INFO: + otp_auth = true; + break; + case SSS_PAM_CERT_INFO: + cert_auth = true; + break; + case SSS_PASSWORD_PROMPTING: + password_auth = true; + break; + case SSS_CERT_AUTH_PROMPTING: + /* currently not used */ + break; + default: + break; + } + resp = resp->next; + } + + if (!password_auth && !otp_auth && !cert_auth) { + /* If the backend cannot determine which authentication types are + * available the default would be to prompt for a password. */ + password_auth = true; + } + + DEBUG(SSSDBG_TRACE_ALL, "Authentication types for user [%s] and service " + "[%s]:%s%s%s\n", pd->user, pd->service, + password_auth ? " password": "", + otp_auth ? " two-factor" : "", + cert_auth ? " smartcard" : ""); + + if (cert_auth) { + /* If certificate based authentication is possilbe, i.e. a Smartcard + * or similar with the mapped certificate is available we currently + * prefer this authentication type unconditionally. If other types + * should be used the Smartcard can be removed during authentication. + * Since there currently are no specific options for cert_auth we are + * done. */ + ret = EOK; + goto done; + } + + /* If OTP and password auth are possible we currently prefer OTP. */ + if (otp_auth) { + ret = pam_set_prompting_options(pctx->rctx->cdb, pd->service, + pctx->prompting_config_sections, + pctx->num_prompting_config_sections, + CONFDB_PC_TYPE_2FA, + pam_set_2fa_prompting_options, + &pc_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "pam_set_prompting_options failed.\n"); + goto done; + } + } + + if (password_auth) { + ret = pam_set_prompting_options(pctx->rctx->cdb, pd->service, + pctx->prompting_config_sections, + pctx->num_prompting_config_sections, + CONFDB_PC_TYPE_PASSWORD, + pam_set_password_prompting_options, + &pc_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "pam_set_prompting_options failed.\n"); + goto done; + } + } + + if (pc_list != NULL) { + ret = pam_get_response_prompt_config(pc_list, &resp_len, &resp_data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "pam_get_response_prompt_config failed.\n"); + goto done; + } + + ret = pam_add_response(pd, SSS_PAM_PROMPT_CONFIG, resp_len, resp_data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pam_add_response failed.\n"); + goto done; + } + } + + ret = EOK; +done: + free(resp_data); + pc_list_free(pc_list); + + return ret; +} diff --git a/src/responder/pam/pamsrv.c b/src/responder/pam/pamsrv.c index 6cfc5117b0..38db6fc9b0 100644 --- a/src/responder/pam/pamsrv.c +++ b/src/responder/pam/pamsrv.c @@ -254,6 +254,16 @@ static int pam_process_init(TALLOC_CTX *mem_ctx, goto done; } + /* Check if there is a prompting configuration */ + pctx->prompting_config_sections = NULL; + pctx->num_prompting_config_sections = 0; + ret = confdb_get_sub_sections(pctx, pctx->rctx->cdb, CONFDB_PC_CONF_ENTRY, + &pctx->prompting_config_sections, + &pctx->num_prompting_config_sections); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_sub_sections failed, not fatal.\n"); + } + /* Check if certificate based authentication is enabled */ ret = confdb_get_bool(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, @@ -285,11 +295,15 @@ static int pam_process_init(TALLOC_CTX *mem_ctx, goto done; } + } + + if (pctx->cert_auth || pctx->num_prompting_config_sections != 0) { ret = create_preauth_indicator(); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to create pre-authentication indicator file, " - "Smartcard authentication might not work as expected.\n"); + "Smartcard authentication or configured prompting might " + "not work as expected.\n"); } } diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h index 3a927bb39b..99c6e10a7d 100644 --- a/src/responder/pam/pamsrv.h +++ b/src/responder/pam/pamsrv.h @@ -51,6 +51,9 @@ struct pam_ctx { char *nss_db; struct sss_certmap_ctx *sss_certmap_ctx; char **smartcard_services; + + char **prompting_config_sections; + int num_prompting_config_sections; }; struct pam_auth_req { @@ -126,4 +129,7 @@ pam_set_last_online_auth_with_curr_token(struct sss_domain_info *domain, errno_t filter_responses(struct confdb_ctx *cdb, struct response_data *resp_list, struct pam_data *pd); + +errno_t pam_eval_prompting_config(struct pam_ctx *pctx, struct pam_data *pd); + #endif /* __PAMSRV_H__ */ diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c index 6c04cb157d..aabb58f64a 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -1008,6 +1008,14 @@ static void pam_reply(struct pam_auth_req *preq) } } + if (pd->cmd == SSS_PAM_PREAUTH) { + ret = pam_eval_prompting_config(pctx, pd); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add prompting information, " + "using defaults.\n"); + } + } + /* * Export non-overridden shell to tlog-rec-session when opening the session */ From d9464701d88320e31117969a8433b6e0d45d07fa Mon Sep 17 00:00:00 2001 From: Sumit Bose <sb...@redhat.com> Date: Fri, 29 Mar 2019 10:38:50 +0100 Subject: [PATCH 5/5] intg: add test for password prompt configuration Related to Related to https://pagure.io/SSSD/sssd/issue/3264 --- src/tests/intg/Makefile.am | 9 +- src/tests/intg/test_pam_responder.py | 130 +++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) diff --git a/src/tests/intg/Makefile.am b/src/tests/intg/Makefile.am index 25093b5036..2aa1566e35 100644 --- a/src/tests/intg/Makefile.am +++ b/src/tests/intg/Makefile.am @@ -113,6 +113,13 @@ pam_sss_service: echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ +pam_sss_alt_service: + $(MKDIR_P) $(PAM_SERVICE_DIR) + echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so" > $(PAM_SERVICE_DIR)/$@ + echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + pam_sss_sc_required: $(MKDIR_P) $(PAM_SERVICE_DIR) echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so require_cert_auth retry=1" > $(PAM_SERVICE_DIR)/$@ @@ -141,7 +148,7 @@ PAM_CERT_DB_PATH="$(abs_builddir)/../test_CA/SSSD_test_CA.pem" SOFTHSM2_CONF="$(abs_builddir)/../test_CA/softhsm2_one.conf" endif -intgcheck-installed: config.py passwd group pam_sss_service pam_sss_sc_required pam_sss_try_sc +intgcheck-installed: config.py passwd group pam_sss_service pam_sss_alt_service pam_sss_sc_required pam_sss_try_sc pipepath="$(DESTDIR)$(pipepath)"; \ if test $${#pipepath} -gt 80; then \ echo "error: Pipe directory path too long," \ diff --git a/src/tests/intg/test_pam_responder.py b/src/tests/intg/test_pam_responder.py index d1ad9affd0..8e1fcf126e 100644 --- a/src/tests/intg/test_pam_responder.py +++ b/src/tests/intg/test_pam_responder.py @@ -30,12 +30,82 @@ import shutil import config +import intg.ds_openldap import pytest from intg.util import unindent from intg.files_ops import passwd_ops_setup +LDAP_BASE_DN = "dc=example,dc=com" + + +@pytest.fixture(scope="module") +def ad_inst(request): + """Fake AD server instance fixture""" + instance = intg.ds_openldap.FakeAD( + config.PREFIX, 10389, LDAP_BASE_DN, + "cn=admin", "Secret123" + ) + + try: + instance.setup() + except: + instance.teardown() + raise + request.addfinalizer(instance.teardown) + return instance + + +@pytest.fixture(scope="module") +def ldap_conn(request, ad_inst): + """LDAP server connection fixture""" + ldap_conn = ad_inst.bind() + ldap_conn.ad_inst = ad_inst + request.addfinalizer(ldap_conn.unbind_s) + return ldap_conn + + +def format_basic_conf(ldap_conn): + """Format a basic SSSD configuration""" + return unindent("""\ + [sssd] + domains = FakeAD + services = pam, nss + + [nss] + + [pam] + debug_level = 10 + + [domain/FakeAD] + debug_level = 10 + ldap_search_base = {ldap_conn.ad_inst.base_dn} + ldap_referrals = false + + id_provider = ldap + auth_provider = ldap + chpass_provider = ldap + access_provider = ldap + + ldap_uri = {ldap_conn.ad_inst.ldap_url} + ldap_default_bind_dn = {ldap_conn.ad_inst.admin_dn} + ldap_default_authtok_type = password + ldap_default_authtok = {ldap_conn.ad_inst.admin_pw} + + ldap_schema = ad + ldap_id_mapping = true + ldap_idmap_default_domain_sid = S-1-5-21-1305200397-2901131868-73388776 + case_sensitive = False + + [prompting/password] + password_prompt = My global prompt + + [prompting/password/pam_sss_alt_service] + password_prompt = My alt service prompt + """).format(**locals()) + + USER1 = dict(name='user1', passwd='x', uid=10001, gid=20001, gecos='User for tests', dir='/home/user1', @@ -220,6 +290,66 @@ def test_preauth_indicator(simple_pam_cert_auth): assert stat.S_ISREG(statinfo.st_mode) +@pytest.fixture +def pam_prompting_config(request, ldap_conn): + """Setup SSSD with PAM prompting config""" + conf = format_basic_conf(ldap_conn) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_password_prompting_config_global(ldap_conn, pam_prompting_config, + env_for_sssctl): + """Check global change of the password prompt""" + + sssctl = subprocess.Popen(["sssctl", "user-checks", "user1_dom1-19661", + "--action=auth", "--service=pam_sss_service"], + universal_newlines=True, + env=env_for_sssctl, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + out, err = sssctl.communicate(input="111") + except: + sssctl.kill() + out, err = sssctl.communicate() + + sssctl.stdin.close() + sssctl.stdout.close() + + if sssctl.wait() != 0: + raise Exception("sssctl failed") + + assert err.find("My global prompt") != -1 + + +def test_password_prompting_config_srv(ldap_conn, pam_prompting_config, + env_for_sssctl): + """Check change of the password prompt for dedicated service""" + + sssctl = subprocess.Popen(["sssctl", "user-checks", "user1_dom1-19661", + "--action=auth", + "--service=pam_sss_alt_service"], + universal_newlines=True, + env=env_for_sssctl, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + out, err = sssctl.communicate(input="111") + except: + sssctl.kill() + out, err = sssctl.communicate() + + sssctl.stdin.close() + sssctl.stdout.close() + + if sssctl.wait() != 0: + raise Exception("sssctl failed") + + assert err.find("My alt service prompt") != -1 + + @pytest.fixture def env_for_sssctl(request): pwrap_runtimedir = os.getenv("PAM_WRAPPER_SERVICE_DIR")
_______________________________________________ sssd-devel mailing list -- sssd-devel@lists.fedorahosted.org To unsubscribe send an email to sssd-devel-le...@lists.fedorahosted.org Fedora Code of Conduct: https://getfedora.org/code-of-conduct.html List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines List Archives: https://lists.fedorahosted.org/archives/list/sssd-devel@lists.fedorahosted.org