From 8e8b242401e82631e47699f669c9ea99381c498c Mon Sep 17 00:00:00 2001 From: Philippe Smadja Date: Thu, 12 Jun 2025 16:18:47 +0200 Subject: [PATCH 1/8] Passkey User Verification device Initial Revision - support of Passkey User Veification device (fingerprint). Fallback to PIN if the verification fails or the device does not support user verification. --- src/passkey_child/passkey_child.c | 218 +- src/passkey_child/passkey_child.h | 16 + src/passkey_child/passkey_child_assert.c | 1014 +-- src/passkey_child/passkey_child_common.c | 1862 ++--- src/passkey_child/passkey_child_credentials.c | 1371 ++-- src/passkey_child/passkey_child_devices.c | 492 +- src/responder/pam/pamsrv_cmd.c | 6331 ++++++++-------- src/responder/pam/pamsrv_passkey.c | 3342 ++++---- src/responder/pam/pamsrv_passkey.h | 189 +- src/sss_client/pam_message.h | 161 +- src/sss_client/pam_sss.c | 6708 +++++++++-------- src/sss_client/sss_cli.h | 1638 ++-- 12 files changed, 12152 insertions(+), 11190 deletions(-) diff --git a/src/passkey_child/passkey_child.c b/src/passkey_child/passkey_child.c index 320996608ec..3ca35dd02a7 100644 --- a/src/passkey_child/passkey_child.c +++ b/src/passkey_child/passkey_child.c @@ -1,106 +1,112 @@ -/* - SSSD - - Helper child to commmunicate with passkey devices - - Authors: - Iker Pedrosa - - Copyright (C) 2022 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 . -*/ - -#include -#include - -#include "util/debug.h" -#include "util/util.h" - -#include "passkey_child.h" - -int main(int argc, const char *argv[]) -{ - TALLOC_CTX *main_ctx = NULL; - struct passkey_data data; - int init_flags = 0; - errno_t ret = EOK; - - main_ctx = talloc_new(NULL); - if (main_ctx == NULL) { - ERROR("talloc_new() failed.\n"); - talloc_free(discard_const(debug_prg_name)); - ret = ENOMEM; - goto done; - } - - ret = parse_arguments(main_ctx, argc, argv, &data); - if (ret != EOK) { - ERROR("Error parsing argument(s).\n"); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "passkey_child started.\n"); - talloc_steal(main_ctx, debug_prg_name); - - ret = check_arguments(&data); - if (ret != EOK) { - ERROR("Invalid argument(s).\n"); - goto done; - } - - init_flags = (int)data.debug_libfido2 | FIDO_DISABLE_U2F_FALLBACK; - fido_init(init_flags); - - if (data.action == ACTION_REGISTER) { - ret = register_key(&data); - if (ret != EOK) { - ERROR("Error registering key.\n"); - goto done; - } - } else if (data.action == ACTION_AUTHENTICATE) { - ret = authenticate(&data); - if (ret == EOK) { - PRINT("Authentication success.\n"); - goto done; - } else { - ERROR("Authentication error.\n"); - goto done; - } - } else if (data.action == ACTION_GET_ASSERT) { - ret = get_assert_data(&data); - if (ret != EOK) { - ERROR("Error getting assertion data.\n"); - goto done; - } - } else if (data.action == ACTION_VERIFY_ASSERT) { - ret = verify_assert_data(&data); - if (ret == EOK) { - PRINT("Verification success.\n"); - goto done; - } else { - ERROR("Verification error.\n"); - goto done; - } - } - -done: - talloc_free(main_ctx); - - if (ret != EOK) { - return EXIT_FAILURE; - } else { - return EXIT_SUCCESS; - } -} +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa + + Copyright (C) 2022 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 . +*/ + +#include +#include + +#include "util/debug.h" +#include "util/util.h" + +#include "passkey_child.h" + +int main(int argc, const char *argv[]) +{ + TALLOC_CTX *main_ctx = NULL; + struct passkey_data data; + int init_flags = 0; + errno_t ret = EOK; + + main_ctx = talloc_new(NULL); + if (main_ctx == NULL) { + ERROR("talloc_new() failed.\n"); + talloc_free(discard_const(debug_prg_name)); + ret = ENOMEM; + goto done; + } + + ret = parse_arguments(main_ctx, argc, argv, &data); + if (ret != EOK) { + ERROR("Error parsing argument(s).\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "passkey_child started.\n"); + talloc_steal(main_ctx, debug_prg_name); + + ret = check_arguments(&data); + if (ret != EOK) { + ERROR("Invalid argument(s).\n"); + goto done; + } + + init_flags = (int)data.debug_libfido2 | FIDO_DISABLE_U2F_FALLBACK; + fido_init(init_flags); + + if (data.action == ACTION_REGISTER) { + ret = register_key(&data); + if (ret != EOK) { + ERROR("Error registering key.\n"); + goto done; + } + } else if (data.action == ACTION_AUTHENTICATE) { + ret = authenticate(&data); + if (ret == EOK) { + PRINT("Authentication success.\n"); + goto done; + } else { + ERROR("Authentication error.\n"); + goto done; + } + } else if (data.action == ACTION_GET_ASSERT) { + ret = get_assert_data(&data); + if (ret != EOK) { + ERROR("Error getting assertion data.\n"); + goto done; + } + } else if (data.action == ACTION_GET_DEVINFO) { + ret = get_device_info(&data); + if (ret != EOK) { + ERROR("Error getting device information.\n"); + goto done; + } + } else if (data.action == ACTION_VERIFY_ASSERT) { + ret = verify_assert_data(&data); + if (ret == EOK) { + PRINT("Verification success.\n"); + goto done; + } else { + ERROR("Verification error.\n"); + goto done; + } + } + +done: + talloc_free(main_ctx); + + if (ret != EOK) { + return EXIT_FAILURE; + } else { + return EXIT_SUCCESS; + } +} diff --git a/src/passkey_child/passkey_child.h b/src/passkey_child/passkey_child.h index 185e7e63af7..e0d35397770 100644 --- a/src/passkey_child/passkey_child.h +++ b/src/passkey_child/passkey_child.h @@ -40,6 +40,7 @@ enum action_opt { ACTION_REGISTER, ACTION_AUTHENTICATE, ACTION_GET_ASSERT, + ACTION_GET_DEVINFO, ACTION_VERIFY_ASSERT }; @@ -540,6 +541,21 @@ print_assert_data(const char *key_handle, const char *crypto_challenge, errno_t get_assert_data(struct passkey_data *data); +/** + * @brief Obtain device information + * + * Obtain information of the device supporting one of the handles in parameters + * + * @param[in] data passkey data + * + * @return 0 if a device is available + * create file /var/run/passkey-pinonly or passkey-pinuv + * error code otherwise. + */ +errno_t +get_device_info(struct passkey_data *data); + + /** * @brief Verify assertion data * diff --git a/src/passkey_child/passkey_child_assert.c b/src/passkey_child/passkey_child_assert.c index 5139dc82ef0..55056c61ab5 100644 --- a/src/passkey_child/passkey_child_assert.c +++ b/src/passkey_child/passkey_child_assert.c @@ -1,448 +1,566 @@ -/* - SSSD - - Helper child to commmunicate with passkey devices - - Authors: - Iker Pedrosa - - Copyright (C) 2022 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 . -*/ - -#include -#include -#include -#include -#include -#include - -#include "util/crypto/sss_crypto.h" -#include "util/debug.h" -#include "util/util.h" - -#include "passkey_child.h" - -errno_t -set_assert_client_data_hash(const struct passkey_data *data, - fido_assert_t *_assert) -{ - TALLOC_CTX *tmp_ctx = NULL; - unsigned char cdh[32]; - unsigned char *crypto_challenge = NULL; - size_t crypto_challenge_len; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - return ENOMEM; - } - - if (data->action == ACTION_AUTHENTICATE) { - ret = sss_generate_csprng_buffer(cdh, sizeof(cdh)); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "sss_generate_csprng_buffer failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - ret = fido_assert_set_clientdata_hash(_assert, cdh, sizeof(cdh)); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_clientdata_hash failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - } else { - crypto_challenge = sss_base64_decode(tmp_ctx, data->crypto_challenge, - &crypto_challenge_len); - if (crypto_challenge == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "failed to decode client data hash.\n"); - ret = ENOMEM; - goto done; - } - - if (crypto_challenge_len != 32) { - DEBUG(SSSDBG_OP_FAILURE, - "cryptographic-challenge length [%ld] must be 32.\n", - crypto_challenge_len); - ret = EINVAL; - goto done; - } - - ret = fido_assert_set_clientdata_hash(_assert, crypto_challenge, - crypto_challenge_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_clientdata_hash failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - } - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -set_assert_options(fido_opt_t up, fido_opt_t uv, fido_assert_t *_assert) -{ - errno_t ret; - - ret = fido_assert_set_up(_assert, up); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_up failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - ret = fido_assert_set_uv(_assert, uv); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_uv failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - -done: - return ret; -} - -errno_t -get_assert_auth_data_signature(TALLOC_CTX *mem_ctx, fido_assert_t *assert, - const char **_auth_data, - const char **_signature) -{ - TALLOC_CTX *tmp_ctx = NULL; - const unsigned char *auth_data; - const unsigned char *signature; - const char *b64_auth_data; - const char *b64_signature; - size_t auth_data_len; - size_t signature_len; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - return ENOMEM; - } - - auth_data = fido_assert_authdata_ptr(assert, 0); - if (auth_data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_authdata_ptr failed.\n"); - ret = ENOMEM; - goto done; - } - - auth_data_len = fido_assert_authdata_len(assert, 0); - if (auth_data_len == 0) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_authdata_len failed.\n"); - ret = ENOMEM; - goto done; - } - - b64_auth_data = sss_base64_encode(tmp_ctx, auth_data, auth_data_len); - if (b64_auth_data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "failed to encode authenticator data.\n"); - ret = ENOMEM; - goto done; - } - - signature = fido_assert_sig_ptr(assert, 0); - if (signature == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_sig_ptr failed.\n"); - ret = ENOMEM; - goto done; - } - - signature_len = fido_assert_sig_len(assert, 0); - if (signature_len == 0) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_sig_len failed.\n"); - ret = ENOMEM; - goto done; - } - - b64_signature = sss_base64_encode(tmp_ctx, signature, signature_len); - if (b64_signature == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "failed to encode signature.\n"); - ret = ENOMEM; - goto done; - } - - *_auth_data = talloc_steal(mem_ctx, b64_auth_data); - *_signature = talloc_steal(mem_ctx, b64_signature); - ret = EOK; - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -set_assert_auth_data_signature(const struct passkey_data *data, - fido_assert_t *_assert) -{ - TALLOC_CTX *tmp_ctx = NULL; - const unsigned char *auth_data = NULL; - const unsigned char *signature = NULL; - size_t auth_data_len; - size_t signature_len; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - ret = fido_assert_set_count(_assert, 1); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_count failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - auth_data = sss_base64_decode(tmp_ctx, data->auth_data, &auth_data_len); - if (auth_data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "failed to decode authenticator data.\n"); - ret = ENOMEM; - goto done; - } - - ret = fido_assert_set_authdata(_assert, 0, auth_data, auth_data_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_authdata failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - signature = sss_base64_decode(tmp_ctx, data->signature, &signature_len); - if (signature == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "failed to decode signature.\n"); - ret = ENOMEM; - goto done; - } - - ret = fido_assert_set_sig(_assert, 0, signature, signature_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_sig failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -prepare_assert(const struct passkey_data *data, int index, - fido_assert_t *_assert) -{ - TALLOC_CTX *tmp_ctx = NULL; - unsigned char *key_handle; - size_t key_handle_len; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - ret = fido_assert_set_rp(_assert, data->domain); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_rp failed [%d]: %s.\n", ret, fido_strerr(ret)); - goto done; - } - - key_handle = sss_base64_decode(tmp_ctx, data->key_handle_list[index], - &key_handle_len); - if (key_handle == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to decode key handle.\n"); - ret = ENOMEM; - goto done; - } - - ret = fido_assert_allow_cred(_assert, key_handle, key_handle_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_allow_cred failed [%d]: %s.\n", ret, fido_strerr(ret)); - goto done; - } - - ret = set_assert_options(FIDO_OPT_FALSE, FIDO_OPT_OMIT, _assert); - if (ret != FIDO_OK) { - goto done; - } - - ret = set_assert_client_data_hash(data, _assert); - if (ret != EOK) { - goto done; - } - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -reset_public_key(struct pk_data_t *_pk_data) -{ - if (_pk_data->type == COSE_ES256) { - es256_pk_free((es256_pk_t **) &_pk_data->public_key); - } else if (_pk_data->type == COSE_RS256) { - rs256_pk_free((rs256_pk_t **) &_pk_data->public_key); - } else if (_pk_data->type == COSE_EDDSA) { - eddsa_pk_free((eddsa_pk_t **) &_pk_data->public_key); - } - - return EOK; -} - -errno_t -request_assert(struct passkey_data *data, fido_dev_t *dev, - fido_assert_t *_assert) -{ - TALLOC_CTX *tmp_ctx = NULL; - char *pin = NULL; - bool has_pin; - bool has_uv; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - return ENOMEM; - } - - has_pin = fido_dev_has_pin(dev); - has_uv = fido_dev_has_uv(dev); - if (has_uv == true && data->user_verification != FIDO_OPT_FALSE) { - ret = fido_dev_get_assert(dev, _assert, NULL); - if (ret != FIDO_OK && has_pin == true) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_dev_get_assert failed [%d]: %s. " - "Falling back to PIN authentication.\n", - ret, fido_strerr(ret)); - } else if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } else { - DEBUG(SSSDBG_TRACE_FUNC, "fido_dev_get_assert succeeded.\n"); - goto done; - } - } - - if (has_pin == true && data->user_verification != FIDO_OPT_FALSE) { - ret = passkey_recv_pin(tmp_ctx, STDIN_FILENO, &pin); - if (ret != EOK) { - goto done; - } - } - - ret = fido_dev_get_assert(dev, _assert, pin); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - ret = fido_assert_set_uv(_assert, data->user_verification); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_uv failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - -done: - if (pin != NULL) { - sss_erase_mem_securely(pin, strlen(pin)); - } - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -verify_assert(struct pk_data_t *pk_data, fido_assert_t *assert) -{ - errno_t ret; - - ret = fido_assert_verify(assert, 0, pk_data->type, pk_data->public_key); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_assert_verify failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - -done: - return ret; -} - -void -print_assert_data(const char *key_handle, const char *crypto_challenge, - const char *auth_data, const char *signature) -{ - json_t *passkey = NULL; - char* string = NULL; - - /* Kerberos expects the user_id field, thus it cannot be removed and there - * is nothing to set so it's an empty string. - */ - passkey = json_pack("{s:s*, s:s*, s:s*, s:s*, s:s*}", - "credential_id", key_handle, - "cryptographic_challenge", crypto_challenge, - "authenticator_data", auth_data, - "assertion_signature", signature, - "user_id", ""); - if (passkey == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to create passkey object.\n"); - goto done; - } - - string = json_dumps(passkey, 0); - if (string == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "json_dumps() failed.\n"); - goto done; - } - - puts(string); - free(string); - -done: - json_decref(passkey); - - return; -} +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa + + Copyright (C) 2022 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "util/crypto/sss_crypto.h" +#include "util/debug.h" +#include "util/util.h" + +#include "passkey_child.h" + +errno_t +set_assert_client_data_hash(const struct passkey_data *data, + fido_assert_t *_assert) +{ + TALLOC_CTX *tmp_ctx = NULL; + unsigned char cdh[32]; + unsigned char *crypto_challenge = NULL; + size_t crypto_challenge_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + if (data->action == ACTION_AUTHENTICATE) { + ret = sss_generate_csprng_buffer(cdh, sizeof(cdh)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_generate_csprng_buffer failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + ret = fido_assert_set_clientdata_hash(_assert, cdh, sizeof(cdh)); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_clientdata_hash failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } else if (data->action == ACTION_GET_ASSERT) { + crypto_challenge = sss_base64_decode(tmp_ctx, data->crypto_challenge, + &crypto_challenge_len); + if (crypto_challenge == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to decode client data hash.\n"); + ret = ENOMEM; + goto done; + } + + if (crypto_challenge_len != 32) { + DEBUG(SSSDBG_OP_FAILURE, + "cryptographic-challenge length [%ld] must be 32.\n", + crypto_challenge_len); + ret = EINVAL; + goto done; + } + + ret = fido_assert_set_clientdata_hash(_assert, crypto_challenge, + crypto_challenge_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_clientdata_hash failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } + else { + memset(cdh, 0, sizeof(cdh)); + ret = fido_assert_set_clientdata_hash(_assert, cdh, sizeof(cdh)); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_clientdata_hash failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +set_assert_options(fido_opt_t up, fido_opt_t uv, fido_assert_t *_assert) +{ + errno_t ret; + + ret = fido_assert_set_up(_assert, up); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_up failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + ret = fido_assert_set_uv(_assert, uv); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_uv failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + +done: + return ret; +} + +errno_t +get_assert_auth_data_signature(TALLOC_CTX *mem_ctx, fido_assert_t *assert, + const char **_auth_data, + const char **_signature) +{ + TALLOC_CTX *tmp_ctx = NULL; + const unsigned char *auth_data; + const unsigned char *signature; + const char *b64_auth_data; + const char *b64_signature; + size_t auth_data_len; + size_t signature_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + auth_data = fido_assert_authdata_ptr(assert, 0); + if (auth_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_authdata_ptr failed.\n"); + ret = ENOMEM; + goto done; + } + + auth_data_len = fido_assert_authdata_len(assert, 0); + if (auth_data_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_authdata_len failed.\n"); + ret = ENOMEM; + goto done; + } + + b64_auth_data = sss_base64_encode(tmp_ctx, auth_data, auth_data_len); + if (b64_auth_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to encode authenticator data.\n"); + ret = ENOMEM; + goto done; + } + + signature = fido_assert_sig_ptr(assert, 0); + if (signature == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_sig_ptr failed.\n"); + ret = ENOMEM; + goto done; + } + + signature_len = fido_assert_sig_len(assert, 0); + if (signature_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_sig_len failed.\n"); + ret = ENOMEM; + goto done; + } + + b64_signature = sss_base64_encode(tmp_ctx, signature, signature_len); + if (b64_signature == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to encode signature.\n"); + ret = ENOMEM; + goto done; + } + + *_auth_data = talloc_steal(mem_ctx, b64_auth_data); + *_signature = talloc_steal(mem_ctx, b64_signature); + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +set_assert_auth_data_signature(const struct passkey_data *data, + fido_assert_t *_assert) +{ + TALLOC_CTX *tmp_ctx = NULL; + const unsigned char *auth_data = NULL; + const unsigned char *signature = NULL; + size_t auth_data_len; + size_t signature_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = fido_assert_set_count(_assert, 1); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_count failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + auth_data = sss_base64_decode(tmp_ctx, data->auth_data, &auth_data_len); + if (auth_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to decode authenticator data.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_assert_set_authdata(_assert, 0, auth_data, auth_data_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_authdata failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + signature = sss_base64_decode(tmp_ctx, data->signature, &signature_len); + if (signature == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to decode signature.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_assert_set_sig(_assert, 0, signature, signature_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_sig failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +prepare_assert(const struct passkey_data *data, int index, + fido_assert_t *_assert) +{ + TALLOC_CTX *tmp_ctx = NULL; + unsigned char *key_handle; + size_t key_handle_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = fido_assert_set_rp(_assert, data->domain); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_rp failed [%d]: %s.\n", ret, fido_strerr(ret)); + goto done; + } + + key_handle = sss_base64_decode(tmp_ctx, data->key_handle_list[index], + &key_handle_len); + if (key_handle == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to decode key handle.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_assert_allow_cred(_assert, key_handle, key_handle_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_allow_cred failed [%d]: %s.\n", ret, fido_strerr(ret)); + goto done; + } + + ret = set_assert_options(FIDO_OPT_FALSE, FIDO_OPT_OMIT, _assert); + if (ret != FIDO_OK) { + goto done; + } + + ret = set_assert_client_data_hash(data, _assert); + if (ret != EOK) { + goto done; + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +reset_public_key(struct pk_data_t *_pk_data) +{ + if (_pk_data->type == COSE_ES256) { + es256_pk_free((es256_pk_t **) &_pk_data->public_key); + } else if (_pk_data->type == COSE_RS256) { + rs256_pk_free((rs256_pk_t **) &_pk_data->public_key); + } else if (_pk_data->type == COSE_EDDSA) { + eddsa_pk_free((eddsa_pk_t **) &_pk_data->public_key); + } + + return EOK; +} +#define DOPIN "/var/run/passkey-dopin" + +static +errno_t enable_dopin() +{ + int fd = creat (DOPIN, (mode_t)0000); + DEBUG(SSSDBG_TRACE_LIBS, "enable_dopin file [%s] fd=[%d], errno: [%d].\n", + DOPIN , fd, (fd == -1)? errno : 0); + close(fd); + + return EOK; +} + +static +errno_t disable_dopin() +{ + int ret = remove(DOPIN); + DEBUG(SSSDBG_TRACE_LIBS, "disable_dopin [%d]\n", ret); + return EOK; +} +#undef DOPIN + +errno_t +request_assert(struct passkey_data *data, fido_dev_t *dev, + fido_assert_t *_assert) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *pin = NULL; + bool has_pin; + bool has_uv; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + has_pin = fido_dev_has_pin(dev); + has_uv = fido_dev_has_uv(dev); + + if (has_pin == true && data->user_verification != FIDO_OPT_FALSE) { + ret = passkey_recv_pin(tmp_ctx, STDIN_FILENO, &pin); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "PIN is missing: try UV\n"); + ret = FIDO_ERR_PIN_REQUIRED; + /* and try UV that will reset the ret code. + */ + } else { + /* DEBUG(SSSDBG_TRACE_FUNC, "Proceed with PIN [%s]\n", pin); */ + /* ------------- */ + + ret = fido_dev_get_assert(dev, _assert, pin); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert PIN failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "fido_dev_get_assert PIN succeeded.\n"); + + ret = fido_assert_set_uv(_assert, data->user_verification); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_uv failed [%d]: %s.\n", + ret, fido_strerr(ret)); + } + /* error or not: finished */ + goto done; + } + } + + if (has_uv == true && data->user_verification != FIDO_OPT_FALSE) { + + ret = fido_dev_get_assert(dev, _assert, NULL); + if (ret != FIDO_OK && has_pin == true) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_dev_get_assert UV failed [%d]: %s. " + "User shall fall back to PIN authentication.\n", + ret, fido_strerr(ret)); + } else if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert UV failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "fido_dev_get_assert UV succeeded.\n"); + } + /* error not: finished */ + } + +done: + if (pin != NULL) { + sss_erase_mem_securely(pin, strlen(pin)); + } + talloc_free(tmp_ctx); + + if (ret == FIDO_OK && has_uv == true) { + /* PIN or UV has been OK */ + disable_dopin(); + } else if (ret == FIDO_ERR_PIN_REQUIRED || ret == FIDO_ERR_UV_INVALID || + ret == FIDO_ERR_PIN_INVALID || ret == FIDO_ERR_UV_BLOCKED) { + enable_dopin(); + } + return ret; +} + +/* + * + * errno_t + * request_assert(struct passkey_data *data, fido_dev_t *dev, + * fido_assert_t *_assert) + * { + * TALLOC_CTX *tmp_ctx = NULL; + * char *pin = NULL; + * bool has_pin; + * bool has_uv; + * errno_t ret; + * + * tmp_ctx = talloc_new(NULL); + * if (tmp_ctx == NULL) { + * DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + * return ENOMEM; + * } + * + * has_pin = fido_dev_has_pin(dev); + * has_uv = fido_dev_has_uv(dev); + * if (has_uv == true && data->user_verification != FIDO_OPT_FALSE) { + * ret = fido_dev_get_assert(dev, _assert, NULL); + * if (ret != FIDO_OK && has_pin == true) { + * DEBUG(SSSDBG_OP_FAILURE, + * "fido_dev_get_assert failed [%d]: %s. " + * "Falling back to PIN authentication.\n", + * ret, fido_strerr(ret)); + * } else if (ret != FIDO_OK) { + * DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert failed [%d]: %s.\n", + * ret, fido_strerr(ret)); + * goto done; + * } else { + * DEBUG(SSSDBG_TRACE_FUNC, "fido_dev_get_assert succeeded.\n"); + * goto done; + * } + * } + * + * if (has_pin == true && data->user_verification != FIDO_OPT_FALSE) { + * ret = passkey_recv_pin(tmp_ctx, STDIN_FILENO, &pin); + * if (ret != EOK) { + * goto done; + * } + * } + * + * ret = fido_deev_get_assert(dev, _assert, pin); + * if (ret != FIDO_OK) { + * DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert failed [%d]: %s.\n", + * ret, fido_strerr(ret)); + * goto done; + * } + * + * ret = fido_assert_set_uv(_assert, data->user_verification); + * if (ret != FIDO_OK) { + * DEBUG(SSSDBG_OP_FAILURE, + * "fido_assert_set_uv failed [%d]: %s.\n", + * ret, fido_strerr(ret)); + * goto done; + * } + * + * done: + * if (pin != NULL) { + * sss_erase_mem_securely(pin, strlen(pin)); + * } + * talloc_free(tmp_ctx); + * + * return ret; + * } + */ + +errno_t +verify_assert(struct pk_data_t *pk_data, fido_assert_t *assert) +{ + errno_t ret; + + ret = fido_assert_verify(assert, 0, pk_data->type, pk_data->public_key); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_assert_verify failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + +done: + return ret; +} + +void +print_assert_data(const char *key_handle, const char *crypto_challenge, + const char *auth_data, const char *signature) +{ + json_t *passkey = NULL; + char* string = NULL; + + /* Kerberos expects the user_id field, thus it cannot be removed and there + * is nothing to set so it's an empty string. + */ + passkey = json_pack("{s:s*, s:s*, s:s*, s:s*, s:s*}", + "credential_id", key_handle, + "cryptographic_challenge", crypto_challenge, + "authenticator_data", auth_data, + "assertion_signature", signature, + "user_id", ""); + if (passkey == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create passkey object.\n"); + goto done; + } + + string = json_dumps(passkey, 0); + if (string == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "json_dumps() failed.\n"); + goto done; + } + + puts(string); + free(string); + +done: + json_decref(passkey); + + return; +} diff --git a/src/passkey_child/passkey_child_common.c b/src/passkey_child/passkey_child_common.c index 6fadd024333..83eeeb5d821 100644 --- a/src/passkey_child/passkey_child_common.c +++ b/src/passkey_child/passkey_child_common.c @@ -1,889 +1,973 @@ -/* - SSSD - - Helper child to commmunicate with passkey devices - - Authors: - Iker Pedrosa - - Copyright (C) 2022 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 . -*/ - -#include -#include -#include -#include - -#include -#include - -#include "util/crypto/sss_crypto.h" -#include "util/debug.h" -#include "util/util.h" -#include "util/crypto/sss_crypto.h" -#include "util/sss_prctl.h" - -#include "passkey_child.h" - -#if OPENSSL_VERSION_NUMBER >= 0x30000000 -#define get_id(x) EVP_PKEY_get_base_id((x)) -#else -#define get_id(x) EVP_PKEY_base_id((x)) -#endif /* OPENSSL_VERSION_NUMBER */ - -errno_t -cose_str_to_int(const char *type, int *out) -{ - if (strcasecmp(type, "es256") == 0) { - *out = COSE_ES256; - } else if (strcasecmp(type, "rs256") == 0) { - *out = COSE_RS256; - } else if (strcasecmp(type, "eddsa") == 0) { - *out = COSE_EDDSA; - } else { - *out = 0; - return ERR_INVALID_CRED_TYPE; - } - - return EOK; -} - -static errno_t -cred_type_str_to_enum(const char *type, enum credential_type *out) -{ - if (strcasecmp(type, "server-side") == 0) { - *out = CRED_SERVER_SIDE; - } else if (strcasecmp(type, "discoverable") == 0) { - *out = CRED_DISCOVERABLE; - } else { - *out = 0; - return ERR_INVALID_CRED_TYPE; - } - - return EOK; -} - -static errno_t -parse_public_keys_and_handlers(TALLOC_CTX *mem_ctx, - const char *public_keys, - const char *key_handles, - struct passkey_data *_data) -{ - TALLOC_CTX *tmp_ctx = NULL; - char **pk_list = NULL; - char **kh_list = NULL; - int pk_num = 0; - int kh_num = 0; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ERROR("talloc_new() failed\n"); - return ENOMEM; - } - - ret = split_on_separator(tmp_ctx, public_keys, ',', true, true, &pk_list, &pk_num); - if (ret != EOK && _data->action == ACTION_AUTHENTICATE) { - ERROR("Incorrectly formatted public keys.\n"); - goto done; - } - - ret = split_on_separator(tmp_ctx, key_handles, ',', true, true, &kh_list, &kh_num); - if (ret != EOK) { - ERROR("Incorrectly formatted public keys.\n"); - goto done; - } - - if (_data->action == ACTION_AUTHENTICATE && pk_num != kh_num) { - ERROR("The number of public keys and key handles don't match.\n"); - goto done; - } - - _data->public_key_list = talloc_steal(mem_ctx, pk_list); - _data->key_handle_list = talloc_steal(mem_ctx, kh_list); - _data->public_key_size = pk_num; - _data->key_handle_size = kh_num; - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -parse_arguments(TALLOC_CTX *mem_ctx, int argc, const char *argv[], - struct passkey_data *data) -{ - int opt; - int dumpable = 1; - int backtrace = 1; - int debug_fd = -1; - char *user_verification = NULL; - char *public_keys = NULL; - char *key_handles = NULL; - const char *opt_logger = NULL; - const char *type = NULL; - const char *cred_type = NULL; - poptContext pc; - errno_t ret; - - /* Set defaults */ - data->action = ACTION_NONE; - data->shortname = NULL; - data->domain = NULL; - data->public_key_list = NULL; - data->key_handle_list = NULL; - data->public_key_size = 0; - data->key_handle_size = 0; - data->crypto_challenge = NULL; - data->auth_data = NULL; - data->signature = NULL; - data->type = COSE_ES256; - data->user_verification = FIDO_OPT_OMIT; - data->cred_type = CRED_SERVER_SIDE; - data->user_id = NULL; - data->mapping_file = NULL; - data->quiet = false; - data->debug_libfido2 = false; - - struct poptOption long_options[] = { - POPT_AUTOHELP - SSSD_DEBUG_OPTS - {"dumpable", 0, POPT_ARG_INT, &dumpable, 0, - _("Allow core dumps"), NULL }, - {"backtrace", 0, POPT_ARG_INT, &backtrace, 0, - _("Enable debug backtrace"), NULL }, - {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0, - _("An open file descriptor for the debug logs"), NULL}, - SSSD_LOGGER_OPTS - {"register", 0, POPT_ARG_NONE, NULL, 'r', - _("Register a passkey for a user"), NULL }, - {"authenticate", 0, POPT_ARG_NONE, NULL, 'a', - _("Authenticate a user with a passkey"), NULL }, - {"get-assert", 0, POPT_ARG_NONE, NULL, 'g', - _("Obtain assertion data"), NULL }, - {"verify-assert", 0, POPT_ARG_NONE, NULL, 'v', - _("Verify assertion data"), NULL }, - {"username", 0, POPT_ARG_STRING, &data->shortname, 0, - _("Shortname"), NULL }, - {"domain", 0, POPT_ARG_STRING, &data->domain, 0, - _("Domain"), NULL}, - {"public-key", 0, POPT_ARG_STRING, &public_keys, 0, - _("Public key"), NULL }, - {"key-handle", 0, POPT_ARG_STRING, &key_handles, 0, - _("Key handle"), NULL}, - {"cryptographic-challenge", 0, POPT_ARG_STRING, - &data->crypto_challenge, 0, - _("Cryptographic challenge"), NULL}, - {"auth-data", 0, POPT_ARG_STRING, &data->auth_data, 0, - _("Authenticator data"), NULL}, - {"signature", 0, POPT_ARG_STRING, &data->signature, 0, - _("Signature"), NULL}, - {"type", 0, POPT_ARG_STRING, &type, 0, - _("COSE type to use"), "es256|rs256|eddsa"}, - {"user-verification", 0, POPT_ARG_STRING, &user_verification, 0, - _("Require user-verification"), "true|false"}, - {"cred-type", 0, POPT_ARG_STRING, &cred_type, 0, - _("Credential type"), "server-side|discoverable"}, - {"output-file", 0, POPT_ARG_STRING, &data->mapping_file, 0, - _("Write key mapping data to file"), NULL}, - {"quiet", 0, POPT_ARG_NONE, NULL, 'q', - _("Supress prompts"), NULL}, - {"debug-libfido2", 0, POPT_ARG_NONE, NULL, 'd', - _("Enable debug in libfido2 library"), NULL}, - SSSD_LOGGER_OPTS - POPT_TABLEEND - }; - - /* 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) { - case 'r': - if (data->action != ACTION_NONE - && data->action != ACTION_REGISTER) { - fprintf(stderr, "\nActions are mutually exclusive and should" \ - " be used only once.\n\n"); - poptPrintUsage(pc, stderr, 0); - ret = EINVAL; - goto done; - } - data->action = ACTION_REGISTER; - break; - case 'a': - if (data->action != ACTION_NONE - && data->action != ACTION_AUTHENTICATE) { - fprintf(stderr, "\nActions are mutually exclusive and should" \ - " be used only once.\n\n"); - poptPrintUsage(pc, stderr, 0); - ret = EINVAL; - goto done; - } - data->action = ACTION_AUTHENTICATE; - break; - case 'g': - if (data->action != ACTION_NONE - && data->action != ACTION_GET_ASSERT) { - fprintf(stderr, "\nActions are mutually exclusive and should" \ - " be used only once.\n\n"); - poptPrintUsage(pc, stderr, 0); - ret = EINVAL; - goto done; - } - data->action = ACTION_GET_ASSERT; - break; - case 'v': - if (data->action != ACTION_NONE - && data->action != ACTION_VERIFY_ASSERT) { - fprintf(stderr, "\nActions are mutually exclusive and should" \ - " be used only once.\n\n"); - poptPrintUsage(pc, stderr, 0); - ret = EINVAL; - goto done; - } - data->action = ACTION_VERIFY_ASSERT; - break; - case 'q': - data->quiet = true; - break; - case 'd': - data->debug_libfido2 = true; - break; - default: - fprintf(stderr, "\nInvalid option %s: %s\n\n", - poptBadOption(pc, 0), poptStrerror(opt)); - poptPrintUsage(pc, stderr, 0); - ret = EINVAL; - goto done; - } - } - - poptFreeContext(pc); - - sss_prctl_set_dumpable((dumpable == 0) ? 0 : 1); - - if (user_verification != NULL) { - if (strcmp(user_verification, "true") == 0) { - data->user_verification = FIDO_OPT_TRUE; - } else if (strcmp(user_verification, "false") == 0) { - data->user_verification = FIDO_OPT_FALSE; - } else if (user_verification != NULL) { - ERROR("[%s] is not a valid user-verification value.\n", - user_verification); - ret = EINVAL; - goto done; - } - } - - if (type != NULL) { - ret = cose_str_to_int(type, &data->type); - if (ret != EOK) { - ERROR("[%s] is not a valid COSE type (es256, rs256 or eddsa).\n", - type); - goto done; - } - } - - if (public_keys != NULL || key_handles != NULL) { - ret = parse_public_keys_and_handlers(mem_ctx, public_keys, key_handles, - data); - if (ret != EOK) { - goto done; - } - } - - if (cred_type != NULL) { - ret = cred_type_str_to_enum(cred_type, &data->cred_type); - if (ret != EOK) { - ERROR("[%s] is not a valid credential type (server-side or" - " discoverable).\n", - cred_type); - goto done; - } - } - - debug_prg_name = talloc_asprintf(NULL, "passkey_child[%d]", getpid()); - if (debug_prg_name == NULL) { - ERROR("talloc_asprintf failed.\n"); - ret = ENOMEM; - goto done; - } - - if (debug_fd != -1) { - opt_logger = sss_logger_str[FILES_LOGGER]; - ret = set_debug_file_from_fd(debug_fd); - if (ret != EOK) { - opt_logger = sss_logger_str[STDERR_LOGGER]; - ERROR("set_debug_file_from_fd failed.\n"); - } - } - - DEBUG_INIT(debug_level, opt_logger); - sss_set_debug_backtrace_enable((backtrace == 0) ? false : true); - - ret = EOK; - -done: - return ret; -} - -errno_t -check_arguments(const struct passkey_data *data) -{ - errno_t ret = EOK; - - DEBUG(SSSDBG_TRACE_FUNC, "Argument values after parsing\n"); - DEBUG(SSSDBG_TRACE_FUNC, "action: %d\n", data->action); - DEBUG(SSSDBG_TRACE_FUNC, "shortname: %s, domain: %s\n", - data->shortname, data->domain); - DEBUG(SSSDBG_TRACE_FUNC, "Number of key handles %d\n", - data->key_handle_size); - for (int i = 0; i < data->key_handle_size; i++) { - DEBUG(SSSDBG_TRACE_FUNC, "key %d, key_handle: %s\n", - i + 1, data->key_handle_list[i]); - } - DEBUG(SSSDBG_TRACE_FUNC, "Number of public keys %d\n", - data->public_key_size); - for (int i = 0; i < data->public_key_size; i++) { - DEBUG(SSSDBG_TRACE_FUNC, "key %d, public_key: %s\n", - i + 1, data->public_key_list[i]); - } - DEBUG(SSSDBG_TRACE_FUNC, "cryptographic-challenge: %s\n", - data->crypto_challenge); - DEBUG(SSSDBG_TRACE_FUNC, "auth-data: %s\n", - data->auth_data); - DEBUG(SSSDBG_TRACE_FUNC, "signature: %s\n", - data->signature); - DEBUG(SSSDBG_TRACE_FUNC, "type: %d\n", data->type); - DEBUG(SSSDBG_TRACE_FUNC, "user_verification: %d\n", - data->user_verification); - DEBUG(SSSDBG_TRACE_FUNC, "cred_type: %d\n", - data->cred_type); - DEBUG(SSSDBG_TRACE_FUNC, "Mapping file: %s\n", data->mapping_file); - DEBUG(SSSDBG_TRACE_FUNC, "debug_libfido2: %d\n", data->debug_libfido2); - - if (data->action == ACTION_NONE) { - DEBUG(SSSDBG_OP_FAILURE, "No action set.\n"); - ret = ERR_INPUT_PARSE; - goto done; - } - - if (data->action == ACTION_REGISTER - && (data->shortname == NULL || data->domain == NULL)) { - DEBUG(SSSDBG_OP_FAILURE, "Too few arguments for register action.\n"); - ret = ERR_INPUT_PARSE; - goto done; - } - - if (data->action == ACTION_AUTHENTICATE - && (data->domain == NULL || data->public_key_list == NULL - || data->key_handle_list == NULL)) { - DEBUG(SSSDBG_OP_FAILURE, - "Too few arguments for authenticate action.\n"); - ret = ERR_INPUT_PARSE; - goto done; - } - - if (data->action == ACTION_GET_ASSERT - && (data->domain == NULL || data->key_handle_list == NULL - || data->crypto_challenge == NULL)) { - DEBUG(SSSDBG_OP_FAILURE, - "Too few arguments for get-assert action.\n"); - ret = ERR_INPUT_PARSE; - goto done; - } - - if (data->action == ACTION_VERIFY_ASSERT - && (data->domain == NULL || data->public_key_list == NULL - || data->key_handle_list == NULL || data->crypto_challenge == NULL - || data->auth_data == NULL || data->signature == NULL)) { - DEBUG(SSSDBG_OP_FAILURE, - "Too few arguments for verify-assert action.\n"); - ret = ERR_INPUT_PARSE; - goto done; - } - -done: - return ret; -} - -errno_t -register_key(struct passkey_data *data) -{ - TALLOC_CTX *tmp_ctx = NULL; - fido_cred_t *cred = NULL; - fido_dev_t *dev = NULL; - fido_dev_info_t *dev_list = NULL; - size_t dev_number = 0; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - return ENOMEM; - } - - data->user_id = talloc_array(tmp_ctx, unsigned char, USER_ID_SIZE); - if (data->user_id == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_array() failed.\n"); - ret = ENOMEM; - goto done; - } - - cred = fido_cred_new(); - if (cred == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_new failed.\n"); - ret = ENOMEM; - goto done; - } - - dev_list = fido_dev_info_new(DEVLIST_SIZE); - if (dev_list == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = list_devices(dev_list, &dev_number); - if (ret != EOK) { - goto done; - } - - ret = select_device(data->action, dev_list, dev_number, NULL, &dev); - if (ret != EOK) { - goto done; - } - - ret = prepare_credentials(data, dev, cred); - if (ret != EOK) { - goto done; - } - - ret = generate_credentials(data, dev, cred); - if (ret != EOK) { - ERROR("A problem occurred while generating the credentials.\n"); - goto done; - } - - ret = verify_credentials(cred); - if (ret != EOK) { - goto done; - } - - ret = print_credentials(data, cred); - if (ret != EOK) { - goto done; - } - - ret = EOK; - -done: - fido_cred_free(&cred); - fido_dev_info_free(&dev_list, dev_number); - if (dev != NULL) { - fido_dev_close(dev); - } - fido_dev_free(&dev); - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -public_key_to_base64(TALLOC_CTX *mem_ctx, const struct passkey_data *data, - const unsigned char *public_key, size_t pk_len, - char **_pem_key) -{ - EVP_PKEY *evp_pkey = NULL; - unsigned char *pub = NULL; - char *pem_key = NULL; - unsigned long err; - errno_t ret; - - if (_pem_key == NULL) { - ret = EINVAL; - goto done; - } - - switch (data->type) { - case COSE_ES256: - ret = es256_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); - break; - case COSE_RS256: - ret = rs256_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); - break; - case COSE_EDDSA: - ret = eddsa_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); - break; - default: - DEBUG(SSSDBG_OP_FAILURE, "Invalid key type.\n"); - ret = EINVAL; - break; - } - - if (ret != EOK) { - goto done; - } - - ret = i2d_PUBKEY(evp_pkey, &pub); - if (ret < 1) { - err = ERR_get_error(); - DEBUG(SSSDBG_OP_FAILURE, "i2d_PUBKEY failed [%lu][%s].\n", - err, ERR_error_string(err, NULL)); - ret = EIO; - goto done; - } - - pem_key = sss_base64_encode(mem_ctx, pub, ret); - if (pem_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "sss_base64_encode failed.\n"); - ret = ENOMEM; - goto done; - } - - *_pem_key = pem_key; - ret = EOK; - -done: - free(pub); - - if (evp_pkey != NULL) { - EVP_PKEY_free(evp_pkey); - } - - return ret; -} - -errno_t -select_authenticator(struct passkey_data *data, fido_dev_t **_dev, - fido_assert_t **_assert, int *_index) -{ - fido_dev_info_t *dev_list = NULL; - fido_dev_t *dev = NULL; - size_t dev_list_len = 0; - fido_assert_t *assert = NULL; - int index = 0; - errno_t ret; - - dev_list = fido_dev_info_new(DEVLIST_SIZE); - if (dev_list == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_new failed.\n"); - ret = ENOMEM; - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Checking for devices.\n"); - ret = list_devices(dev_list, &dev_list_len); - if (ret != EOK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "%d key handles provided.\n", - data->key_handle_size); - - while (index < data->key_handle_size) { - DEBUG(SSSDBG_TRACE_FUNC, - "Preparing assert request data with key handle %d.\n", index + 1); - - assert = fido_assert_new(); - if (assert == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_assert_new failed.\n"); - ret = ENOMEM; - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Preparing assert request data.\n"); - ret = prepare_assert(data, index, assert); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Selecting device.\n"); - ret = select_device(data->action, dev_list, dev_list_len, assert, &dev); - if (ret == EOK) { - /* Key handle found in device */ - break; - } - - if (dev != NULL) { - fido_dev_close(dev); - } - fido_dev_free(&dev); - fido_assert_free(&assert); - index++; - } - - *_dev = dev; - *_assert = assert; - *_index = index; - -done: - if (ret != EOK) { - fido_assert_free(&assert); - } - fido_dev_info_free(&dev_list, dev_list_len); - - return ret; -} - -errno_t -public_key_to_libfido2(const char *pem_public_key, struct pk_data_t *_pk_data) -{ - TALLOC_CTX *tmp_ctx = NULL; - const unsigned char *public_key = NULL; - size_t pk_len; - const EVP_PKEY *evp_pkey = NULL; - int base_id; - unsigned long err; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - public_key = sss_base64_decode(tmp_ctx, pem_public_key, &pk_len); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to decode public key.\n"); - ret = ENOMEM; - goto done; - } - - evp_pkey = d2i_PUBKEY(NULL, &public_key, pk_len); - if (evp_pkey == NULL) { - err = ERR_get_error(); - DEBUG(SSSDBG_OP_FAILURE, "d2i_pubkey failed [%lu][%s].\n", - err, ERR_error_string(err, NULL)); - ret = EIO; - goto done; - } - - base_id = get_id(evp_pkey); - if (base_id == EVP_PKEY_EC) { - _pk_data->type = COSE_ES256; - ret = evp_pkey_to_es256_pubkey(evp_pkey, _pk_data); - } else if (base_id == EVP_PKEY_RSA) { - _pk_data->type = COSE_RS256; - ret = evp_pkey_to_rs256_pubkey(evp_pkey, _pk_data); - } else if (base_id == EVP_PKEY_ED25519) { - _pk_data->type = COSE_EDDSA; - ret = evp_pkey_to_eddsa_pubkey(evp_pkey, _pk_data); - } else { - DEBUG(SSSDBG_OP_FAILURE, - "Unrecognized key type.\n"); - ret = EINVAL; - } - if (ret != EOK) { - goto done; - } - - ret = EOK; - -done: - if (evp_pkey != NULL) { - EVP_PKEY_free(discard_const(evp_pkey)); - } - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -authenticate(struct passkey_data *data) -{ - TALLOC_CTX *tmp_ctx = NULL; - fido_assert_t *assert = NULL; - fido_dev_t *dev = NULL; - struct pk_data_t pk_data = { 0 }; - int index; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ERROR("talloc_new() failed\n"); - return ENOMEM; - } - - ret = select_authenticator(data, &dev, &assert, &index); - if (ret != EOK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Comparing the device and policy options.\n"); - ret = get_device_options(dev, data); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert options.\n"); - ret = set_assert_options(FIDO_OPT_TRUE, data->user_verification, assert); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to reset assert options.\n"); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert client data.\n"); - ret = set_assert_client_data_hash(data, assert); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to reset client data hash.\n"); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Decoding public key.\n"); - ret = public_key_to_libfido2(data->public_key_list[index], &pk_data); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Getting assert.\n"); - ret = request_assert(data, dev, assert); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Verifying assert.\n"); - ret = verify_assert(&pk_data, assert); - if (ret != FIDO_OK) { - goto done; - } - - ret = FIDO_OK; - -done: - reset_public_key(&pk_data); - if (dev != NULL) { - fido_dev_close(dev); - } - fido_dev_free(&dev); - fido_assert_free(&assert); - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -get_assert_data(struct passkey_data *data) -{ - TALLOC_CTX *tmp_ctx = NULL; - fido_dev_t *dev = NULL; - fido_assert_t *assert = NULL; - const char *auth_data = NULL; - const char *signature = NULL; - int index; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - return ENOMEM; - } - - ret = select_authenticator(data, &dev, &assert, &index); - if (ret != EOK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Comparing the device and policy options.\n"); - ret = get_device_options(dev, data); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert options.\n"); - ret = set_assert_options(FIDO_OPT_TRUE, data->user_verification, assert); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to reset assert options.\n"); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Getting assert.\n"); - ret = request_assert(data, dev, assert); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Getting authentication data and signature.\n"); - ret = get_assert_auth_data_signature(tmp_ctx, assert, &auth_data, - &signature); - if (ret != EOK) { - goto done; - } - - print_assert_data(data->key_handle_list[index], data->crypto_challenge, - auth_data, signature); - -done: - if (dev != NULL) { - fido_dev_close(dev); - } - fido_dev_free(&dev); - fido_assert_free(&assert); - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -verify_assert_data(struct passkey_data *data) -{ - fido_assert_t *assert = NULL; - struct pk_data_t pk_data = { 0 }; - errno_t ret; - - assert = fido_assert_new(); - if (assert == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_assert_new failed.\n"); - ret = ENOMEM; - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Preparing assert data.\n"); - ret = prepare_assert(data, 0, assert); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, - "Preparing assert authenticator data and signature.\n"); - ret = set_assert_auth_data_signature(data, assert); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Decoding public key.\n"); - ret = public_key_to_libfido2(data->public_key_list[0], &pk_data); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Verifying assert.\n"); - ret = verify_assert(&pk_data, assert); - if (ret != FIDO_OK) { - goto done; - } - - ret = FIDO_OK; - -done: - reset_public_key(&pk_data); - fido_assert_free(&assert); - - return ret; -} +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa + + Copyright (C) 2022 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 . +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "util/crypto/sss_crypto.h" +#include "util/debug.h" +#include "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "util/sss_prctl.h" + +#include "passkey_child.h" + +#if OPENSSL_VERSION_NUMBER >= 0x30000000 +#define get_id(x) EVP_PKEY_get_base_id((x)) +#else +#define get_id(x) EVP_PKEY_base_id((x)) +#endif /* OPENSSL_VERSION_NUMBER */ + +errno_t +cose_str_to_int(const char *type, int *out) +{ + if (strcasecmp(type, "es256") == 0) { + *out = COSE_ES256; + } else if (strcasecmp(type, "rs256") == 0) { + *out = COSE_RS256; + } else if (strcasecmp(type, "eddsa") == 0) { + *out = COSE_EDDSA; + } else { + *out = 0; + return ERR_INVALID_CRED_TYPE; + } + + return EOK; +} + +static errno_t +cred_type_str_to_enum(const char *type, enum credential_type *out) +{ + if (strcasecmp(type, "server-side") == 0) { + *out = CRED_SERVER_SIDE; + } else if (strcasecmp(type, "discoverable") == 0) { + *out = CRED_DISCOVERABLE; + } else { + *out = 0; + return ERR_INVALID_CRED_TYPE; + } + + return EOK; +} + +static errno_t +parse_public_keys_and_handlers(TALLOC_CTX *mem_ctx, + const char *public_keys, + const char *key_handles, + struct passkey_data *_data) +{ + TALLOC_CTX *tmp_ctx = NULL; + char **pk_list = NULL; + char **kh_list = NULL; + int pk_num = 0; + int kh_num = 0; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ERROR("talloc_new() failed\n"); + return ENOMEM; + } + + ret = split_on_separator(tmp_ctx, public_keys, ',', true, true, &pk_list, &pk_num); + if (ret != EOK && _data->action == ACTION_AUTHENTICATE) { + ERROR("Incorrectly formatted public keys.\n"); + goto done; + } + + ret = split_on_separator(tmp_ctx, key_handles, ',', true, true, &kh_list, &kh_num); + if (ret != EOK) { + ERROR("Incorrectly formatted public keys.\n"); + goto done; + } + + if (_data->action == ACTION_AUTHENTICATE && pk_num != kh_num) { + ERROR("The number of public keys and key handles don't match.\n"); + goto done; + } + + _data->public_key_list = talloc_steal(mem_ctx, pk_list); + _data->key_handle_list = talloc_steal(mem_ctx, kh_list); + _data->public_key_size = pk_num; + _data->key_handle_size = kh_num; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +parse_arguments(TALLOC_CTX *mem_ctx, int argc, const char *argv[], + struct passkey_data *data) +{ + int opt; + int dumpable = 1; + int backtrace = 1; + int debug_fd = -1; + char *user_verification = NULL; + char *public_keys = NULL; + char *key_handles = NULL; + const char *opt_logger = NULL; + const char *type = NULL; + const char *cred_type = NULL; + poptContext pc; + errno_t ret; + + /* Set defaults */ + data->action = ACTION_NONE; + data->shortname = NULL; + data->domain = NULL; + data->public_key_list = NULL; + data->key_handle_list = NULL; + data->public_key_size = 0; + data->key_handle_size = 0; + data->crypto_challenge = NULL; + data->auth_data = NULL; + data->signature = NULL; + data->type = COSE_ES256; + data->user_verification = FIDO_OPT_OMIT; + data->cred_type = CRED_SERVER_SIDE; + data->user_id = NULL; + data->mapping_file = NULL; + data->quiet = false; + data->debug_libfido2 = false; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_DEBUG_OPTS + {"dumpable", 0, POPT_ARG_INT, &dumpable, 0, + _("Allow core dumps"), NULL }, + {"backtrace", 0, POPT_ARG_INT, &backtrace, 0, + _("Enable debug backtrace"), NULL }, + {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0, + _("An open file descriptor for the debug logs"), NULL}, + SSSD_LOGGER_OPTS + {"register", 0, POPT_ARG_NONE, NULL, 'r', + _("Register a passkey for a user"), NULL }, + {"authenticate", 0, POPT_ARG_NONE, NULL, 'a', + _("Authenticate a user with a passkey"), NULL }, + {"get-assert", 0, POPT_ARG_NONE, NULL, 'g', + _("Obtain assertion data"), NULL }, + {"get-device-info", 0, POPT_ARG_NONE, NULL, 'i', + _("Obtain device information"), NULL }, + {"verify-assert", 0, POPT_ARG_NONE, NULL, 'v', + _("Verify assertion data"), NULL }, + {"username", 0, POPT_ARG_STRING, &data->shortname, 0, + _("Shortname"), NULL }, + {"domain", 0, POPT_ARG_STRING, &data->domain, 0, + _("Domain"), NULL}, + {"public-key", 0, POPT_ARG_STRING, &public_keys, 0, + _("Public key"), NULL }, + {"key-handle", 0, POPT_ARG_STRING, &key_handles, 0, + _("Key handle"), NULL}, + {"cryptographic-challenge", 0, POPT_ARG_STRING, + &data->crypto_challenge, 0, + _("Cryptographic challenge"), NULL}, + {"auth-data", 0, POPT_ARG_STRING, &data->auth_data, 0, + _("Authenticator data"), NULL}, + {"signature", 0, POPT_ARG_STRING, &data->signature, 0, + _("Signature"), NULL}, + {"type", 0, POPT_ARG_STRING, &type, 0, + _("COSE type to use"), "es256|rs256|eddsa"}, + {"user-verification", 0, POPT_ARG_STRING, &user_verification, 0, + _("Require user-verification"), "true|false"}, + {"cred-type", 0, POPT_ARG_STRING, &cred_type, 0, + _("Credential type"), "server-side|discoverable"}, + {"output-file", 0, POPT_ARG_STRING, &data->mapping_file, 0, + _("Write key mapping data to file"), NULL}, + {"quiet", 0, POPT_ARG_NONE, NULL, 'q', + _("Supress prompts"), NULL}, + {"debug-libfido2", 0, POPT_ARG_NONE, NULL, 'd', + _("Enable debug in libfido2 library"), NULL}, + SSSD_LOGGER_OPTS + POPT_TABLEEND + }; + + /* 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) { + case 'r': + if (data->action != ACTION_NONE + && data->action != ACTION_REGISTER) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_REGISTER; + break; + case 'a': + if (data->action != ACTION_NONE + && data->action != ACTION_AUTHENTICATE) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_AUTHENTICATE; + break; + case 'g': + if (data->action != ACTION_NONE + && data->action != ACTION_GET_ASSERT) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_GET_ASSERT; + break; + case 'i': + if (data->action != ACTION_NONE + && data->action != ACTION_GET_DEVINFO) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_GET_DEVINFO; + break; + case 'v': + if (data->action != ACTION_NONE + && data->action != ACTION_VERIFY_ASSERT) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_VERIFY_ASSERT; + break; + case 'q': + data->quiet = true; + break; + case 'd': + data->debug_libfido2 = true; + break; + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + } + + poptFreeContext(pc); + + sss_prctl_set_dumpable((dumpable == 0) ? 0 : 1); + + if (user_verification != NULL) { + if (strcmp(user_verification, "true") == 0) { + data->user_verification = FIDO_OPT_TRUE; + } else if (strcmp(user_verification, "false") == 0) { + data->user_verification = FIDO_OPT_FALSE; + } else if (user_verification != NULL) { + ERROR("[%s] is not a valid user-verification value.\n", + user_verification); + ret = EINVAL; + goto done; + } + } + + if (type != NULL) { + ret = cose_str_to_int(type, &data->type); + if (ret != EOK) { + ERROR("[%s] is not a valid COSE type (es256, rs256 or eddsa).\n", + type); + goto done; + } + } + + if (public_keys != NULL || key_handles != NULL) { + ret = parse_public_keys_and_handlers(mem_ctx, public_keys, key_handles, + data); + if (ret != EOK) { + goto done; + } + } + + if (cred_type != NULL) { + ret = cred_type_str_to_enum(cred_type, &data->cred_type); + if (ret != EOK) { + ERROR("[%s] is not a valid credential type (server-side or" + " discoverable).\n", + cred_type); + goto done; + } + } + + debug_prg_name = talloc_asprintf(NULL, "passkey_child[%d]", getpid()); + if (debug_prg_name == NULL) { + ERROR("talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + if (debug_fd != -1) { + opt_logger = sss_logger_str[FILES_LOGGER]; + ret = set_debug_file_from_fd(debug_fd); + if (ret != EOK) { + opt_logger = sss_logger_str[STDERR_LOGGER]; + ERROR("set_debug_file_from_fd failed.\n"); + } + } + + DEBUG_INIT(debug_level, opt_logger); + sss_set_debug_backtrace_enable((backtrace == 0) ? false : true); + + ret = EOK; + +done: + return ret; +} + +errno_t +check_arguments(const struct passkey_data *data) +{ + errno_t ret = EOK; + + DEBUG(SSSDBG_TRACE_FUNC, "Argument values after parsing\n"); + DEBUG(SSSDBG_TRACE_FUNC, "action: %d\n", data->action); + DEBUG(SSSDBG_TRACE_FUNC, "shortname: %s, domain: %s\n", + data->shortname, data->domain); + DEBUG(SSSDBG_TRACE_FUNC, "Number of key handles %d\n", + data->key_handle_size); + for (int i = 0; i < data->key_handle_size; i++) { + DEBUG(SSSDBG_TRACE_FUNC, "key %d, key_handle: %s\n", + i + 1, data->key_handle_list[i]); + } + DEBUG(SSSDBG_TRACE_FUNC, "Number of public keys %d\n", + data->public_key_size); + for (int i = 0; i < data->public_key_size; i++) { + DEBUG(SSSDBG_TRACE_FUNC, "key %d, public_key: %s\n", + i + 1, data->public_key_list[i]); + } + DEBUG(SSSDBG_TRACE_FUNC, "cryptographic-challenge: %s\n", + data->crypto_challenge); + DEBUG(SSSDBG_TRACE_FUNC, "auth-data: %s\n", + data->auth_data); + DEBUG(SSSDBG_TRACE_FUNC, "signature: %s\n", + data->signature); + DEBUG(SSSDBG_TRACE_FUNC, "type: %d\n", data->type); + DEBUG(SSSDBG_TRACE_FUNC, "user_verification: %d\n", + data->user_verification); + DEBUG(SSSDBG_TRACE_FUNC, "cred_type: %d\n", + data->cred_type); + DEBUG(SSSDBG_TRACE_FUNC, "Mapping file: %s\n", data->mapping_file); + DEBUG(SSSDBG_TRACE_FUNC, "debug_libfido2: %d\n", data->debug_libfido2); + + if (data->action == ACTION_NONE) { + DEBUG(SSSDBG_OP_FAILURE, "No action set.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + + if (data->action == ACTION_REGISTER + && (data->shortname == NULL || data->domain == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, "Too few arguments for register action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + + if (data->action == ACTION_AUTHENTICATE + && (data->domain == NULL || data->public_key_list == NULL + || data->key_handle_list == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, + "Too few arguments for authenticate action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + + if (data->action == ACTION_GET_DEVINFO + && (data->domain == NULL || data->key_handle_list == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, + "Too few arguments for get-device-info action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + + if (data->action == ACTION_GET_ASSERT + && (data->domain == NULL || data->key_handle_list == NULL + || data->crypto_challenge == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, + "Too few arguments for get-assert action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + + if (data->action == ACTION_VERIFY_ASSERT + && (data->domain == NULL || data->public_key_list == NULL + || data->key_handle_list == NULL || data->crypto_challenge == NULL + || data->auth_data == NULL || data->signature == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, + "Too few arguments for verify-assert action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + +done: + return ret; +} + +errno_t +register_key(struct passkey_data *data) +{ + TALLOC_CTX *tmp_ctx = NULL; + fido_cred_t *cred = NULL; + fido_dev_t *dev = NULL; + fido_dev_info_t *dev_list = NULL; + size_t dev_number = 0; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + data->user_id = talloc_array(tmp_ctx, unsigned char, USER_ID_SIZE); + if (data->user_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array() failed.\n"); + ret = ENOMEM; + goto done; + } + + cred = fido_cred_new(); + if (cred == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_new failed.\n"); + ret = ENOMEM; + goto done; + } + + dev_list = fido_dev_info_new(DEVLIST_SIZE); + if (dev_list == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = list_devices(dev_list, &dev_number); + if (ret != EOK) { + goto done; + } + + ret = select_device(data->action, dev_list, dev_number, NULL, &dev); + if (ret != EOK) { + goto done; + } + + ret = prepare_credentials(data, dev, cred); + if (ret != EOK) { + goto done; + } + + ret = generate_credentials(data, dev, cred); + if (ret != EOK) { + ERROR("A problem occurred while generating the credentials.\n"); + goto done; + } + + ret = verify_credentials(cred); + if (ret != EOK) { + goto done; + } + + ret = print_credentials(data, cred); + if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + fido_cred_free(&cred); + fido_dev_info_free(&dev_list, dev_number); + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +public_key_to_base64(TALLOC_CTX *mem_ctx, const struct passkey_data *data, + const unsigned char *public_key, size_t pk_len, + char **_pem_key) +{ + EVP_PKEY *evp_pkey = NULL; + unsigned char *pub = NULL; + char *pem_key = NULL; + unsigned long err; + errno_t ret; + + if (_pem_key == NULL) { + ret = EINVAL; + goto done; + } + + switch (data->type) { + case COSE_ES256: + ret = es256_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); + break; + case COSE_RS256: + ret = rs256_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); + break; + case COSE_EDDSA: + ret = eddsa_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Invalid key type.\n"); + ret = EINVAL; + break; + } + + if (ret != EOK) { + goto done; + } + + ret = i2d_PUBKEY(evp_pkey, &pub); + if (ret < 1) { + err = ERR_get_error(); + DEBUG(SSSDBG_OP_FAILURE, "i2d_PUBKEY failed [%lu][%s].\n", + err, ERR_error_string(err, NULL)); + ret = EIO; + goto done; + } + + pem_key = sss_base64_encode(mem_ctx, pub, ret); + if (pem_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_encode failed.\n"); + ret = ENOMEM; + goto done; + } + + *_pem_key = pem_key; + ret = EOK; + +done: + free(pub); + + if (evp_pkey != NULL) { + EVP_PKEY_free(evp_pkey); + } + + return ret; +} + +errno_t +select_authenticator(struct passkey_data *data, fido_dev_t **_dev, + fido_assert_t **_assert, int *_index) +{ + fido_dev_info_t *dev_list = NULL; + fido_dev_t *dev = NULL; + size_t dev_list_len = 0; + fido_assert_t *assert = NULL; + int index = 0; + errno_t ret; + + dev_list = fido_dev_info_new(DEVLIST_SIZE); + if (dev_list == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_new failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Checking for devices.\n"); + ret = list_devices(dev_list, &dev_list_len); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "%d key handles provided.\n", + data->key_handle_size); + + while (index < data->key_handle_size) { + DEBUG(SSSDBG_TRACE_FUNC, + "Preparing assert request data with key handle %d.\n", index + 1); + + assert = fido_assert_new(); + if (assert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_assert_new failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Preparing assert request data.\n"); + ret = prepare_assert(data, index, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Selecting device.\n"); + ret = select_device(data->action, dev_list, dev_list_len, assert, &dev); + if (ret == EOK) { + /* Key handle found in device */ + /* update device current configuration + */ + bool has_pin = fido_dev_has_pin(dev); + bool has_uv = fido_dev_has_uv (dev); + int fd = -1; + if (has_pin && has_uv) { + fd = creat("/var/run/passkey-pinuv", 0000); + if (fd < 0) + DEBUG(SSSDBG_TRACE_FUNC, "rror creat pinuv errno = %d\n", errno); + close(fd); + } + else { + (void)remove ("/var/run/passkey-pinuv"); + if (has_pin) { + fd = creat("/var/run/passkey-pinonly", 0000); + if (fd < 0) + DEBUG(SSSDBG_TRACE_FUNC, "eror creat pinonly errno = %d\n", errno); + close (fd); + } else { + /* no pin and no uv; */ + (void)remove ("/var/run/passkey-pinonly"); + fido_dev_close(dev); + dev = NULL; + ret = FIDO_ERR_NOTFOUND; + } + } + break; + } + + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + fido_assert_free(&assert); + index++; + } + + *_dev = dev; + *_assert = assert; + *_index = index; + +done: + if (ret != EOK) { + fido_assert_free(&assert); + } + fido_dev_info_free(&dev_list, dev_list_len); + + return ret; +} + +errno_t +public_key_to_libfido2(const char *pem_public_key, struct pk_data_t *_pk_data) +{ + TALLOC_CTX *tmp_ctx = NULL; + const unsigned char *public_key = NULL; + size_t pk_len; + const EVP_PKEY *evp_pkey = NULL; + int base_id; + unsigned long err; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + public_key = sss_base64_decode(tmp_ctx, pem_public_key, &pk_len); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to decode public key.\n"); + ret = ENOMEM; + goto done; + } + + evp_pkey = d2i_PUBKEY(NULL, &public_key, pk_len); + if (evp_pkey == NULL) { + err = ERR_get_error(); + DEBUG(SSSDBG_OP_FAILURE, "d2i_pubkey failed [%lu][%s].\n", + err, ERR_error_string(err, NULL)); + ret = EIO; + goto done; + } + + base_id = get_id(evp_pkey); + if (base_id == EVP_PKEY_EC) { + _pk_data->type = COSE_ES256; + ret = evp_pkey_to_es256_pubkey(evp_pkey, _pk_data); + } else if (base_id == EVP_PKEY_RSA) { + _pk_data->type = COSE_RS256; + ret = evp_pkey_to_rs256_pubkey(evp_pkey, _pk_data); + } else if (base_id == EVP_PKEY_ED25519) { + _pk_data->type = COSE_EDDSA; + ret = evp_pkey_to_eddsa_pubkey(evp_pkey, _pk_data); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Unrecognized key type.\n"); + ret = EINVAL; + } + if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + if (evp_pkey != NULL) { + EVP_PKEY_free(discard_const(evp_pkey)); + } + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +authenticate(struct passkey_data *data) +{ + TALLOC_CTX *tmp_ctx = NULL; + fido_assert_t *assert = NULL; + fido_dev_t *dev = NULL; + struct pk_data_t pk_data = { 0 }; + int index; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ERROR("talloc_new() failed\n"); + return ENOMEM; + } + + ret = select_authenticator(data, &dev, &assert, &index); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Comparing the device and policy options.\n"); + ret = get_device_options(dev, data); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert options.\n"); + ret = set_assert_options(FIDO_OPT_TRUE, data->user_verification, assert); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to reset assert options.\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert client data.\n"); + ret = set_assert_client_data_hash(data, assert); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to reset client data hash.\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Decoding public key.\n"); + ret = public_key_to_libfido2(data->public_key_list[index], &pk_data); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Getting assert.\n"); + ret = request_assert(data, dev, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Verifying assert.\n"); + ret = verify_assert(&pk_data, assert); + if (ret != FIDO_OK) { + goto done; + } + + ret = FIDO_OK; + +done: + reset_public_key(&pk_data); + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + fido_assert_free(&assert); + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +get_assert_data(struct passkey_data *data) +{ + TALLOC_CTX *tmp_ctx = NULL; + fido_dev_t *dev = NULL; + fido_assert_t *assert = NULL; + const char *auth_data = NULL; + const char *signature = NULL; + int index; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + ret = select_authenticator(data, &dev, &assert, &index); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Comparing the device and policy options.\n"); + ret = get_device_options(dev, data); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert options.\n"); + ret = set_assert_options(FIDO_OPT_TRUE, data->user_verification, assert); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to reset assert options.\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Getting assert.\n"); + ret = request_assert(data, dev, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Getting authentication data and signature.\n"); + ret = get_assert_auth_data_signature(tmp_ctx, assert, &auth_data, + &signature); + if (ret != EOK) { + goto done; + } + + print_assert_data(data->key_handle_list[index], data->crypto_challenge, + auth_data, signature); + +done: + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + fido_assert_free(&assert); + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +get_device_info(struct passkey_data *data) +{ + TALLOC_CTX *tmp_ctx = NULL; + fido_dev_t *dev = NULL; + fido_assert_t *assert = NULL; + int index; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + ret = select_authenticator(data, &dev, &assert, &index); + if (ret != EOK) { + goto done; + } + /* unused */ + fido_assert_free(&assert); + + done: + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +verify_assert_data(struct passkey_data *data) +{ + fido_assert_t *assert = NULL; + struct pk_data_t pk_data = { 0 }; + errno_t ret; + + assert = fido_assert_new(); + if (assert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_assert_new failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Preparing assert data.\n"); + ret = prepare_assert(data, 0, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Preparing assert authenticator data and signature.\n"); + ret = set_assert_auth_data_signature(data, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Decoding public key.\n"); + ret = public_key_to_libfido2(data->public_key_list[0], &pk_data); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Verifying assert.\n"); + ret = verify_assert(&pk_data, assert); + if (ret != FIDO_OK) { + goto done; + } + + ret = FIDO_OK; + +done: + reset_public_key(&pk_data); + fido_assert_free(&assert); + + return ret; +} diff --git a/src/passkey_child/passkey_child_credentials.c b/src/passkey_child/passkey_child_credentials.c index e27afb411bb..3edce33b452 100644 --- a/src/passkey_child/passkey_child_credentials.c +++ b/src/passkey_child/passkey_child_credentials.c @@ -1,681 +1,690 @@ -/* - SSSD - - Helper child to commmunicate with passkey devices - - Authors: - Iker Pedrosa - - Copyright (C) 2022 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 . -*/ - -#include -#include -#include - -#include -#include -#include - -#include "util/crypto/sss_crypto.h" -#include "util/debug.h" -#include "util/util.h" - -#include "passkey_child.h" - -#define IN_BUF_SIZE 1024 - -errno_t -prepare_credentials(struct passkey_data *data, fido_dev_t *dev, - fido_cred_t *cred) -{ - unsigned char cdh[32]; - fido_opt_t rk = FIDO_OPT_OMIT; - bool has_pin; - bool has_uv; - errno_t ret = EOK; - - ret = fido_cred_set_type(cred, data->type); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_type failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - ret = sss_generate_csprng_buffer(cdh, sizeof(cdh)); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "sss_generate_csprng_buffer failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - ret = fido_cred_set_clientdata_hash(cred, cdh, sizeof(cdh)); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_cred_set_clientdata_hash failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Setting Relying Party ID and name to %s.\n", - data->domain); - - ret = fido_cred_set_rp(cred, data->domain, data->domain); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_rp failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - if (data->user_id == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "user_id must be allocated before using it.\n"); - ret = ENOMEM; - goto done; - } - - ret = sss_generate_csprng_buffer(data->user_id, USER_ID_SIZE); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "sss_generate_csprng_buffer failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Setting user: %s.\n", data->shortname); - - ret = fido_cred_set_user(cred, data->user_id, USER_ID_SIZE, - data->shortname, NULL, NULL); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_user failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - if (data->cred_type == CRED_DISCOVERABLE) { - rk = FIDO_OPT_TRUE; - } - - /* Set to FIDO_OPT_OMIT instead of FIDO_OPT_FALSE for compatibility reasons - */ - ret = fido_cred_set_rk(cred, rk); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_rk failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - has_uv = fido_dev_has_uv(dev); - has_pin = fido_dev_has_pin(dev); - if (data->user_verification == FIDO_OPT_TRUE && has_uv == false - && has_pin == false) { - DEBUG(SSSDBG_OP_FAILURE, - "Policy enabled user-verification but there isn't any " - "verification method set.\n"); - ret = EINVAL; - goto done; - } - - if (data->user_verification == FIDO_OPT_FALSE - && (has_uv == true || has_pin == true)) { - DEBUG(SSSDBG_CONF_SETTINGS, - "Policy disabled user-verification but the key settings are " - "enforcing it. Thus, enabling user-verification.\n"); - data->user_verification = FIDO_OPT_TRUE; - } - - if (has_uv == true) { - ret = fido_cred_set_uv(cred, FIDO_OPT_TRUE); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_uv failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - } - -done: - return ret; -} - -errno_t -passkey_recv_pin(TALLOC_CTX *mem_ctx, int fd, char **_pin) -{ - uint8_t buf[IN_BUF_SIZE]; - ssize_t len; - errno_t ret; - char *str; - - errno = 0; - len = sss_atomic_read_s(fd, buf, IN_BUF_SIZE); - if (len == -1) { - ret = errno; - ret = (ret == 0) ? EINVAL: ret; - DEBUG(SSSDBG_CRIT_FAILURE, - "read failed [%d][%s].\n", ret, strerror(ret)); - return ret; - } - - if (len == 0 || *buf == '\0') { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing PIN.\n"); - return EINVAL; - } - - str = talloc_strndup(mem_ctx, (char *) buf, len); - if (str == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n"); - return ENOMEM; - } - - if (strlen(str) != len) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Input contains additional data, only PIN expected.\n"); - talloc_free(str); - return EINVAL; - } - - *_pin = str; - - return EOK; -} - -ssize_t -read_pin(char **pin) -{ - char *line_ptr = NULL; - struct termios old, new; - size_t line_len = 0; - ssize_t bytes_read; - ssize_t ret; - - ret = tcgetattr(STDIN_FILENO, &old); - if (ret != 0) { - DEBUG(SSSDBG_OP_FAILURE, - "Unable to get the parameters associated with stdin [%d]: %s.\n", - errno, sss_strerror(errno)); - goto done; - } - new = old; - new.c_lflag &= ~ECHO; - ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &new); - if (ret != 0) { - DEBUG(SSSDBG_OP_FAILURE, "Unable to turn echoing off [%d]: %s.\n", - errno, sss_strerror(errno)); - goto done; - } - - PRINT("Enter PIN:\n"); - fflush(stdout); - bytes_read = getline(&line_ptr, &line_len, stdin); - if (bytes_read == -1) { - DEBUG(SSSDBG_OP_FAILURE, "getline failed [%d]: %s.\n", - errno, sss_strerror(errno)); - } else { - /* Remove the end of line '\n' character */ - line_ptr[--bytes_read] = '\0'; - } - PRINT("\n"); - - ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &old); - if (ret != 0) { - DEBUG(SSSDBG_OP_FAILURE, - "Unable to restore parameters associated with stdin [%d]: %s.\n", - errno, sss_strerror(errno)); - goto done; - } - - ret = bytes_read; - *pin = line_ptr; - -done: - return ret; -} - -errno_t -generate_credentials(struct passkey_data *data, fido_dev_t *dev, - fido_cred_t *cred) -{ - TALLOC_CTX *tmp_ctx = NULL; - char *pin = NULL; - char *tmp_pin = NULL; - bool has_pin; - ssize_t pin_len = 0; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - return ENOMEM; - } - - has_pin = fido_dev_has_pin(dev); - if (has_pin == true) { - if (data->quiet == true) { - ret = passkey_recv_pin(tmp_ctx, STDIN_FILENO, &pin); - if (ret != EOK) { - goto done; - } - } else { - pin_len = read_pin(&tmp_pin); - if (pin_len == -1) { - ret = ERR_INPUT_PARSE; - goto done; - } - pin = talloc_strdup(tmp_ctx, tmp_pin); - sss_erase_mem_securely(tmp_pin, pin_len); - free(tmp_pin); - } - } - - if (data->quiet == false) { - PRINT("Please touch the device.\n"); - fflush(stdout); - } - ret = fido_dev_make_cred(dev, cred, pin); - sss_erase_mem_securely(pin, pin_len); - - if (ret != FIDO_OK) { - if (ret == FIDO_ERR_PIN_INVALID) { - ERROR("Invalid PIN.\n"); - } - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_make_cred failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - if (has_pin == true) { - ret = fido_cred_set_uv(cred, FIDO_OPT_TRUE); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_uv failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - } - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -verify_credentials(const fido_cred_t *const cred) -{ - errno_t ret; - - if (fido_cred_x5c_ptr(cred) != NULL) { - ret = fido_cred_verify(cred); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_verify failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - } else { - DEBUG(SSSDBG_TRACE_FUNC, - "Attestation certificate missing. " - "Falling back to self attestation.\n"); - ret = fido_cred_verify_self(cred); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_cred_verify_self failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - } - -done: - return ret; -} - -errno_t -print_credentials(const struct passkey_data *data, - const fido_cred_t *const cred) -{ - TALLOC_CTX *tmp_ctx = NULL; - const unsigned char *cred_id = NULL; - const unsigned char *public_key = NULL; - const char *b64_cred_id = NULL; - char *pem_key = NULL; - size_t cred_id_len; - size_t user_key_len; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - cred_id = fido_cred_id_ptr(cred); - if (cred_id == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_id_ptr failed.\n"); - ret = ERR_CREDS_INVALID; - goto done; - } - - cred_id_len = fido_cred_id_len(cred); - if (cred_id_len == 0) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_id_len returned 0.\n"); - ret = ERR_CREDS_INVALID; - goto done; - } - - b64_cred_id = sss_base64_encode(tmp_ctx, cred_id, cred_id_len); - if (b64_cred_id == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "failed to encode key handle.\n"); - ret = ENOMEM; - goto done; - } - - public_key = fido_cred_pubkey_ptr(cred); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_pubkey_ptr failed.\n"); - ret = ERR_CREDS_INVALID; - goto done; - } - - user_key_len = fido_cred_pubkey_len(cred); - if (user_key_len == 0) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_pubkey_len returned 0.\n"); - ret = ERR_CREDS_INVALID; - goto done; - } - - ret = public_key_to_base64(tmp_ctx, data, public_key, user_key_len, - &pem_key); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "failed to format public key to b64 [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - PRINT("passkey:%s,%s\n", b64_cred_id, pem_key); - if (data->mapping_file != NULL) { - print_credentials_to_file(data, b64_cred_id, pem_key); - } - ret = EOK; - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -print_credentials_to_file(const struct passkey_data *data, - const char *b64_cred_id, - const char *pem_key) -{ - TALLOC_CTX *tmp_ctx = NULL; - char *mapping_data = NULL; - int mapping_data_len = 0; - int fd = -1; - ssize_t written = 0; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - mapping_data = talloc_asprintf(tmp_ctx, "passkey:%s,%s", - b64_cred_id, pem_key); - if (mapping_data == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); - ret = ENOMEM; - goto done; - } - mapping_data_len = strlen(mapping_data); - - fd = open(data->mapping_file, O_WRONLY|O_CREAT, 0640); - if (fd == -1) { - ret = errno; - DEBUG(SSSDBG_OP_FAILURE, - "open() failed [%d][%s]\n", ret, strerror(ret)); - ret = EIO; - goto done; - } - - errno = 0; - written = sss_atomic_write_s(fd, mapping_data, mapping_data_len); - if (written == -1) { - ret = errno; - DEBUG(SSSDBG_OP_FAILURE, - "Write failed [%d][%s].\n", ret, strerror(ret)); - goto done; - } - - if (written != mapping_data_len) { - DEBUG(SSSDBG_OP_FAILURE, - "Write error, wrote [%zd] bytes, expected [%d]\n", - written, mapping_data_len); - ret = EIO; - goto done; - } - - ret = EOK; - -done: - if (fd != -1) { - if (close(fd) == -1) { - DEBUG(SSSDBG_OP_FAILURE, - "Close failed [%s].\n", strerror(errno)); - } - } - talloc_free(tmp_ctx); - - return ret; -} - -int -es256_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *es256_key, - size_t es256_key_len, EVP_PKEY **_evp_pkey) -{ - EVP_PKEY *evp_pkey = NULL; - es256_pk_t *public_key = NULL; - errno_t ret; - - public_key = es256_pk_new(); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "es256_pk_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = es256_pk_from_ptr(public_key, es256_key, es256_key_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "es256_pk_from_ptr failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - evp_pkey = es256_pk_to_EVP_PKEY(public_key); - if (evp_pkey == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "es256_pk_to_EVP_PKEY failed.\n"); - ret = ENOMEM; - goto done; - } - - *_evp_pkey = evp_pkey; - ret = EOK; - -done: - es256_pk_free(&public_key); - - return ret; -} - -int -rs256_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *rs256_key, - size_t rs256_key_len, EVP_PKEY **_evp_pkey) -{ - EVP_PKEY *evp_pkey = NULL; - rs256_pk_t *public_key = NULL; - errno_t ret; - - public_key = rs256_pk_new(); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = rs256_pk_from_ptr(public_key, rs256_key, rs256_key_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_from_ptr failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - evp_pkey = rs256_pk_to_EVP_PKEY(public_key); - if (evp_pkey == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_to_EVP_PKEY failed.\n"); - ret = ENOMEM; - goto done; - } - - *_evp_pkey = evp_pkey; - ret = EOK; - -done: - rs256_pk_free(&public_key); - - return ret; -} - -int -eddsa_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *eddsa_key, - size_t eddsa_key_len, EVP_PKEY **_evp_pkey) -{ - EVP_PKEY *evp_pkey = NULL; - eddsa_pk_t *public_key = NULL; - errno_t ret; - - public_key = eddsa_pk_new(); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = eddsa_pk_from_ptr(public_key, eddsa_key, eddsa_key_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_from_ptr failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - evp_pkey = eddsa_pk_to_EVP_PKEY(public_key); - if (evp_pkey == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_to_EVP_PKEY failed.\n"); - ret = ENOMEM; - goto done; - } - - *_evp_pkey = evp_pkey; - ret = EOK; - -done: - eddsa_pk_free(&public_key); - - return ret; -} - -errno_t -evp_pkey_to_es256_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) -{ - void *public_key = NULL; - errno_t ret; - - public_key = es256_pk_new(); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "es256_pk_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = es256_pk_from_EVP_PKEY(public_key, evp_pkey); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "es256_pk_from_EVP_PKEY failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - _pk_data->public_key = public_key; - ret = EOK; - -done: - return ret; -} - -errno_t -evp_pkey_to_rs256_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) -{ - void *public_key = NULL; - errno_t ret; - - public_key = rs256_pk_new(); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = rs256_pk_from_EVP_PKEY(public_key, evp_pkey); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "rs256_pk_from_EVP_PKEY failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - _pk_data->public_key = public_key; - ret = EOK; - -done: - return ret; -} - -errno_t -evp_pkey_to_eddsa_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) -{ - void *public_key = NULL; - errno_t ret; - - public_key = eddsa_pk_new(); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = eddsa_pk_from_EVP_PKEY(public_key, evp_pkey); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "eddsa_pk_from_EVP_PKEY failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - _pk_data->public_key = public_key; - ret = EOK; - -done: - return ret; -} +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa + + Copyright (C) 2022 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 . +*/ + +#include +#include +#include + +#include +#include +#include + +#include "util/crypto/sss_crypto.h" +#include "util/debug.h" +#include "util/util.h" + +#include "passkey_child.h" + +#define IN_BUF_SIZE 1024 + +errno_t +prepare_credentials(struct passkey_data *data, fido_dev_t *dev, + fido_cred_t *cred) +{ + unsigned char cdh[32]; + fido_opt_t rk = FIDO_OPT_OMIT; + bool has_pin; + bool has_uv; + errno_t ret = EOK; + + ret = fido_cred_set_type(cred, data->type); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_type failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + ret = sss_generate_csprng_buffer(cdh, sizeof(cdh)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_generate_csprng_buffer failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + ret = fido_cred_set_clientdata_hash(cred, cdh, sizeof(cdh)); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_cred_set_clientdata_hash failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Setting Relying Party ID and name to %s.\n", + data->domain); + + ret = fido_cred_set_rp(cred, data->domain, data->domain); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_rp failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + if (data->user_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "user_id must be allocated before using it.\n"); + ret = ENOMEM; + goto done; + } + + ret = sss_generate_csprng_buffer(data->user_id, USER_ID_SIZE); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_generate_csprng_buffer failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Setting user: %s.\n", data->shortname); + + ret = fido_cred_set_user(cred, data->user_id, USER_ID_SIZE, + data->shortname, NULL, NULL); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_user failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + if (data->cred_type == CRED_DISCOVERABLE) { + rk = FIDO_OPT_TRUE; + } + + /* Set to FIDO_OPT_OMIT instead of FIDO_OPT_FALSE for compatibility reasons + */ + ret = fido_cred_set_rk(cred, rk); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_rk failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + has_uv = fido_dev_has_uv(dev); + has_pin = fido_dev_has_pin(dev); + if (data->user_verification == FIDO_OPT_TRUE && has_uv == false + && has_pin == false) { + DEBUG(SSSDBG_OP_FAILURE, + "Policy enabled user-verification but there isn't any " + "verification method set.\n"); + ret = EINVAL; + goto done; + } + + if (data->user_verification == FIDO_OPT_FALSE + && (has_uv == true || has_pin == true)) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Policy disabled user-verification but the key settings are " + "enforcing it. Thus, enabling user-verification.\n"); + data->user_verification = FIDO_OPT_TRUE; + } + + if (has_uv == true) { + ret = fido_cred_set_uv(cred, FIDO_OPT_TRUE); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_uv failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } + +done: + return ret; +} + +errno_t +passkey_recv_pin(TALLOC_CTX *mem_ctx, int fd, char **_pin) +{ + uint8_t buf[IN_BUF_SIZE]; + ssize_t len; + errno_t ret; + char *str; + + errno = 0; + len = sss_atomic_read_s(fd, buf, IN_BUF_SIZE); + if (len == -1) { + ret = errno; + ret = (ret == 0) ? EINVAL: ret; + DEBUG(SSSDBG_CRIT_FAILURE, + "read failed [%d][%s].\n", ret, strerror(ret)); + return ret; + } + + if (len == 0 || *buf == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing PIN.\n"); + return EINVAL; + } + + str = talloc_strndup(mem_ctx, (char *) buf, len); + if (str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n"); + return ENOMEM; + } + + if (strlen(str) != len) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Input contains additional data, only PIN expected.\n"); + talloc_free(str); + return EINVAL; + } + + /* see FIX no PIN in sss_cli.c */ + if ((strncasecmp(str, "null", len) == 0) || + (strncasecmp(str, "NULL", len) == 0)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "%s PIN is received - return EINVAL\n", str); + talloc_free(str); + return EINVAL; + } + + *_pin = str; + + return EOK; +} + +ssize_t +read_pin(char **pin) +{ + char *line_ptr = NULL; + struct termios old, new; + size_t line_len = 0; + ssize_t bytes_read; + ssize_t ret; + + ret = tcgetattr(STDIN_FILENO, &old); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to get the parameters associated with stdin [%d]: %s.\n", + errno, sss_strerror(errno)); + goto done; + } + new = old; + new.c_lflag &= ~ECHO; + ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &new); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to turn echoing off [%d]: %s.\n", + errno, sss_strerror(errno)); + goto done; + } + + PRINT("Enter PIN:\n"); + fflush(stdout); + bytes_read = getline(&line_ptr, &line_len, stdin); + if (bytes_read == -1) { + DEBUG(SSSDBG_OP_FAILURE, "getline failed [%d]: %s.\n", + errno, sss_strerror(errno)); + } else { + /* Remove the end of line '\n' character */ + line_ptr[--bytes_read] = '\0'; + } + PRINT("\n"); + + ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &old); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to restore parameters associated with stdin [%d]: %s.\n", + errno, sss_strerror(errno)); + goto done; + } + + ret = bytes_read; + *pin = line_ptr; + +done: + return ret; +} + +errno_t +generate_credentials(struct passkey_data *data, fido_dev_t *dev, + fido_cred_t *cred) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *pin = NULL; + char *tmp_pin = NULL; + bool has_pin; + ssize_t pin_len = 0; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + has_pin = fido_dev_has_pin(dev); + if (has_pin == true) { + if (data->quiet == true) { + ret = passkey_recv_pin(tmp_ctx, STDIN_FILENO, &pin); + if (ret != EOK) { + goto done; + } + } else { + pin_len = read_pin(&tmp_pin); + if (pin_len == -1) { + ret = ERR_INPUT_PARSE; + goto done; + } + pin = talloc_strdup(tmp_ctx, tmp_pin); + sss_erase_mem_securely(tmp_pin, pin_len); + free(tmp_pin); + } + } + + if (data->quiet == false) { + PRINT("Please touch the device.\n"); + fflush(stdout); + } + ret = fido_dev_make_cred(dev, cred, pin); + sss_erase_mem_securely(pin, pin_len); + + if (ret != FIDO_OK) { + if (ret == FIDO_ERR_PIN_INVALID) { + ERROR("Invalid PIN.\n"); + } + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_make_cred failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + if (has_pin == true) { + ret = fido_cred_set_uv(cred, FIDO_OPT_TRUE); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_uv failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +verify_credentials(const fido_cred_t *const cred) +{ + errno_t ret; + + if (fido_cred_x5c_ptr(cred) != NULL) { + ret = fido_cred_verify(cred); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_verify failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Attestation certificate missing. " + "Falling back to self attestation.\n"); + ret = fido_cred_verify_self(cred); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_cred_verify_self failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } + +done: + return ret; +} + +errno_t +print_credentials(const struct passkey_data *data, + const fido_cred_t *const cred) +{ + TALLOC_CTX *tmp_ctx = NULL; + const unsigned char *cred_id = NULL; + const unsigned char *public_key = NULL; + const char *b64_cred_id = NULL; + char *pem_key = NULL; + size_t cred_id_len; + size_t user_key_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + cred_id = fido_cred_id_ptr(cred); + if (cred_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_id_ptr failed.\n"); + ret = ERR_CREDS_INVALID; + goto done; + } + + cred_id_len = fido_cred_id_len(cred); + if (cred_id_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_id_len returned 0.\n"); + ret = ERR_CREDS_INVALID; + goto done; + } + + b64_cred_id = sss_base64_encode(tmp_ctx, cred_id, cred_id_len); + if (b64_cred_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to encode key handle.\n"); + ret = ENOMEM; + goto done; + } + + public_key = fido_cred_pubkey_ptr(cred); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_pubkey_ptr failed.\n"); + ret = ERR_CREDS_INVALID; + goto done; + } + + user_key_len = fido_cred_pubkey_len(cred); + if (user_key_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_pubkey_len returned 0.\n"); + ret = ERR_CREDS_INVALID; + goto done; + } + + ret = public_key_to_base64(tmp_ctx, data, public_key, user_key_len, + &pem_key); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "failed to format public key to b64 [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + PRINT("passkey:%s,%s\n", b64_cred_id, pem_key); + if (data->mapping_file != NULL) { + print_credentials_to_file(data, b64_cred_id, pem_key); + } + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +print_credentials_to_file(const struct passkey_data *data, + const char *b64_cred_id, + const char *pem_key) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *mapping_data = NULL; + int mapping_data_len = 0; + int fd = -1; + ssize_t written = 0; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + mapping_data = talloc_asprintf(tmp_ctx, "passkey:%s,%s", + b64_cred_id, pem_key); + if (mapping_data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + mapping_data_len = strlen(mapping_data); + + fd = open(data->mapping_file, O_WRONLY|O_CREAT, 0640); + if (fd == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "open() failed [%d][%s]\n", ret, strerror(ret)); + ret = EIO; + goto done; + } + + errno = 0; + written = sss_atomic_write_s(fd, mapping_data, mapping_data_len); + if (written == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "Write failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + if (written != mapping_data_len) { + DEBUG(SSSDBG_OP_FAILURE, + "Write error, wrote [%zd] bytes, expected [%d]\n", + written, mapping_data_len); + ret = EIO; + goto done; + } + + ret = EOK; + +done: + if (fd != -1) { + if (close(fd) == -1) { + DEBUG(SSSDBG_OP_FAILURE, + "Close failed [%s].\n", strerror(errno)); + } + } + talloc_free(tmp_ctx); + + return ret; +} + +int +es256_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *es256_key, + size_t es256_key_len, EVP_PKEY **_evp_pkey) +{ + EVP_PKEY *evp_pkey = NULL; + es256_pk_t *public_key = NULL; + errno_t ret; + + public_key = es256_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "es256_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = es256_pk_from_ptr(public_key, es256_key, es256_key_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "es256_pk_from_ptr failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + evp_pkey = es256_pk_to_EVP_PKEY(public_key); + if (evp_pkey == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "es256_pk_to_EVP_PKEY failed.\n"); + ret = ENOMEM; + goto done; + } + + *_evp_pkey = evp_pkey; + ret = EOK; + +done: + es256_pk_free(&public_key); + + return ret; +} + +int +rs256_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *rs256_key, + size_t rs256_key_len, EVP_PKEY **_evp_pkey) +{ + EVP_PKEY *evp_pkey = NULL; + rs256_pk_t *public_key = NULL; + errno_t ret; + + public_key = rs256_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = rs256_pk_from_ptr(public_key, rs256_key, rs256_key_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_from_ptr failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + evp_pkey = rs256_pk_to_EVP_PKEY(public_key); + if (evp_pkey == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_to_EVP_PKEY failed.\n"); + ret = ENOMEM; + goto done; + } + + *_evp_pkey = evp_pkey; + ret = EOK; + +done: + rs256_pk_free(&public_key); + + return ret; +} + +int +eddsa_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *eddsa_key, + size_t eddsa_key_len, EVP_PKEY **_evp_pkey) +{ + EVP_PKEY *evp_pkey = NULL; + eddsa_pk_t *public_key = NULL; + errno_t ret; + + public_key = eddsa_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = eddsa_pk_from_ptr(public_key, eddsa_key, eddsa_key_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_from_ptr failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + evp_pkey = eddsa_pk_to_EVP_PKEY(public_key); + if (evp_pkey == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_to_EVP_PKEY failed.\n"); + ret = ENOMEM; + goto done; + } + + *_evp_pkey = evp_pkey; + ret = EOK; + +done: + eddsa_pk_free(&public_key); + + return ret; +} + +errno_t +evp_pkey_to_es256_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) +{ + void *public_key = NULL; + errno_t ret; + + public_key = es256_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "es256_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = es256_pk_from_EVP_PKEY(public_key, evp_pkey); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "es256_pk_from_EVP_PKEY failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + _pk_data->public_key = public_key; + ret = EOK; + +done: + return ret; +} + +errno_t +evp_pkey_to_rs256_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) +{ + void *public_key = NULL; + errno_t ret; + + public_key = rs256_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = rs256_pk_from_EVP_PKEY(public_key, evp_pkey); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "rs256_pk_from_EVP_PKEY failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + _pk_data->public_key = public_key; + ret = EOK; + +done: + return ret; +} + +errno_t +evp_pkey_to_eddsa_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) +{ + void *public_key = NULL; + errno_t ret; + + public_key = eddsa_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = eddsa_pk_from_EVP_PKEY(public_key, evp_pkey); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "eddsa_pk_from_EVP_PKEY failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + _pk_data->public_key = public_key; + ret = EOK; + +done: + return ret; +} diff --git a/src/passkey_child/passkey_child_devices.c b/src/passkey_child/passkey_child_devices.c index 2011b12f661..74e0559e49a 100644 --- a/src/passkey_child/passkey_child_devices.c +++ b/src/passkey_child/passkey_child_devices.c @@ -1,246 +1,246 @@ -/* - SSSD - - Helper child to commmunicate with passkey devices - - Authors: - Iker Pedrosa - - Copyright (C) 2022 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 . -*/ - -#include "util/debug.h" -#include "util/util.h" - -#include "passkey_child.h" - -errno_t -list_devices(fido_dev_info_t *dev_list, size_t *dev_number) -{ - errno_t ret; - - for (int i = 0; i < TIMEOUT; i += FREQUENCY) { - ret = fido_dev_info_manifest(dev_list, DEVLIST_SIZE, dev_number); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "Unable to discover device(s) [%d]: %s.\n", - ret, fido_strerr(ret)); - } - - if ((*dev_number) != 0) { - DEBUG(SSSDBG_TRACE_FUNC, "Device found.\n"); - break; - } - - if (i < (TIMEOUT - 1)) { - DEBUG(SSSDBG_TRACE_FUNC, "No device available, retrying.\n"); - sleep(FREQUENCY); - } - } - - return ret; -} - -errno_t -select_device(enum action_opt action, fido_dev_info_t *dev_list, - size_t dev_list_len, fido_assert_t *assert, - fido_dev_t **_dev) -{ - fido_dev_t *dev = NULL; - const char *path; - const fido_dev_info_t *di = NULL; - errno_t ret; - - if (dev_list_len == 0) { - DEBUG(SSSDBG_OP_FAILURE, "No device found. Aborting.\n"); - ret = ENOENT; - goto done; - } else if (action == ACTION_REGISTER && dev_list_len == 1) { - dev = fido_dev_new(); - if (dev == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_new failed.\n"); - ret = ENOMEM; - goto done; - } - - di = fido_dev_info_ptr(dev_list, 0); - if (di == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_ptr failed.\n"); - ret = ENOMEM; - goto done; - } - - path = fido_dev_info_path(di); - if (path == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_path failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = fido_dev_open(dev, path); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_open failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - *_dev = dev; - } else if (action == ACTION_REGISTER && dev_list_len > 1) { - DEBUG(SSSDBG_OP_FAILURE, - "Only one device is supported at a time. Aborting.\n"); - fprintf(stderr, "Only one device is supported at a time. Aborting.\n"); - ret = EPERM; - goto done; - } else { - if (assert == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "assert cannot be NULL.\n"); - ret = EINVAL; - goto done; - } - - ret = select_from_multiple_devices(dev_list, dev_list_len, assert, _dev); - if (ret != FIDO_OK) { - goto done; - } - } - - ret = EOK; - -done: - if (ret != EOK) { - if (dev != NULL) { - fido_dev_close(dev); - } - fido_dev_free(&dev); - } - - return ret; -} - -errno_t -select_from_multiple_devices(fido_dev_info_t *dev_list, - size_t dev_list_len, - fido_assert_t *assert, - fido_dev_t **_dev) -{ - fido_dev_t *dev = NULL; - const fido_dev_info_t *di = NULL; - const char *path; - bool is_fido2; - errno_t ret; - - DEBUG(SSSDBG_TRACE_FUNC, - "Working with %ld authenticator(s).\n", dev_list_len); - - for (size_t i = 0; i < dev_list_len; i++) { - dev = fido_dev_new(); - if (dev == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_new failed.\n"); - ret = ENOMEM; - goto done; - } - - di = fido_dev_info_ptr(dev_list, i); - if (di == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_ptr failed.\n"); - ret = ENOMEM; - goto done; - } - - path = fido_dev_info_path(di); - if (path == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_path failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = fido_dev_open(dev, path); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_open failed [%d]: %s.\n", - ret, fido_strerr(ret)); - } - - is_fido2 = fido_dev_is_fido2(dev); - ret = fido_dev_get_assert(dev, assert, NULL); - if ((is_fido2 == false && ret == FIDO_ERR_USER_PRESENCE_REQUIRED) - || (is_fido2 == true && ret == FIDO_OK)) { - *_dev = dev; - DEBUG(SSSDBG_FUNC_DATA, "Assertion found in passkey %ld.\n", i); - ret = EOK; - goto done; - } - - DEBUG(SSSDBG_FUNC_DATA, "Assertion not found in passkey %ld.\n", i); - - fido_dev_close(dev); - fido_dev_free(&dev); - } - - ret = FIDO_ERR_NOTFOUND; - DEBUG(SSSDBG_OP_FAILURE, "Assertion not found.\n"); - -done: - return ret; -} - -errno_t -get_device_options(fido_dev_t *dev, struct passkey_data *_data) -{ - bool has_pin; - bool has_uv; - bool supports_uv; - errno_t ret; - - has_uv = fido_dev_has_uv(dev); - has_pin = fido_dev_has_pin(dev); - supports_uv = fido_dev_supports_uv(dev); - - if (_data->user_verification == FIDO_OPT_TRUE && has_pin != true - && has_uv != true) { - DEBUG(SSSDBG_OP_FAILURE, - "Policy enabled user-verification but there isn't any " - "verification method set.\n"); - ret = EINVAL; - goto done; - } - - if (_data->user_verification == FIDO_OPT_OMIT - && (has_uv == true || has_pin == true)) { - DEBUG(SSSDBG_CONF_SETTINGS, - "Policy didn't indicate any preference for user-verification " - "but the key settings are enforcing it. Thus, enforcing the " - "user-verification.\n"); - _data->user_verification = FIDO_OPT_TRUE; - ret = EOK; - goto done; - } - - if (_data->user_verification == FIDO_OPT_FALSE - && supports_uv == false) { - DEBUG(SSSDBG_CONF_SETTINGS, - "Policy disabled user-verification but the key doesn't support " - "it. Thus, omitting the user-verification.\n"); - _data->user_verification = FIDO_OPT_OMIT; - ret = EOK; - goto done; - } - - ret = EOK; - -done: - - return ret; -} +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa + + Copyright (C) 2022 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 . +*/ + +#include "util/debug.h" +#include "util/util.h" + +#include "passkey_child.h" + +errno_t +list_devices(fido_dev_info_t *dev_list, size_t *dev_number) +{ + errno_t ret; + + for (int i = 0; i < TIMEOUT; i += FREQUENCY) { + ret = fido_dev_info_manifest(dev_list, DEVLIST_SIZE, dev_number); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to discover device(s) [%d]: %s.\n", + ret, fido_strerr(ret)); + } + + if ((*dev_number) != 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Device found.\n"); + break; + } + + if (i < (TIMEOUT - 1)) { + DEBUG(SSSDBG_TRACE_FUNC, "No device available, retrying.\n"); + sleep(FREQUENCY); + } + } + + return ret; +} + +errno_t +select_device(enum action_opt action, fido_dev_info_t *dev_list, + size_t dev_list_len, fido_assert_t *assert, + fido_dev_t **_dev) +{ + fido_dev_t *dev = NULL; + const char *path; + const fido_dev_info_t *di = NULL; + errno_t ret; + + if (dev_list_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "No device found. Aborting.\n"); + ret = ENOENT; + goto done; + } else if (action == ACTION_REGISTER && dev_list_len == 1) { + dev = fido_dev_new(); + if (dev == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_new failed.\n"); + ret = ENOMEM; + goto done; + } + + di = fido_dev_info_ptr(dev_list, 0); + if (di == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_ptr failed.\n"); + ret = ENOMEM; + goto done; + } + + path = fido_dev_info_path(di); + if (path == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_path failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_dev_open(dev, path); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_open failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + *_dev = dev; + } else if (action == ACTION_REGISTER && dev_list_len > 1) { + DEBUG(SSSDBG_OP_FAILURE, + "Only one device is supported at a time. Aborting.\n"); + fprintf(stderr, "Only one device is supported at a time. Aborting.\n"); + ret = EPERM; + goto done; + } else { + if (assert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "assert cannot be NULL.\n"); + ret = EINVAL; + goto done; + } + + ret = select_from_multiple_devices(dev_list, dev_list_len, assert, _dev); + if (ret != FIDO_OK) { + goto done; + } + } + + ret = EOK; + +done: + if (ret != EOK) { + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + } + + return ret; +} + +errno_t +select_from_multiple_devices(fido_dev_info_t *dev_list, + size_t dev_list_len, + fido_assert_t *assert, + fido_dev_t **_dev) +{ + fido_dev_t *dev = NULL; + const fido_dev_info_t *di = NULL; + const char *path; + bool is_fido2; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, + "Working with %ld authenticator(s).\n", dev_list_len); + + for (size_t i = 0; i < dev_list_len; i++) { + dev = fido_dev_new(); + if (dev == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_new failed.\n"); + ret = ENOMEM; + goto done; + } + + di = fido_dev_info_ptr(dev_list, i); + if (di == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_ptr failed.\n"); + ret = ENOMEM; + goto done; + } + + path = fido_dev_info_path(di); + if (path == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_path failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_dev_open(dev, path); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_open failed [%d]: %s.\n", + ret, fido_strerr(ret)); + } + + is_fido2 = fido_dev_is_fido2(dev); + ret = fido_dev_get_assert(dev, assert, NULL); + if ((is_fido2 == false && ret == FIDO_ERR_USER_PRESENCE_REQUIRED) + || (is_fido2 == true && ret == FIDO_OK)) { + *_dev = dev; + DEBUG(SSSDBG_FUNC_DATA, "Assertion found in passkey %ld.\n", i); + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Assertion not found in passkey %ld.\n", i); + + fido_dev_close(dev); + fido_dev_free(&dev); + } + + ret = FIDO_ERR_NOTFOUND; + DEBUG(SSSDBG_OP_FAILURE, "Assertion not found.\n"); + +done: + return ret; +} + +errno_t +get_device_options(fido_dev_t *dev, struct passkey_data *_data) +{ + bool has_pin; + bool has_uv; + bool supports_uv; + errno_t ret; + + has_uv = fido_dev_has_uv(dev); + has_pin = fido_dev_has_pin(dev); + supports_uv = fido_dev_supports_uv(dev); + + if (_data->user_verification == FIDO_OPT_TRUE && has_pin != true + && has_uv != true) { + DEBUG(SSSDBG_OP_FAILURE, + "Policy enabled user-verification but there isn't any " + "verification method set.\n"); + ret = EINVAL; + goto done; + } + + if (_data->user_verification == FIDO_OPT_OMIT + && (has_uv == true || has_pin == true)) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Policy didn't indicate any preference for user-verification " + "but the key settings are enforcing it. Thus, enforcing the " + "user-verification.\n"); + _data->user_verification = FIDO_OPT_TRUE; + ret = EOK; + goto done; + } + + if (_data->user_verification == FIDO_OPT_FALSE + && supports_uv == false) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Policy disabled user-verification but the key doesn't support " + "it. Thus, omitting the user-verification.\n"); + _data->user_verification = FIDO_OPT_OMIT; + ret = EOK; + goto done; + } + + ret = EOK; + +done: + + return ret; +} diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c index 42834eeae0d..26699dbd3f8 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -1,3106 +1,3225 @@ -/* - SSSD - - PAM Responder - - Copyright (C) Simo Sorce 2009 - Copyright (C) Sumit Bose 2009 - - 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 . -*/ - -#define _GNU_SOURCE - -#include -#include -#include "util/util.h" -#include "util/auth_utils.h" -#include "util/find_uid.h" -#include "util/sss_ptr_hash.h" -#include "db/sysdb.h" -#include "confdb/confdb.h" -#include "responder/common/responder_packet.h" -#include "responder/common/responder.h" -#include "responder/common/negcache.h" -#include "providers/data_provider.h" -#include "responder/pam/pamsrv.h" -#include "responder/pam/pamsrv_passkey.h" -#include "responder/pam/pam_helpers.h" -#include "responder/common/cache_req/cache_req.h" - -enum pam_verbosity { - PAM_VERBOSITY_NO_MESSAGES = 0, - PAM_VERBOSITY_IMPORTANT, - PAM_VERBOSITY_INFO, - PAM_VERBOSITY_DEBUG -}; - -#define DEFAULT_PAM_VERBOSITY PAM_VERBOSITY_IMPORTANT - -struct pam_initgroup_enum_str { - enum pam_initgroups_scheme scheme; - const char *option; -}; - -struct pam_initgroup_enum_str pam_initgroup_enum_str[] = { - { PAM_INITGR_NEVER, "never" }, - { PAM_INITGR_NO_SESSION, "no_session" }, - { PAM_INITGR_ALWAYS, "always" }, - { PAM_INITGR_INVALID, NULL } -}; - -enum pam_initgroups_scheme pam_initgroups_string_to_enum(const char *str) -{ - size_t c; - - for (c = 0 ; pam_initgroup_enum_str[c].option != NULL; c++) { - if (strcasecmp(pam_initgroup_enum_str[c].option, str) == 0) { - return pam_initgroup_enum_str[c].scheme; - } - } - - return PAM_INITGR_INVALID; -} - -const char *pam_initgroup_enum_to_string(enum pam_initgroups_scheme scheme) -{ - size_t c; - - for (c = 0 ; pam_initgroup_enum_str[c].option != NULL; c++) { - if (pam_initgroup_enum_str[c].scheme == scheme) { - return pam_initgroup_enum_str[c].option; - } - } - - return "(NULL)"; -} - -static errno_t -pam_null_last_online_auth_with_curr_token(struct sss_domain_info *domain, - const char *username); -static errno_t -pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain, - const char *name, - uint64_t *_value); - -void pam_reply(struct pam_auth_req *preq); - -static errno_t check_cert(TALLOC_CTX *mctx, - struct tevent_context *ev, - struct pam_ctx *pctx, - struct pam_auth_req *preq, - struct pam_data *pd); - -int pam_check_user_done(struct pam_auth_req *preq, int ret); - -static errno_t pack_user_info_msg(TALLOC_CTX *mem_ctx, - const char *user_error_message, - size_t *resp_len, - uint8_t **_resp) -{ - uint32_t resp_type = SSS_PAM_USER_INFO_ACCOUNT_EXPIRED; - size_t err_len; - uint8_t *resp; - size_t p; - - err_len = strlen(user_error_message); - *resp_len = 2 * sizeof(uint32_t) + err_len; - resp = talloc_size(mem_ctx, *resp_len); - if (resp == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); - return ENOMEM; - } - - p = 0; - SAFEALIGN_SET_UINT32(&resp[p], resp_type, &p); - SAFEALIGN_SET_UINT32(&resp[p], err_len, &p); - safealign_memcpy(&resp[p], user_error_message, err_len, &p); - if (p != *resp_len) { - DEBUG(SSSDBG_FATAL_FAILURE, "Size mismatch\n"); - } - - *_resp = resp; - return EOK; -} - -static void inform_user(struct pam_data* pd, const char *pam_message) -{ - size_t msg_len; - uint8_t *msg; - errno_t ret; - - ret = pack_user_info_msg(pd, pam_message, &msg_len, &msg); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "pack_user_info_msg failed.\n"); - } else { - ret = pam_add_response(pd, SSS_PAM_USER_INFO, msg_len, msg); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); - } - } -} - -static bool is_domain_requested(struct pam_data *pd, const char *domain_name) -{ - int i; - - /* If none specific domains got requested via pam, all domains are allowed. - * Which mimics the default/original behaviour. - */ - if (!pd->requested_domains) { - return true; - } - - for (i = 0; pd->requested_domains[i]; i++) { - if (strcasecmp(domain_name, pd->requested_domains[i])) { - continue; - } - - return true; - } - - return false; -} - -static int extract_authtok_v2(struct sss_auth_token *tok, - size_t data_size, uint8_t *body, size_t blen, - size_t *c) -{ - uint32_t auth_token_type; - uint32_t auth_token_length; - uint8_t *auth_token_data; - int ret = EOK; - - if (data_size < sizeof(uint32_t) || *c+data_size > blen || - SIZE_T_OVERFLOW(*c, data_size)) return EINVAL; - - SAFEALIGN_COPY_UINT32_CHECK(&auth_token_type, &body[*c], blen, c); - auth_token_length = data_size - sizeof(uint32_t); - auth_token_data = body+(*c); - - switch (auth_token_type) { - case SSS_AUTHTOK_TYPE_EMPTY: - sss_authtok_set_empty(tok); - break; - case SSS_AUTHTOK_TYPE_PASSWORD: - if (auth_token_length == 0) { - sss_authtok_set_empty(tok); - } else { - ret = sss_authtok_set_password(tok, (const char *)auth_token_data, - auth_token_length); - } - break; - case SSS_AUTHTOK_TYPE_2FA: - case SSS_AUTHTOK_TYPE_2FA_SINGLE: - case SSS_AUTHTOK_TYPE_SC_PIN: - case SSS_AUTHTOK_TYPE_SC_KEYPAD: - case SSS_AUTHTOK_TYPE_OAUTH2: - case SSS_AUTHTOK_TYPE_PASSKEY: - case SSS_AUTHTOK_TYPE_PASSKEY_KRB: - case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: - case SSS_AUTHTOK_TYPE_PAM_STACKED: - ret = sss_authtok_set(tok, auth_token_type, - auth_token_data, auth_token_length); - break; - default: - return EINVAL; - } - - *c += auth_token_length; - - return ret; -} - -static int extract_string(char **var, size_t size, uint8_t *body, size_t blen, - size_t *c) { - uint8_t *str; - - if (*c+size > blen || SIZE_T_OVERFLOW(*c, size)) return EINVAL; - - str = body+(*c); - - if (str[size-1]!='\0') return EINVAL; - - /* If the string isn't valid UTF-8, fail */ - if (!sss_utf8_check(str, size-1)) { - return EINVAL; - } - - *c += size; - - *var = (char *) str; - - return EOK; -} - -static int extract_uint32_t(uint32_t *var, size_t size, uint8_t *body, - size_t blen, size_t *c) { - - if (size != sizeof(uint32_t) || *c+size > blen || SIZE_T_OVERFLOW(*c, size)) - return EINVAL; - - SAFEALIGN_COPY_UINT32_CHECK(var, &body[*c], blen, c); - - return EOK; -} - -static int pd_set_primary_name(const struct ldb_message *msg,struct pam_data *pd) -{ - const char *name; - - name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); - if (!name) { - DEBUG(SSSDBG_CRIT_FAILURE, "A user with no name?\n"); - return EIO; - } - - if (strcmp(pd->user, name)) { - DEBUG(SSSDBG_TRACE_FUNC, "User's primary name is %s\n", name); - talloc_free(pd->user); - pd->user = talloc_strdup(pd, name); - if (!pd->user) return ENOMEM; - } - - return EOK; -} - -static int pam_parse_in_data_v2(struct pam_data *pd, - uint8_t *body, size_t blen) -{ - size_t c; - uint32_t type; - uint32_t size; - int ret; - uint32_t start; - uint32_t terminator; - char *requested_domains; - - if (blen < 4*sizeof(uint32_t)+2) { - DEBUG(SSSDBG_CRIT_FAILURE, "Received data is invalid.\n"); - return EINVAL; - } - - SAFEALIGN_COPY_UINT32(&start, body, NULL); - SAFEALIGN_COPY_UINT32(&terminator, body + blen - sizeof(uint32_t), NULL); - - if (start != SSS_START_OF_PAM_REQUEST - || terminator != SSS_END_OF_PAM_REQUEST) { - DEBUG(SSSDBG_CRIT_FAILURE, "Received data is invalid.\n"); - return EINVAL; - } - - c = sizeof(uint32_t); - do { - SAFEALIGN_COPY_UINT32_CHECK(&type, &body[c], blen, &c); - - if (type == SSS_END_OF_PAM_REQUEST) { - if (c != blen) return EINVAL; - } else { - SAFEALIGN_COPY_UINT32_CHECK(&size, &body[c], blen, &c); - /* the uint32_t end maker SSS_END_OF_PAM_REQUEST does not count to - * the remaining buffer */ - if (size > (blen - c - sizeof(uint32_t))) { - DEBUG(SSSDBG_CRIT_FAILURE, "Invalid data size.\n"); - return EINVAL; - } - - switch(type) { - case SSS_PAM_ITEM_USER: - ret = extract_string(&pd->logon_name, size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_SERVICE: - ret = extract_string(&pd->service, size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_TTY: - ret = extract_string(&pd->tty, size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_RUSER: - ret = extract_string(&pd->ruser, size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_RHOST: - ret = extract_string(&pd->rhost, size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_REQUESTED_DOMAINS: - ret = extract_string(&requested_domains, size, body, blen, - &c); - if (ret != EOK) return ret; - - ret = split_on_separator(pd, requested_domains, ',', true, - true, &pd->requested_domains, - NULL); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to parse requested_domains list!\n"); - return ret; - } - break; - case SSS_PAM_ITEM_CLI_PID: - ret = extract_uint32_t(&pd->cli_pid, size, - body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_CHILD_PID: - /* This is optional. */ - ret = extract_uint32_t(&pd->child_pid, size, - body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_AUTHTOK: - ret = extract_authtok_v2(pd->authtok, - size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_NEWAUTHTOK: - ret = extract_authtok_v2(pd->newauthtok, - size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_FLAGS: - ret = extract_uint32_t(&pd->cli_flags, size, - body, blen, &c); - if (ret != EOK) return ret; - break; - default: - DEBUG(SSSDBG_CRIT_FAILURE, - "Ignoring unknown data type [%d].\n", type); - c += size; - } - } - - } while(c < blen); - - return EOK; - -} - -static int pam_parse_in_data_v3(struct pam_data *pd, - uint8_t *body, size_t blen) -{ - int ret; - - ret = pam_parse_in_data_v2(pd, body, blen); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_parse_in_data_v2 failed.\n"); - return ret; - } - - if (pd->cli_pid == 0) { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing client PID.\n"); - return EINVAL; - } - - return EOK; -} - -static int extract_authtok_v1(struct sss_auth_token *tok, - uint8_t *body, size_t blen, size_t *c) -{ - uint32_t auth_token_type; - uint32_t auth_token_length; - uint8_t *auth_token_data; - int ret = EOK; - - SAFEALIGN_COPY_UINT32_CHECK(&auth_token_type, &body[*c], blen, c); - SAFEALIGN_COPY_UINT32_CHECK(&auth_token_length, &body[*c], blen, c); - auth_token_data = body+(*c); - - switch (auth_token_type) { - case SSS_AUTHTOK_TYPE_EMPTY: - sss_authtok_set_empty(tok); - break; - case SSS_AUTHTOK_TYPE_PASSWORD: - ret = sss_authtok_set_password(tok, (const char *)auth_token_data, - auth_token_length); - break; - default: - return EINVAL; - } - - *c += auth_token_length; - - return ret; -} - -static int pam_parse_in_data(struct pam_data *pd, - uint8_t *body, size_t blen) -{ - size_t start; - size_t end; - size_t last; - int ret; - - last = blen - 1; - end = 0; - - /* user name */ - for (start = end; end < last; end++) if (body[end] == '\0') break; - if (body[end++] != '\0') return EINVAL; - pd->logon_name = (char *) &body[start]; - - for (start = end; end < last; end++) if (body[end] == '\0') break; - if (body[end++] != '\0') return EINVAL; - pd->service = (char *) &body[start]; - - for (start = end; end < last; end++) if (body[end] == '\0') break; - if (body[end++] != '\0') return EINVAL; - pd->tty = (char *) &body[start]; - - for (start = end; end < last; end++) if (body[end] == '\0') break; - if (body[end++] != '\0') return EINVAL; - pd->ruser = (char *) &body[start]; - - for (start = end; end < last; end++) if (body[end] == '\0') break; - if (body[end++] != '\0') return EINVAL; - pd->rhost = (char *) &body[start]; - - ret = extract_authtok_v1(pd->authtok, body, blen, &end); - if (ret) { - DEBUG(SSSDBG_CRIT_FAILURE, "Invalid auth token\n"); - return ret; - } - ret = extract_authtok_v1(pd->newauthtok, body, blen, &end); - if (ret) { - DEBUG(SSSDBG_CRIT_FAILURE, "Invalid new auth token\n"); - return ret; - } - - DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); - - return EOK; -} - -static errno_t -pam_get_local_auth_policy(struct sss_domain_info *domain, - const char *name, - bool *_sc_allow, - bool *_passkey_allow) -{ - TALLOC_CTX *tmp_ctx = NULL; - const char *attrs[] = { SYSDB_LOCAL_SMARTCARD_AUTH, SYSDB_LOCAL_PASSKEY_AUTH, NULL }; - struct ldb_message *ldb_msg; - bool sc_allow = false; - bool passkey_allow = false; - errno_t ret; - - if (name == NULL || *name == '\0') { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); - ret = EINVAL; - goto done; - } - - if (domain->sysdb == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing sysdb db context.\n"); - ret = EINVAL; - goto done; - } - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - ret = sysdb_search_user_by_name(tmp_ctx, domain, name, attrs, &ldb_msg); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "sysdb_search_user_by_name failed [%d][%s].\n", - ret, strerror(ret)); - goto done; - } - - sc_allow = ldb_msg_find_attr_as_bool(ldb_msg, SYSDB_LOCAL_SMARTCARD_AUTH, - false); - - passkey_allow = ldb_msg_find_attr_as_bool(ldb_msg, SYSDB_LOCAL_PASSKEY_AUTH, - true); - - ret = EOK; - -done: - if (ret == EOK) { - *_sc_allow = sc_allow; - *_passkey_allow = passkey_allow; - } - - talloc_free(tmp_ctx); - return ret; -} -static errno_t set_local_auth_type(struct pam_auth_req *preq, - bool sc_allow, - bool passkey_allow) -{ - struct sysdb_attrs *attrs; - errno_t ret; - - attrs = sysdb_new_attrs(preq); - if (!attrs) { - ret = ENOMEM; - goto fail; - } - - if (sc_allow) { - /* Only set SYSDB_LOCAL_SMARTCARD_AUTH to 'true' but never to - * 'false'. The krb5 backend will only returns that Smartcard - * authentication is available if a Smartcard is present. That means - * if the user authenticates with a different method and a Smartcard - * is not present at this time 'sc_allow' will be 'false' and might - * overwrite a 'true' value written during a previous authentication - * attempt where a Smartcard was present. To avoid this we only write - * 'true' values. Since the default if SYSDB_LOCAL_SMARTCARD_AUTH is - * missing is 'false' local Smartcard authentication (offline) will - * still only be enabled if online Smartcard authentication was - * detected. */ - ret = sysdb_attrs_add_bool(attrs, SYSDB_LOCAL_SMARTCARD_AUTH, sc_allow); - if (ret != EOK) { - goto fail; - } - } - - ret = sysdb_attrs_add_bool(attrs, SYSDB_LOCAL_PASSKEY_AUTH, passkey_allow); - if (ret != EOK) { - goto fail; - } - - ret = sysdb_set_user_attr(preq->domain, preq->pd->user, attrs, - SYSDB_MOD_REP); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "set_local_auth_type failed.\n"); - preq->pd->pam_status = PAM_SYSTEM_ERR; - goto fail; - } - - return EOK; - -fail: - return ret; -} -/*=Save-Last-Login-State===================================================*/ - -static errno_t set_last_login(struct pam_auth_req *preq) -{ - struct sysdb_attrs *attrs; - errno_t ret; - - attrs = sysdb_new_attrs(preq); - if (!attrs) { - ret = ENOMEM; - goto fail; - } - - ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_ONLINE_AUTH, time(NULL)); - if (ret != EOK) { - goto fail; - } - - ret = sysdb_attrs_add_time_t(attrs, - SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, - time(NULL)); - if (ret != EOK) { - goto fail; - } - - ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_LOGIN, time(NULL)); - if (ret != EOK) { - goto fail; - } - - ret = sysdb_set_user_attr(preq->domain, preq->pd->user, attrs, - SYSDB_MOD_REP); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "set_last_login failed.\n"); - preq->pd->pam_status = PAM_SYSTEM_ERR; - goto fail; - } else { - preq->pd->last_auth_saved = true; - } - preq->callback(preq); - - return EOK; - -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 ? var_name : "(NULL)"), - (service ? service : "(NULL)")); - - 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 pam_ctx *pctx, - struct response_data *resp_list, - struct pam_data *pd) -{ - int ret; - struct response_data *resp; - uint32_t user_info_type; - int64_t expire_date = 0; - int pam_verbosity = DEFAULT_PAM_VERBOSITY; - char **new_opts; - size_t c; - const char *default_pam_response_filter[] = { "ENV:KRB5CCNAME:sudo", - "ENV:KRB5CCNAME:sudo-i", - NULL }; - - ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_VERBOSITY, DEFAULT_PAM_VERBOSITY, - &pam_verbosity); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read PAM verbosity, not fatal.\n"); - pam_verbosity = DEFAULT_PAM_VERBOSITY; - } - - if (pctx->pam_filter_opts == NULL) { - ret = confdb_get_string_as_list(pctx->rctx->cdb, pctx, - CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_RESPONSE_FILTER, - &pctx->pam_filter_opts); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read values of [%s], not fatal.\n", - CONFDB_PAM_RESPONSE_FILTER); - pctx->pam_filter_opts = NULL; - } else { - if (pctx->pam_filter_opts == NULL - || *pctx->pam_filter_opts[0] == '+' - || *pctx->pam_filter_opts[0] == '-') { - ret = mod_defaults_list(pctx, default_pam_response_filter, - pctx->pam_filter_opts, &new_opts); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to modify [%s] defaults.\n", - CONFDB_PAM_RESPONSE_FILTER); - return ret; - } - talloc_free(pctx->pam_filter_opts); - pctx->pam_filter_opts = new_opts; - } - } - - if (pctx->pam_filter_opts == NULL) { - DEBUG(SSSDBG_CONF_SETTINGS, "No PAM response filter set.\n"); - } else { - /* Make sure there are no '+' or '-' prefixes anymore */ - for (c = 0; pctx->pam_filter_opts[c] != NULL; c++) { - if (*pctx->pam_filter_opts[0] == '+' - || *pctx->pam_filter_opts[0] == '-') { - DEBUG(SSSDBG_CRIT_FAILURE, - "Unsupport mix of prefixed and not prefixed " - "values of [%s].\n", CONFDB_PAM_RESPONSE_FILTER); - return EINVAL; - } - DEBUG(SSSDBG_CONF_SETTINGS, - "PAM response filter: [%s].\n", - pctx->pam_filter_opts[c]); - } - } - } - - 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"); - ret = EINVAL; - goto done; - } - - if (pam_verbosity == PAM_VERBOSITY_NO_MESSAGES) { - resp->do_not_send_to_client = true; - resp = resp->next; - continue; - } - - memcpy(&user_info_type, resp->data, sizeof(uint32_t)); - - resp->do_not_send_to_client = false; - switch (user_info_type) { - case SSS_PAM_USER_INFO_OFFLINE_AUTH: - if (resp->len != sizeof(uint32_t) + sizeof(int64_t)) { - DEBUG(SSSDBG_CRIT_FAILURE, - "User info offline auth entry is " - "too short.\n"); - ret = EINVAL; - goto done; - } - memcpy(&expire_date, resp->data + sizeof(uint32_t), - sizeof(int64_t)); - if ((expire_date == 0 && - pam_verbosity < PAM_VERBOSITY_INFO) || - (expire_date > 0 && - pam_verbosity < PAM_VERBOSITY_IMPORTANT)) { - resp->do_not_send_to_client = true; - } - - break; - default: - DEBUG(SSSDBG_TRACE_LIBS, - "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, pctx->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; - } - - resp = resp->next; - } - - ret = EOK; -done: - - return ret; -} - -static void do_not_send_cert_info(struct pam_data *pd) -{ - struct response_data *resp; - - resp = pd->resp_list; - while (resp != NULL) { - switch (resp->type) { - case SSS_PAM_CERT_INFO: - case SSS_PAM_CERT_INFO_WITH_HINT: - resp->do_not_send_to_client = true; - break; - default: - break; - } - resp = resp->next; - } -} - -static void evaluate_pam_resp_list(struct pam_data *pd, - struct pam_resp_auth_type *_auth_types, - bool *_found_cert_info) -{ - struct response_data *resp; - struct pam_resp_auth_type types = {0}; - bool found_cert_info = false; - - resp = pd->resp_list; - while (resp != NULL) { - switch (resp->type) { - case SSS_PAM_OTP_INFO: - types.otp_auth = true; - break; - case SSS_PAM_CERT_INFO: - case SSS_PAM_CERT_INFO_WITH_HINT: - found_cert_info = true; - break; - case SSS_PAM_PASSKEY_INFO: - case SSS_PAM_PASSKEY_KRB_INFO: - types.passkey_auth = true; - break; - case SSS_PASSWORD_PROMPTING: - types.password_auth = true; - break; - case SSS_CERT_AUTH_PROMPTING: - types.cert_auth = true; - break; - default: - break; - } - resp = resp->next; - } - - if (_auth_types != NULL) { - *_auth_types = types; - } - if (_found_cert_info != NULL) { - *_found_cert_info = found_cert_info; - } -} - -static void evalute_sending_cert_info(struct pam_data *pd) -{ - struct pam_resp_auth_type types = {0}; - bool found_cert_info = false; - - evaluate_pam_resp_list(pd, &types, &found_cert_info); - - if (found_cert_info && !types.cert_auth) { - do_not_send_cert_info(pd); - } -} - -errno_t pam_get_auth_types(struct pam_data *pd, - struct pam_resp_auth_type *_auth_types) -{ - int ret; - struct pam_resp_auth_type types = {0}; - - evaluate_pam_resp_list(pd, &types, NULL); - - if (!types.password_auth && !types.otp_auth && !types.cert_auth && !types.passkey_auth) { - /* If the backend cannot determine which authentication types are - * available the default would be to prompt for a password. */ - types.password_auth = true; - types.backend_returned_no_auth_type = true; - } - - DEBUG(SSSDBG_TRACE_ALL, "Authentication types for user [%s] and service " - "[%s]:%s%s%s%s\n", pd->user, pd->service, - types.password_auth ? " password": "", - types.otp_auth ? " two-factor" : "", - types.passkey_auth ? " passkey" : "", - types.cert_auth ? " smartcard" : ""); - - ret = EOK; - - *_auth_types = types; - - return ret; -} - -static errno_t pam_eval_local_auth_policy(TALLOC_CTX *mem_ctx, - struct pam_ctx *pctx, - struct pam_data *pd, - struct pam_auth_req *preq, - bool *_sc_allow, - bool *_passkey_allow, - char **_local_policy) { - - TALLOC_CTX *tmp_ctx; - errno_t ret; - const char *domain_cdb; - char *local_policy = NULL; - bool sc_allow = false; - bool passkey_allow = false; - struct pam_resp_auth_type auth_types; - char **opts; - size_t c; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - return ENOMEM; - } - - /* Check local auth policy */ - domain_cdb = talloc_asprintf(tmp_ctx, CONFDB_DOMAIN_PATH_TMPL, preq->domain->name); - if (domain_cdb == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = confdb_get_string(pctx->rctx->cdb, tmp_ctx, domain_cdb, - CONFDB_DOMAIN_LOCAL_AUTH_POLICY, - "match", &local_policy); - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get the confdb local_auth_policy\n"); - return ret; - } - - /* "only" ignores online methods and allows all local ones */ - if (strcasecmp(local_policy, "only") == 0) { - sc_allow = true; - passkey_allow = true; - /* Match what the KDC supports and provides */ - } else if (strcasecmp(local_policy, "match") == 0) { - /* Don't overwrite the local auth type when offline */ - if (pd->pam_status == PAM_SUCCESS && pd->cmd == SSS_PAM_PREAUTH && - !is_domain_provider(preq->domain, "ldap")) { - ret = pam_get_auth_types(pd, &auth_types); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to get authentication types\n"); - goto done; - } - - if (auth_types.cert_auth) { - sc_allow = true; - } else if (auth_types.passkey_auth) { - passkey_allow = true; - } - - /* Store the local auth types, in case we go offline */ - if (!auth_types.backend_returned_no_auth_type) { - ret = set_local_auth_type(preq, sc_allow, passkey_allow); - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, - "Failed to evaluate local auth policy\n"); - goto done; - } - } - } - - /* Read the latest auth types */ - ret = pam_get_local_auth_policy(preq->domain, preq->pd->user, - &sc_allow, &passkey_allow); - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, - "Failed to get PAM local auth policy\n"); - goto done; - } - /* Check for enable */ - } else { - ret = split_on_separator(tmp_ctx, local_policy, ',', true, true, &opts, - NULL); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d], %s.\n", - ret, sss_strerror(ret)); - goto done; - } - - for (c = 0; opts[c] != NULL; c++) { - if (strcasestr(opts[c], "passkey") != NULL) { - passkey_allow = strstr(opts[c], "enable") ? true : false; - } else if (strcasestr(opts[c], "smartcard") != NULL) { - sc_allow = strstr(opts[c], "enable") ? true : false; - } else { - DEBUG(SSSDBG_MINOR_FAILURE, - "Unexpected local auth policy option [%s], " \ - "skipping.\n", opts[c]); - } - } - } - - /* if passkey is enabled but local Smartcard authentication is not but - * possible, the cert info data has to be remove as well if only local - * Smartcard authentication is possible. If Smartcard authentication - * is possible on the server side we have to keep it because the - * 'enable' option should only add local methods but not reject remote - * ones. */ - if (!sc_allow) { - evalute_sending_cert_info(pd); - } - - *_sc_allow = sc_allow; - *_passkey_allow = passkey_allow; - *_local_policy = talloc_steal(mem_ctx, local_policy); - - ret = EOK; - -done: - talloc_free(tmp_ctx); - return ret; -} - -static void pam_reply_delay(struct tevent_context *ev, struct tevent_timer *te, - struct timeval tv, void *pvt) -{ - struct pam_auth_req *preq; - - DEBUG(SSSDBG_CONF_SETTINGS, "pam_reply_delay get called.\n"); - - preq = talloc_get_type(pvt, struct pam_auth_req); - - pam_reply(preq); -} - -static errno_t get_password_for_cache_auth(struct sss_auth_token *authtok, - const char **password) -{ - int ret; - size_t pw_len; - const char *fa2; - size_t fa2_len; - - switch (sss_authtok_get_type(authtok)) { - case SSS_AUTHTOK_TYPE_PASSWORD: - ret = sss_authtok_get_password(authtok, password, NULL); - break; - case SSS_AUTHTOK_TYPE_2FA: - ret = sss_authtok_get_2fa(authtok, password, &pw_len, &fa2, &fa2_len); - break; - default: - DEBUG(SSSDBG_FATAL_FAILURE, "Unsupported auth token type [%d].\n", - sss_authtok_get_type(authtok)); - ret = EINVAL; - } - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get password.\n"); - return ret; - } - - return EOK; -} - -static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd); -static void pam_handle_cached_login(struct pam_auth_req *preq, int ret, - time_t expire_date, time_t delayed_until, bool cached_auth); - -/* - * Add a request to add a variable to the PAM user environment, containing the - * actual (not overridden) user shell, in case session recording is enabled. - */ -static int pam_reply_sr_export_shell(struct pam_auth_req *preq, - const char *var_name) -{ - int ret; - TALLOC_CTX *ctx = NULL; - bool enabled; - const char *enabled_str; - const char *shell; - char *buf; - - /* Create temporary talloc context */ - ctx = talloc_new(NULL); - if (ctx == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); - ret = ENOMEM; - goto done; - } - - /* Check if session recording is enabled */ - if (preq->cctx->rctx->sr_conf.scope == - SESSION_RECORDING_SCOPE_NONE) { - enabled = false; - } else { - enabled_str = ldb_msg_find_attr_as_string(preq->user_obj, - SYSDB_SESSION_RECORDING, NULL); - if (enabled_str == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "%s attribute not found\n", SYSDB_SESSION_RECORDING); - ret = ENOENT; - goto done; - } else if (strcmp(enabled_str, "TRUE") == 0) { - enabled = true; - } else if (strcmp(enabled_str, "FALSE") == 0) { - enabled = false; - } else { - DEBUG(SSSDBG_CRIT_FAILURE, "invalid value of %s attribute: %s\n", - SYSDB_SESSION_RECORDING, enabled_str); - ret = ENOENT; - goto done; - } - } - - /* Export original shell if recording is enabled and so it's overridden */ - if (enabled) { - /* Extract the shell */ - shell = sss_resp_get_shell_override(preq->user_obj, - preq->cctx->rctx, preq->domain); - if (shell == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "user has no shell\n"); - ret = ENOENT; - goto done; - } - - /* Format environment entry */ - buf = talloc_asprintf(ctx, "%s=%s", var_name, shell); - if (buf == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); - ret = ENOMEM; - goto done; - } - - /* Add request to add the entry to user environment */ - ret = pam_add_response(preq->pd, SSS_PAM_ENV_ITEM, - strlen(buf) + 1, (uint8_t *)buf); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); - goto done; - } - } - - ret = EOK; - -done: - talloc_free(ctx); - return ret; -} - -void pam_reply(struct pam_auth_req *preq) -{ - struct cli_ctx *cctx; - struct cli_protocol *prctx; - uint8_t *body; - size_t blen; - int ret; - int32_t resp_c; - int32_t resp_size; - struct response_data *resp; - int p; - struct timeval tv; - struct tevent_timer *te; - struct pam_data *pd; - char *local_policy = NULL; - struct pam_ctx *pctx; - uint32_t user_info_type; - time_t exp_date = -1; - time_t delay_until = -1; - char* pam_account_expired_message; - char* pam_account_locked_message; - int pam_verbosity; - bool local_sc_auth_allow = false; - bool local_passkey_auth_allow = false; -#ifdef BUILD_PASSKEY - bool pk_preauth_done = false; -#endif /* BUILD_PASSKEY */ - - pd = preq->pd; - cctx = preq->cctx; - pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); - prctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); - - ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_VERBOSITY, DEFAULT_PAM_VERBOSITY, - &pam_verbosity); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read PAM verbosity, not fatal.\n"); - pam_verbosity = DEFAULT_PAM_VERBOSITY; - } - - DEBUG(SSSDBG_TRACE_ALL, - "pam_reply initially called with result [%d]: %s. " - "this result might be changed during processing\n", - pd->pam_status, pam_strerror(NULL, pd->pam_status)); - - if (preq->domain != NULL && preq->domain->name != NULL) { - ret = pam_eval_local_auth_policy(cctx, pctx, pd, preq, - &local_sc_auth_allow, - &local_passkey_auth_allow, - &local_policy); - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, - "Failed to evaluate local auth policy\n"); - goto done; - } - } - - /* Ignore local_auth_policy for the files provider, allow local - * smartcard auth (default behavior prior to local_auth_policy) */ - if (is_domain_provider(preq->domain, "files")) { - local_sc_auth_allow = true; - /* For the ldap auth provider we currently only support - * password based authentication */ - } else if (is_domain_provider(preq->domain, "ldap") && local_policy != NULL - && strcasecmp(local_policy, "match") == 0) { - local_passkey_auth_allow = false; - local_sc_auth_allow = false; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Local auth policy allowed: smartcard [%s], passkey [%s]\n", - local_sc_auth_allow ? "True" : "False", - local_passkey_auth_allow ? "True" : "False"); - - if (pd->cmd == SSS_PAM_AUTHENTICATE - && !preq->cert_auth_local - && (pd->pam_status == PAM_AUTHINFO_UNAVAIL - || pd->pam_status == PAM_NO_MODULE_DATA - || pd->pam_status == PAM_BAD_ITEM) - && may_do_cert_auth(pctx, pd)) { - /* We have Smartcard credentials and the backend indicates that it is - * offline (PAM_AUTHINFO_UNAVAIL) or cannot handle the credentials - * (PAM_BAD_ITEM), so let's try authentication against the Smartcard - * PAM_NO_MODULE_DATA is returned by the krb5 backend if no - * authentication method was found at all, this might happen if the - * user has a Smartcard assigned but the pkint plugin is not available - * on the client. */ - DEBUG(SSSDBG_IMPORTANT_INFO, - "Backend cannot handle Smartcard authentication, " - "trying local Smartcard authentication.\n"); - if (local_sc_auth_allow) { - preq->cert_auth_local = true; - ret = check_cert(cctx, cctx->ev, pctx, preq, pd); - pam_check_user_done(preq, ret); - return; - } else { - DEBUG(SSSDBG_IMPORTANT_INFO, - "Local smartcard auth not allowed by local_auth_policy"); - } - } - - if (pd->pam_status == PAM_AUTHINFO_UNAVAIL || preq->use_cached_auth) { - - switch(pd->cmd) { - case SSS_PAM_AUTHENTICATE: - if ((preq->domain != NULL) && - (preq->domain->cache_credentials == true) && - (pd->offline_auth == false)) { - const char *password = NULL; - bool use_cached_auth; - - /* backup value of preq->use_cached_auth*/ - use_cached_auth = preq->use_cached_auth; - /* set to false to avoid entering this branch when pam_reply() - * is recursively called from pam_handle_cached_login() */ - preq->use_cached_auth = false; - - /* do auth with offline credentials */ - pd->offline_auth = true; - - if (preq->domain->sysdb == NULL) { - DEBUG(SSSDBG_FATAL_FAILURE, - "Fatal: Sysdb CTX not found for domain" - " [%s]!\n", preq->domain->name); - goto done; - } - - ret = get_password_for_cache_auth(pd->authtok, &password); - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, - "get_password_and_type_for_cache_auth failed.\n"); - goto done; - } - - ret = sysdb_cache_auth(preq->domain, - pd->user, password, - pctx->rctx->cdb, false, - &exp_date, &delay_until); - - pam_handle_cached_login(preq, ret, exp_date, delay_until, - use_cached_auth); - return; - } - break; - case SSS_PAM_CHAUTHTOK_PRELIM: - case SSS_PAM_CHAUTHTOK: - DEBUG(SSSDBG_FUNC_DATA, - "Password change not possible while offline.\n"); - pd->pam_status = PAM_AUTHTOK_ERR; - user_info_type = SSS_PAM_USER_INFO_OFFLINE_CHPASS; - ret = pam_add_response(pd, SSS_PAM_USER_INFO, sizeof(uint32_t), - (const uint8_t *) &user_info_type); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); - goto done; - } - break; -/* TODO: we need the pam session cookie here to make sure that cached - * authentication was successful */ - case SSS_PAM_PREAUTH: - case SSS_PAM_SETCRED: - case SSS_PAM_ACCT_MGMT: - case SSS_PAM_OPEN_SESSION: - case SSS_PAM_CLOSE_SESSION: - DEBUG(SSSDBG_OP_FAILURE, - "Assuming offline authentication setting status for " - "pam call %d to PAM_SUCCESS.\n", pd->cmd); - pd->pam_status = PAM_SUCCESS; - break; - default: - DEBUG(SSSDBG_CRIT_FAILURE, "Unknown PAM call [%d].\n", pd->cmd); - pd->pam_status = PAM_MODULE_UNKNOWN; - } - } - - if (pd->pam_status == PAM_SUCCESS && pd->cmd == SSS_PAM_CHAUTHTOK) { - ret = pam_null_last_online_auth_with_curr_token(preq->domain, - pd->user); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "sysdb_null_last_online_auth_with_curr_token failed: " - "%s [%d].\n", sss_strerror(ret), ret); - goto done; - } - } - - if (pd->response_delay > 0) { - ret = gettimeofday(&tv, NULL); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "gettimeofday failed [%d][%s].\n", - errno, strerror(errno)); - goto done; - } - tv.tv_sec += pd->response_delay; - tv.tv_usec = 0; - pd->response_delay = 0; - - te = tevent_add_timer(cctx->ev, cctx, tv, pam_reply_delay, preq); - if (te == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to add event pam_reply_delay.\n"); - goto done; - } - - return; - } - - /* If this was a successful login, save the lastLogin time */ - if (pd->cmd == SSS_PAM_AUTHENTICATE && - pd->pam_status == PAM_SUCCESS && - preq->domain && - preq->domain->cache_credentials && - !pd->offline_auth && - !pd->last_auth_saved) { - ret = set_last_login(preq); - if (ret != EOK) { - goto done; - } - return; - } - - ret = sss_packet_new(prctx->creq, 0, sss_packet_get_cmd(prctx->creq->in), - &prctx->creq->out); - if (ret != EOK) { - goto done; - } - -#ifdef BUILD_PASSKEY - if(pd->cmd == SSS_PAM_AUTHENTICATE && - pd->pam_status == PAM_NEW_AUTHTOK_REQD && - sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_REPLY) { - DEBUG(SSSDBG_TRACE_FUNC, "Passkey authentication reply, ignoring " - "new authtok required status\n"); - pd->pam_status = PAM_SUCCESS; - } - - /* Passkey auth user notification if no TGT is granted */ - if (pd->cmd == SSS_PAM_AUTHENTICATE && - pd->pam_status == PAM_SUCCESS && - preq->pd->passkey_local_done) { - user_info_type = SSS_PAM_USER_INFO_NO_KRB_TGT; - pam_add_response(pd, SSS_PAM_USER_INFO, - sizeof(uint32_t), (const uint8_t *) &user_info_type); - DEBUG(SSSDBG_IMPORTANT_INFO, - "User [%s] logged in with local passkey authentication, single " - "sign on ticket is not obtained.\n", pd->user); - } -#endif /* BUILD_PASSKEY */ - - /* Account expiration warning is printed for sshd. If pam_verbosity - * is equal or above PAM_VERBOSITY_INFO then all services are informed - * about account expiration. - */ - if (pd->pam_status == PAM_ACCT_EXPIRED && - ((pd->service != NULL && strcasecmp(pd->service, "sshd") == 0) || - pam_verbosity >= PAM_VERBOSITY_INFO)) { - - ret = confdb_get_string(pctx->rctx->cdb, pd, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_ACCOUNT_EXPIRED_MESSAGE, "", - &pam_account_expired_message); - if (ret != EOK) { - DEBUG(SSSDBG_MINOR_FAILURE, - "Failed to get expiration message: %d:[%s].\n", - ret, sss_strerror(ret)); - goto done; - } - - inform_user(pd, pam_account_expired_message); - } - - if (pd->account_locked) { - - ret = confdb_get_string(pctx->rctx->cdb, pd, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_ACCOUNT_LOCKED_MESSAGE, "", - &pam_account_locked_message); - if (ret != EOK) { - DEBUG(SSSDBG_MINOR_FAILURE, - "Failed to get expiration message: %d:[%s].\n", - ret, sss_strerror(ret)); - goto done; - } - - inform_user(pd, pam_account_locked_message); - } - - ret = filter_responses(pctx, pd->resp_list, pd); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "filter_responses failed, not fatal.\n"); - } - - if (pd->domain != NULL) { - ret = pam_add_response(pd, SSS_PAM_DOMAIN_NAME, strlen(pd->domain)+1, - (uint8_t *) pd->domain); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); - goto done; - } - } - - 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"); - } - -#ifdef BUILD_PASSKEY - ret = pam_eval_passkey_response(pctx, pd, preq, &pk_preauth_done); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to eval passkey response\n"); - goto done; - } - - if (may_do_passkey_auth(pctx, pd) - && !pk_preauth_done - && preq->passkey_data_exists - && local_passkey_auth_allow) { - ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); - pam_check_user_done(preq, ret); - return; - } -#endif /* BUILD_PASSKEY */ - } - - /* - * Export non-overridden shell to tlog-rec-session when opening the session - */ - if (pd->cmd == SSS_PAM_OPEN_SESSION && pd->pam_status == PAM_SUCCESS) { - ret = pam_reply_sr_export_shell(preq, "TLOG_REC_SESSION_SHELL"); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "failed to export the shell to tlog-rec-session.\n"); - goto done; - } - } - - resp_c = 0; - resp_size = 0; - resp = pd->resp_list; - while(resp != NULL) { - if (!resp->do_not_send_to_client) { - resp_c++; - resp_size += resp->len; - } - resp = resp->next; - } - - ret = sss_packet_grow(prctx->creq->out, sizeof(int32_t) + - sizeof(int32_t) + - resp_c * 2* sizeof(int32_t) + - resp_size); - if (ret != EOK) { - goto done; - } - - sss_packet_get_body(prctx->creq->out, &body, &blen); - DEBUG(SSSDBG_FUNC_DATA, "blen: %zu\n", blen); - p = 0; - - memcpy(&body[p], &pd->pam_status, sizeof(int32_t)); - p += sizeof(int32_t); - - memcpy(&body[p], &resp_c, sizeof(int32_t)); - p += sizeof(int32_t); - - resp = pd->resp_list; - while(resp != NULL) { - if (!resp->do_not_send_to_client) { - memcpy(&body[p], &resp->type, sizeof(int32_t)); - p += sizeof(int32_t); - memcpy(&body[p], &resp->len, sizeof(int32_t)); - p += sizeof(int32_t); - memcpy(&body[p], resp->data, resp->len); - p += resp->len; - } - - resp = resp->next; - } - -done: - DEBUG(SSSDBG_FUNC_DATA, "Returning [%d]: %s to the client\n", - pd->pam_status, pam_strerror(NULL, pd->pam_status)); - sss_cmd_done(cctx, preq); -} - -static void pam_dom_forwarder(struct pam_auth_req *preq); - -static void pam_handle_cached_login(struct pam_auth_req *preq, int ret, - time_t expire_date, time_t delayed_until, - bool use_cached_auth) -{ - uint32_t resp_type; - size_t resp_len; - uint8_t *resp; - int64_t dummy; - - preq->pd->pam_status = cached_login_pam_status(ret); - - switch (preq->pd->pam_status) { - case PAM_SUCCESS: - resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH; - resp_len = sizeof(uint32_t) + sizeof(int64_t); - resp = talloc_size(preq->pd, resp_len); - if (resp == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "talloc_size failed, cannot prepare user info.\n"); - } else { - memcpy(resp, &resp_type, sizeof(uint32_t)); - dummy = (int64_t) expire_date; - memcpy(resp+sizeof(uint32_t), &dummy, sizeof(int64_t)); - ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, - (const uint8_t *) resp); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); - } - } - break; - case PAM_PERM_DENIED: - if (delayed_until >= 0) { - resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED; - resp_len = sizeof(uint32_t) + sizeof(int64_t); - resp = talloc_size(preq->pd, resp_len); - if (resp == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "talloc_size failed, cannot prepare user info.\n"); - } else { - memcpy(resp, &resp_type, sizeof(uint32_t)); - dummy = (int64_t) delayed_until; - memcpy(resp+sizeof(uint32_t), &dummy, sizeof(int64_t)); - ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, - (const uint8_t *) resp); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "pam_add_response failed.\n"); - } - } - } - break; - case PAM_AUTH_ERR: - /* Was this attempt to authenticate from cache? */ - if (use_cached_auth) { - /* Don't try cached authentication again, try online check. */ - DEBUG(SSSDBG_FUNC_DATA, - "Cached authentication failed for: %s\n", - preq->pd->user); - preq->cached_auth_failed = true; - pam_dom_forwarder(preq); - return; - } - break; - default: - DEBUG(SSSDBG_TRACE_LIBS, - "cached login returned: %d\n", preq->pd->pam_status); - } - - pam_reply(preq); - return; -} - -static void pam_forwarder_cb(struct tevent_req *req); -static void pam_forwarder_cert_cb(struct tevent_req *req); -int pam_check_user_search(struct pam_auth_req *preq); - - -/* TODO: we should probably return some sort of cookie that is set in the - * PAM_ENVIRONMENT, so that we can save performing some calls and cache - * data. */ - -static errno_t pam_forwarder_parse_data(struct cli_ctx *cctx, struct pam_data *pd) -{ - struct cli_protocol *prctx; - uint8_t *body; - size_t blen; - errno_t ret; - uint32_t terminator; - const char *key_id; - - prctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); - - sss_packet_get_body(prctx->creq->in, &body, &blen); - if (blen >= sizeof(uint32_t)) { - SAFEALIGN_COPY_UINT32(&terminator, - body + blen - sizeof(uint32_t), - NULL); - if (terminator != SSS_END_OF_PAM_REQUEST) { - DEBUG(SSSDBG_CRIT_FAILURE, "Received data not terminated.\n"); - ret = EINVAL; - goto done; - } - } - - switch (prctx->cli_protocol_version->version) { - case 1: - ret = pam_parse_in_data(pd, body, blen); - break; - case 2: - ret = pam_parse_in_data_v2(pd, body, blen); - break; - case 3: - ret = pam_parse_in_data_v3(pd, body, blen); - break; - default: - DEBUG(SSSDBG_CRIT_FAILURE, "Illegal protocol version [%d].\n", - prctx->cli_protocol_version->version); - ret = EINVAL; - } - if (ret != EOK) { - goto done; - } - - if (pd->logon_name != NULL) { - ret = sss_parse_name_for_domains(pd, cctx->rctx->domains, - cctx->rctx->default_domain, - pd->logon_name, - &pd->domain, &pd->user); - } else { - /* SSS_PAM_PREAUTH request may have a missing name, e.g. if the - * name is determined with the help of a certificate. During - * SSS_PAM_AUTHENTICATE at least a key ID is needed to identify the - * selected certificate. */ - if (pd->cmd == SSS_PAM_AUTHENTICATE - && may_do_cert_auth(talloc_get_type(cctx->rctx->pvt_ctx, - struct pam_ctx), pd) - && (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_SC_PIN - || sss_authtok_get_type(pd->authtok) - == SSS_AUTHTOK_TYPE_SC_KEYPAD)) { - ret = sss_authtok_get_sc(pd->authtok, NULL, NULL, NULL, NULL, NULL, - NULL, &key_id, NULL, NULL, NULL); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_sc failed.\n"); - goto done; - } - - if (key_id == NULL || *key_id == '\0') { - DEBUG(SSSDBG_CRIT_FAILURE, - "Missing logon and Smartcard key ID during " - "authentication.\n"); - ret = ERR_NO_CREDS; - goto done; - } - - ret = EOK; - } else if (pd->cmd == SSS_PAM_PREAUTH - && may_do_cert_auth(talloc_get_type(cctx->rctx->pvt_ctx, - struct pam_ctx), pd)) { - ret = EOK; - } else { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing logon name in PAM request.\n"); - ret = ERR_NO_CREDS; - goto done; - } - } - - DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); - -done: - return ret; -} - -static bool is_uid_trusted(struct cli_creds *creds, - size_t trusted_uids_count, - uid_t *trusted_uids) -{ - errno_t ret; - - /* root is always trusted */ - if (client_euid(creds) == 0) { - return true; - } - - /* All uids are allowed */ - if (trusted_uids_count == 0) { - return true; - } - - ret = check_allowed_uids(client_euid(creds), trusted_uids_count, trusted_uids); - if (ret == EOK) return true; - - return false; -} - -static bool is_domain_public(char *name, - char **public_dom_names, - size_t public_dom_names_count) -{ - size_t i; - - for(i=0; i < public_dom_names_count; i++) { - if (strcasecmp(name, public_dom_names[i]) == 0) { - return true; - } - } - return false; -} - -static enum cache_req_dom_type -get_domain_request_type(struct pam_auth_req *preq, - struct pam_ctx *pctx) -{ - enum cache_req_dom_type req_dom_type; - - /* By default, only POSIX domains are to be contacted */ - req_dom_type = CACHE_REQ_POSIX_DOM; - - for (int i = 0; pctx->app_services[i]; i++) { - if (strcmp(pctx->app_services[i], preq->pd->service) == 0) { - req_dom_type = CACHE_REQ_APPLICATION_DOM; - break; - } - } - - return req_dom_type; -} - -static errno_t check_cert(TALLOC_CTX *mctx, - struct tevent_context *ev, - struct pam_ctx *pctx, - struct pam_auth_req *preq, - struct pam_data *pd) -{ - int p11_child_timeout; - int wait_for_card_timeout; - char *cert_verification_opts; - errno_t ret; - struct tevent_req *req; - char *uri = NULL; - - ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_P11_CHILD_TIMEOUT, - P11_CHILD_TIMEOUT_DEFAULT, - &p11_child_timeout); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read p11_child_timeout from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - return ret; - } - if ((pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) && pd->priv == 1) { - ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_WAIT_FOR_CARD_TIMEOUT, - P11_WAIT_FOR_CARD_TIMEOUT_DEFAULT, - &wait_for_card_timeout); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read [%s] from confdb: [%d]: %s\n", - CONFDB_PAM_WAIT_FOR_CARD_TIMEOUT, ret, sss_strerror(ret)); - return ret; - } - - p11_child_timeout += wait_for_card_timeout; - } - - ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_CERT_VERIFICATION, - NULL, &cert_verification_opts); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read '"CONFDB_PAM_CERT_VERIFICATION"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - return ret; - } - - if (cert_verification_opts == NULL) { - ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_MONITOR_CONF_ENTRY, - CONFDB_MONITOR_CERT_VERIFICATION, NULL, - &cert_verification_opts); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read '"CONFDB_MONITOR_CERT_VERIFICATION"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - return ret; - } - } - - ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_P11_URI, NULL, &uri); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read '"CONFDB_PAM_P11_URI"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - return ret; - } - - req = pam_check_cert_send(mctx, ev, - pctx->ca_db, p11_child_timeout, - cert_verification_opts, pctx->sss_certmap_ctx, - uri, pd); - if (req == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "pam_check_cert_send failed.\n"); - return ENOMEM; - } - - tevent_req_set_callback(req, pam_forwarder_cert_cb, preq); - return EAGAIN; -} - - -static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd) -{ - struct pam_auth_req *preq; - struct pam_data *pd; - int ret; - struct pam_ctx *pctx = - talloc_get_type(cctx->rctx->pvt_ctx, struct pam_ctx); - struct tevent_req *req; - - preq = talloc_zero(cctx, struct pam_auth_req); - if (!preq) { - return ENOMEM; - } - preq->cctx = cctx; - preq->cert_auth_local = false; - preq->client_id_num = cctx->client_id_num; - - preq->pd = create_pam_data(preq); - if (!preq->pd) { - talloc_free(preq); - return ENOMEM; - } - pd = preq->pd; - - preq->is_uid_trusted = is_uid_trusted(cctx->creds, - pctx->trusted_uids_count, - pctx->trusted_uids); - - if (!preq->is_uid_trusted) { - DEBUG(SSSDBG_MINOR_FAILURE, "uid %"SPRIuid" is not trusted.\n", - client_euid(cctx->creds)); - } - - - pd->cmd = pam_cmd; - pd->priv = cctx->priv; - pd->client_id_num = cctx->client_id_num; - - ret = pam_forwarder_parse_data(cctx, pd); - if (ret == EAGAIN) { - req = sss_dp_get_domains_send(cctx, cctx->rctx, true, pd->domain); - if (req == NULL) { - ret = ENOMEM; - } else { - tevent_req_set_callback(req, pam_forwarder_cb, preq); - ret = EAGAIN; - } - goto done; - } else if (ret != EOK) { - goto done; - } - - /* Determine what domain type to contact */ - preq->req_dom_type = get_domain_request_type(preq, pctx); - - if (pd->cmd == SSS_PAM_AUTHENTICATE - && (pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) - && !IS_SC_AUTHTOK(pd->authtok)) { - DEBUG(SSSDBG_MINOR_FAILURE, - "Smartcard authentication required but authentication " - "token [%d][%s] is not suitable.\n", - sss_authtok_get_type(pd->authtok), - sss_authtok_type_to_str(sss_authtok_get_type(pd->authtok))); - ret = ERR_NO_CREDS; - goto done; - } - - /* Try backend first for authentication before doing local Smartcard - * authentication if a logon name is available. Otherwise try to derive - * the logon name from the certificate first. */ - if ((pd->cmd != SSS_PAM_AUTHENTICATE - || (pd->cmd == SSS_PAM_AUTHENTICATE && pd->logon_name == NULL)) - && may_do_cert_auth(pctx, pd)) { - ret = check_cert(cctx, cctx->ev, pctx, preq, pd); - /* Finish here */ - goto done; - } - - /* This is set to false inside passkey_local() if no passkey data is found. - * It is checked in pam_reply() to avoid an endless loop */ - preq->passkey_data_exists = true; - -#ifdef BUILD_PASSKEY - if ((pd->cmd == SSS_PAM_AUTHENTICATE)) { - if (may_do_passkey_auth(pctx, pd)) { - if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { - ret = passkey_kerberos(pctx, preq->pd, preq); - goto done; - } else if ((sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY) || - (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_EMPTY)) { - ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); - goto done; - } - } - } -#endif /* BUILD_PASSKEY */ - - ret = pam_check_user_search(preq); - -done: - return pam_check_user_done(preq, ret); -} - -static errno_t pam_user_by_cert_step(struct pam_auth_req *preq); -static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req); -static void pam_forwarder_cert_cb(struct tevent_req *req) -{ - struct pam_auth_req *preq = tevent_req_callback_data(req, - struct pam_auth_req); - struct pam_data *pd; - errno_t ret = EOK; - const char *cert; - - ret = pam_check_cert_recv(req, preq, &preq->cert_list); - talloc_free(req); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "get_cert request failed.\n"); - goto done; - } - - pd = preq->pd; - - cert = sss_cai_get_cert(preq->cert_list); - - if (cert == NULL) { - if (pd->logon_name == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "No certificate found and no logon name given, " \ - "authentication not possible.\n"); - ret = ENOENT; - } else if (pd->cmd == SSS_PAM_PREAUTH - && (pd->cli_flags & PAM_CLI_FLAGS_TRY_CERT_AUTH)) { - DEBUG(SSSDBG_TRACE_ALL, - "try_cert_auth flag set but no certificate available, " - "request finished.\n"); - preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; - pam_reply(preq); - return; - } else { - if (pd->cmd == SSS_PAM_AUTHENTICATE) { - DEBUG(SSSDBG_CRIT_FAILURE, - "No certificate returned, authentication failed.\n"); - preq->pd->pam_status = PAM_AUTH_ERR; - pam_reply(preq); - return; - } else { - ret = pam_check_user_search(preq); - } - - } - goto done; - } - - preq->current_cert = preq->cert_list; - ret = pam_user_by_cert_step(preq); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n"); - goto done; - } - - return; - -done: - pam_check_user_done(preq, ret); -} - -static errno_t pam_user_by_cert_step(struct pam_auth_req *preq) -{ - struct cli_ctx *cctx = preq->cctx; - struct tevent_req *req; - struct pam_ctx *pctx = - talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); - - if (preq->current_cert == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate data.\n"); - return EINVAL; - } - - req = cache_req_user_by_cert_send(preq, cctx->ev, cctx->rctx, - pctx->rctx->ncache, 0, - preq->req_dom_type, NULL, - sss_cai_get_cert(preq->current_cert)); - if (req == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert_send failed.\n"); - return ENOMEM; - } - - tevent_req_set_callback(req, pam_forwarder_lookup_by_cert_done, preq); - return EOK; -} - -static errno_t get_results_from_all_domains(TALLOC_CTX *mem_ctx, - struct cache_req_result **results, - struct ldb_result **ldb_results) -{ - int ret; - size_t count = 0; - size_t c; - size_t d; - size_t r = 0; - struct ldb_result *res; - - for (d = 0; results != NULL && results[d] != NULL; d++) { - count += results[d]->count; - } - - res = talloc_zero(mem_ctx, struct ldb_result); - if (res == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); - return ENOMEM; - } - - if (count == 0) { - *ldb_results = res; - return EOK; - } - - res->msgs = talloc_zero_array(res, struct ldb_message *, count); - if (res->msgs == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); - return ENOMEM; - } - res->count = count; - - for (d = 0; results != NULL && results[d] != NULL; d++) { - for (c = 0; c < results[d]->count; c++) { - if (r >= count) { - DEBUG(SSSDBG_CRIT_FAILURE, - "More results found then counted before.\n"); - ret = EINVAL; - goto done; - } - res->msgs[r++] = talloc_steal(res->msgs, results[d]->msgs[c]); - } - } - - *ldb_results = res; - ret = EOK; - -done: - if (ret != EOK) { - talloc_free(res); - } - - return ret; -} - -/* Return true if hint is set for at least one domain */ -static bool get_user_name_hint(struct sss_domain_info *domains) -{ - struct sss_domain_info *d; - - DLIST_FOR_EACH(d, domains) { - if (d->user_name_hint == true) { - return true; - } - } - - return false; -} - -static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req) -{ - int ret; - struct cache_req_result **results; - struct pam_auth_req *preq = tevent_req_callback_data(req, - struct pam_auth_req); - const char *cert_user = NULL; - size_t cert_count = 0; - size_t cert_user_count = 0; - struct ldb_result *cert_user_objs; - - ret = cache_req_recv(preq, req, &results); - talloc_zfree(req); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert request failed.\n"); - goto done; - } - - if (ret == EOK) { - ret = get_results_from_all_domains(preq, results, - &cert_user_objs); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "get_results_from_all_domains failed.\n"); - goto done; - } - - sss_cai_set_cert_user_objs(preq->current_cert, cert_user_objs); - } - - preq->current_cert = sss_cai_get_next(preq->current_cert); - if (preq->current_cert != NULL) { - ret = pam_user_by_cert_step(preq); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n"); - goto done; - } - return; - } - - sss_cai_check_users(&preq->cert_list, &cert_count, &cert_user_count); - DEBUG(SSSDBG_TRACE_ALL, - "Found [%zu] certificates and [%zu] related users.\n", - cert_count, cert_user_count); - - if (cert_user_count == 0) { - if (preq->pd->logon_name == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Missing logon name and no certificate user found.\n"); - ret = ENOENT; - goto done; - } - } else { - - if (preq->pd->logon_name == NULL) { - if (preq->pd->cmd != SSS_PAM_PREAUTH - && preq->pd->cmd != SSS_PAM_AUTHENTICATE) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Missing logon name only allowed during (pre-)auth.\n"); - ret = ENOENT; - goto done; - } - /* Multiple certificates are only expected during pre-auth */ - if (cert_count > 1 && preq->pd->cmd == SSS_PAM_PREAUTH) { - for (preq->current_cert = preq->cert_list; - preq->current_cert != NULL; - preq->current_cert = sss_cai_get_next(preq->current_cert)) { - - ret = add_pam_cert_response(preq->pd, - preq->cctx->rctx->domains, "", - preq->current_cert, - get_user_name_hint(preq->cctx->rctx->domains) - ? SSS_PAM_CERT_INFO_WITH_HINT - : SSS_PAM_CERT_INFO); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "add_pam_cert_response failed.\n"); - preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; - } - } - - ret = EOK; - preq->pd->pam_status = PAM_SUCCESS; - pam_reply(preq); - goto done; - } - - if (cert_user_count == 1) { - cert_user_objs = sss_cai_get_cert_user_objs(preq->cert_list); - if (cert_user_objs == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate user.\n"); - ret = ENOENT; - goto done; - } - - cert_user = ldb_msg_find_attr_as_string( - cert_user_objs->msgs[0], - SYSDB_NAME, NULL); - if (cert_user == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Certificate user object has not name.\n"); - ret = ENOENT; - goto done; - } - - DEBUG(SSSDBG_FUNC_DATA, - "Found certificate user [%s].\n", cert_user); - - ret = sss_parse_name_for_domains(preq->pd, - preq->cctx->rctx->domains, - preq->cctx->rctx->default_domain, - cert_user, - &preq->pd->domain, - &preq->pd->user); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "sss_parse_name_for_domains failed.\n"); - goto done; - } - } - - if (get_user_name_hint(preq->cctx->rctx->domains) - && preq->pd->cmd == SSS_PAM_PREAUTH) { - ret = add_pam_cert_response(preq->pd, - preq->cctx->rctx->domains, cert_user, - preq->cert_list, - SSS_PAM_CERT_INFO_WITH_HINT); - preq->pd->pam_status = PAM_SUCCESS; - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); - preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; - } - ret = EOK; - pam_reply(preq); - goto done; - } - - /* Without user name hints the certificate must map to single user - * if no login name was given */ - if (cert_user == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "More than one user mapped to certificate.\n"); - ret = ERR_NO_CREDS; - goto done; - } - - /* If logon_name was not given during authentication add a - * SSS_PAM_CERT_INFO message to send the name to the caller. - * Additionally initial_cert_auth_successful is set to - * indicate that the user is already authenticated. */ - if (preq->pd->cmd == SSS_PAM_AUTHENTICATE - && preq->pd->logon_name == NULL) { - ret = add_pam_cert_response(preq->pd, - preq->cctx->rctx->domains, cert_user, - preq->cert_list, - SSS_PAM_CERT_INFO); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); - preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; - goto done; - } - - preq->initial_cert_auth_successful = true; - } - - /* cert_user will be returned to the PAM client as user name, so - * we can use it here already e.g. to set in initgroups timeout */ - preq->pd->logon_name = talloc_strdup(preq->pd, cert_user); - if (preq->pd->logon_name == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); - ret = ENOMEM; - goto done; - } - } - } - - if (preq->user_obj == NULL) { - ret = pam_check_user_search(preq); - } else { - ret = EOK; - } - - if (ret == EOK) { - pam_dom_forwarder(preq); - } - -done: - pam_check_user_done(preq, ret); -} - -static void pam_forwarder_cb(struct tevent_req *req) -{ - struct pam_auth_req *preq = tevent_req_callback_data(req, - struct pam_auth_req); - struct cli_ctx *cctx = preq->cctx; - struct pam_data *pd; - errno_t ret = EOK; - struct pam_ctx *pctx = - talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); - - ret = sss_dp_get_domains_recv(req); - talloc_free(req); - if (ret != EOK) { - goto done; - } - - ret = p11_refresh_certmap_ctx(pctx, pctx->rctx->domains); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "p11_refresh_certmap_ctx failed, " - "certificate matching might not work as expected"); - } - - pd = preq->pd; - - ret = pam_forwarder_parse_data(cctx, pd); - if (ret == EAGAIN) { - DEBUG(SSSDBG_TRACE_FUNC, "Assuming %s is a UPN\n", pd->logon_name); - /* If not, cache_req will error out later */ - pd->user = talloc_strdup(pd, pd->logon_name); - if (pd->user == NULL) { - ret = ENOMEM; - goto done; - } - pd->domain = NULL; - } else if (ret != EOK) { - ret = EINVAL; - goto done; - } - - /* try backend first for authentication before doing local Smartcard - * authentication */ - if (pd->cmd != SSS_PAM_AUTHENTICATE && may_do_cert_auth(pctx, pd)) { - ret = check_cert(cctx, cctx->ev, pctx, preq, pd); - /* Finish here */ - goto done; - } - -#ifdef BUILD_PASSKEY - /* This is set to false inside passkey_local() if no passkey data is found. - * It is checked in pam_reply() to avoid an endless loop */ - preq->passkey_data_exists = true; - - if ((pd->cmd == SSS_PAM_AUTHENTICATE)) { - if (may_do_passkey_auth(pctx, pd)) { - if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { - ret = passkey_kerberos(pctx, preq->pd, preq); - goto done; - } else if ((sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY) || - (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_EMPTY)) { - ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); - goto done; - } - } - } -#endif /* BUILD_PASSKEY */ - - ret = pam_check_user_search(preq); - -done: - pam_check_user_done(preq, ret); -} - -static void pam_check_user_search_next(struct tevent_req *req); -static void pam_check_user_search_lookup(struct tevent_req *req); -static void pam_check_user_search_done(struct pam_auth_req *preq, int ret, - struct cache_req_result *result); - -/* lookup the user uid from the cache first, - * then we'll refresh initgroups if needed */ -int pam_check_user_search(struct pam_auth_req *preq) -{ - struct tevent_req *dpreq; - struct cache_req_data *data; - - data = cache_req_data_name(preq, - CACHE_REQ_INITGROUPS, - preq->pd->logon_name); - if (data == NULL) { - return ENOMEM; - } - - cache_req_data_set_bypass_cache(data, false); - cache_req_data_set_bypass_dp(data, true); - cache_req_data_set_requested_domains(data, preq->pd->requested_domains); - - dpreq = cache_req_send(preq, - preq->cctx->rctx->ev, - preq->cctx->rctx, - preq->cctx->rctx->ncache, - 0, - preq->req_dom_type, - NULL, - data); - if (!dpreq) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Out of memory sending data provider request\n"); - return ENOMEM; - } - - tevent_req_set_callback(dpreq, pam_check_user_search_next, preq); - - /* tell caller we are in an async call */ - return EAGAIN; -} - -static void pam_check_user_search_next(struct tevent_req *req) -{ - struct pam_auth_req *preq; - struct pam_ctx *pctx; - struct cache_req_result *result = NULL; - struct cache_req_data *data; - struct tevent_req *dpreq; - int ret; - - preq = tevent_req_callback_data(req, struct pam_auth_req); - pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); - - ret = cache_req_single_domain_recv(preq, req, &result); - talloc_zfree(req); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_OP_FAILURE, "Cache lookup failed, trying to get fresh " - "data from the backend.\n"); - } - - DEBUG(SSSDBG_TRACE_ALL, "PAM initgroups scheme [%s].\n", - pam_initgroup_enum_to_string(pctx->initgroups_scheme)); - - if (ret == EOK) { - bool user_has_session = false; - - if (pctx->initgroups_scheme == PAM_INITGR_NO_SESSION) { - uid_t uid = ldb_msg_find_attr_as_uint64(result->msgs[0], - SYSDB_UIDNUM, 0); - if (!uid) { - DEBUG(SSSDBG_CRIT_FAILURE, "A user with no UID?\n"); - talloc_zfree(preq->cctx); - return; - } - - /* If a user already has a session on the system, we take the - * cache for granted and do not force an online lookup. This is - * because in most cases the user is just trying to authenticate - * but not create a new session (sudo, lockscreen, polkit, etc.) - * An online refresh in this situation would just delay operations - * without providing any useful additional information. - */ - (void)check_if_uid_is_active(uid, &user_has_session); - - DEBUG(SSSDBG_TRACE_ALL, "Found %s session for uid %"SPRIuid".\n", - user_has_session ? "a" : "no", uid); - } - - /* The initgr cache is used to make sure that during a single PAM - * session (auth, acct_mgtm, ....) the backend is contacted only - * once. logon_name is the name provided by the PAM client and - * will not be modified during the request, so it makes sense to - * use it here instead od the pd->user. - */ - ret = pam_initgr_check_timeout(pctx->id_table, preq->pd->logon_name); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_OP_FAILURE, "Could not look up initgroup timeout\n"); - } - - if ((ret == EOK) || user_has_session - || pctx->initgroups_scheme == PAM_INITGR_NEVER) { - DEBUG(SSSDBG_TRACE_ALL, "No new initgroups needed because:\n"); - if (ret == EOK) { - DEBUG(SSSDBG_TRACE_ALL, "PAM initgr cache still valid.\n"); - } else if (user_has_session) { - DEBUG(SSSDBG_TRACE_ALL, "there is a active session for " - "user [%s].\n", preq->pd->logon_name); - } else if (pctx->initgroups_scheme == PAM_INITGR_NEVER) { - DEBUG(SSSDBG_TRACE_ALL, "initgroups scheme is 'never'.\n"); - } - pam_check_user_search_done(preq, EOK, result); - return; - } - } - - /* If we get here it means the user was not found or does not have a - * session, or initgr has not been cached before, so we force a new - * online lookup */ - data = cache_req_data_name(preq, - CACHE_REQ_INITGROUPS, - preq->pd->logon_name); - if (data == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory\n"); - talloc_zfree(preq->cctx); - return; - } - cache_req_data_set_bypass_cache(data, true); - cache_req_data_set_bypass_dp(data, false); - cache_req_data_set_requested_domains(data, preq->pd->requested_domains); - - dpreq = cache_req_send(preq, - preq->cctx->rctx->ev, - preq->cctx->rctx, - preq->cctx->rctx->ncache, - 0, - preq->req_dom_type, - NULL, - data); - if (!dpreq) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Out of memory sending data provider request\n"); - talloc_zfree(preq->cctx); - return; - } - - tevent_req_set_callback(dpreq, pam_check_user_search_lookup, preq); -} - -static void pam_check_user_search_lookup(struct tevent_req *req) -{ - struct cache_req_result *result; - struct pam_auth_req *preq; - int ret; - - preq = tevent_req_callback_data(req, struct pam_auth_req); - - ret = cache_req_single_domain_recv(preq, req, &result); - talloc_zfree(req); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Fatal error, killing connection!\n"); - talloc_zfree(preq->cctx); - return; - } - - pam_check_user_search_done(preq, ret, result); -} - -static void pam_check_user_search_done(struct pam_auth_req *preq, int ret, - struct cache_req_result *result) -{ - struct pam_ctx *pctx; - - pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); - - if (ret == EOK) { - preq->user_obj = result->msgs[0]; - pd_set_primary_name(preq->user_obj, preq->pd); - preq->domain = result->domain; - - ret = pam_initgr_cache_set(pctx->rctx->ev, - pctx->id_table, - preq->pd->logon_name, - pctx->id_timeout); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Could not save initgr timestamp." - "Proceeding with PAM actions\n"); - } - - pam_dom_forwarder(preq); - } - - ret = pam_check_user_done(preq, ret); - if (ret != EOK) { - preq->pd->pam_status = PAM_SYSTEM_ERR; - pam_reply(preq); - } -} - -int pam_check_user_done(struct pam_auth_req *preq, int ret) -{ - switch (ret) { - case EOK: - break; - - case EAGAIN: - /* performing async request, just return */ - break; - - case ENOENT: - preq->pd->pam_status = PAM_USER_UNKNOWN; - pam_reply(preq); - break; - - case ERR_P11_PIN_LOCKED: - preq->pd->pam_status = PAM_AUTH_ERR; - pam_reply(preq); - break; - - case ERR_NO_CREDS: - preq->pd->pam_status = PAM_CRED_INSUFFICIENT; - pam_reply(preq); - break; - - default: - preq->pd->pam_status = PAM_SYSTEM_ERR; - pam_reply(preq); - break; - } - - return EOK; -} - -static errno_t pam_is_last_online_login_fresh(struct sss_domain_info *domain, - const char* user, - int cached_auth_timeout, - bool *_result) -{ - errno_t ret; - bool result = true; - uint64_t last_login; - - ret = pam_get_last_online_auth_with_curr_token(domain, user, &last_login); - if (ret != EOK) { - DEBUG(SSSDBG_MINOR_FAILURE, - "sysdb_get_last_online_auth_with_curr_token failed: %s:[%d]\n", - sss_strerror(ret), ret); - goto done; - } - - result = time(NULL) < (last_login + cached_auth_timeout); - ret = EOK; - -done: - if (ret == EOK) { - *_result = result; - } - return ret; -} - -static bool pam_is_authtok_cachable(struct sss_auth_token *authtok) -{ - enum sss_authtok_type type; - bool cachable = false; - - type = sss_authtok_get_type(authtok); - if (type == SSS_AUTHTOK_TYPE_PASSWORD) { - cachable = true; - } else { - DEBUG(SSSDBG_TRACE_LIBS, "Authentication token can't be cached\n"); - } - - return cachable; -} - -static bool pam_can_user_cache_auth(struct sss_domain_info *domain, - int pam_cmd, - struct sss_auth_token *authtok, - const char* user, - bool cached_auth_failed) -{ - errno_t ret; - bool result = false; - - if (cached_auth_failed) { - /* Do not retry indefinitely */ - return false; - } - - if (!domain->cache_credentials || domain->cached_auth_timeout <= 0) { - return false; - } - - if (pam_cmd == SSS_PAM_PREAUTH - || (pam_cmd == SSS_PAM_AUTHENTICATE - && pam_is_authtok_cachable(authtok))) { - - ret = pam_is_last_online_login_fresh(domain, user, - domain->cached_auth_timeout, - &result); - if (ret != EOK) { - /* non-critical, consider fail as 'non-fresh value' */ - DEBUG(SSSDBG_MINOR_FAILURE, - "pam_is_last_online_login_fresh failed: %s:[%d]\n", - sss_strerror(ret), ret); - } - } - - return result; -} - -static void pam_dom_forwarder(struct pam_auth_req *preq) -{ - TALLOC_CTX *tmp_ctx = NULL; - int ret; - struct pam_ctx *pctx = - talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); - const char *cert_user; - struct ldb_result *cert_user_objs; - bool sc_auth; - bool passkey_auth; - size_t c; - char *local_policy = NULL; - bool found = false; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - return; - } - - if (!preq->pd->domain) { - preq->pd->domain = preq->domain->name; - } - - /* Untrusted users can access only public domains. */ - if (!preq->is_uid_trusted && - !is_domain_public(preq->pd->domain, pctx->public_domains, - pctx->public_domains_count)) { - DEBUG(SSSDBG_MINOR_FAILURE, - "Untrusted user %"SPRIuid" cannot access non-public domain %s.\n", - client_euid(preq->cctx->creds), preq->pd->domain); - preq->pd->pam_status = PAM_PERM_DENIED; - pam_reply(preq); - return; - } - - /* skip this domain if not requested and the user is trusted - * as untrusted users can't request a domain */ - if (preq->is_uid_trusted && - !is_domain_requested(preq->pd, preq->pd->domain)) { - preq->pd->pam_status = PAM_USER_UNKNOWN; - pam_reply(preq); - return; - } - - if (pam_can_user_cache_auth(preq->domain, - preq->pd->cmd, - preq->pd->authtok, - preq->pd->user, - preq->cached_auth_failed)) { - preq->use_cached_auth = true; - pam_reply(preq); - return; - } - - /* Skip online auth when local auth policy = only */ -#ifdef BUILD_PASSKEY - if (may_do_cert_auth(pctx, preq->pd) || may_do_passkey_auth(pctx, preq->pd)) { -#else - if (may_do_cert_auth(pctx, preq->pd)) { -#endif /* BUILD_PASSKEY */ - if (preq->domain->name != NULL) { - ret = pam_eval_local_auth_policy(preq->cctx, pctx, preq->pd, preq, - &sc_auth, - &passkey_auth, - &local_policy); - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, - "Failed to evaluate local auth policy\n"); - preq->pd->pam_status = PAM_AUTH_ERR; - pam_reply(preq); - return; - } - } - } - - if (may_do_cert_auth(pctx, preq->pd) && preq->cert_list != NULL) { - /* Check if user matches certificate user */ - found = false; - for (preq->current_cert = preq->cert_list; - preq->current_cert != NULL; - preq->current_cert = sss_cai_get_next(preq->current_cert)) { - - cert_user_objs = sss_cai_get_cert_user_objs(preq->current_cert); - if (cert_user_objs == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "Unexpected missing certificate user, " - "trying next certificate.\n"); - continue; - } - - for (c = 0; c < cert_user_objs->count; c++) { - cert_user = ldb_msg_find_attr_as_string(cert_user_objs->msgs[c], - SYSDB_NAME, NULL); - if (cert_user == NULL) { - /* Even if there might be other users mapped to the - * certificate a missing SYSDB_NAME indicates some critical - * condition which justifies that the whole request is aborted - * */ - DEBUG(SSSDBG_CRIT_FAILURE, - "Certificate user object has no name.\n"); - preq->pd->pam_status = PAM_USER_UNKNOWN; - pam_reply(preq); - return; - } - - if (ldb_dn_compare(cert_user_objs->msgs[c]->dn, - preq->user_obj->dn) == 0) { - found = true; - if (preq->pd->cmd == SSS_PAM_PREAUTH) { - ret = sss_authtok_set_sc(preq->pd->authtok, - SSS_AUTHTOK_TYPE_SC_PIN, NULL, 0, - sss_cai_get_token_name(preq->current_cert), 0, - sss_cai_get_module_name(preq->current_cert), 0, - sss_cai_get_key_id(preq->current_cert), 0, - sss_cai_get_label(preq->current_cert), 0); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "sss_authtok_set_sc failed, Smartcard " - "authentication detection might fail in " - "the backend.\n"); - } - - ret = add_pam_cert_response(preq->pd, - preq->cctx->rctx->domains, - cert_user, - preq->current_cert, - SSS_PAM_CERT_INFO); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); - preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; - } - } - - } - } - } - - if (found) { - if (local_policy != NULL && strcasecmp(local_policy, "only") == 0) { - talloc_free(tmp_ctx); - DEBUG(SSSDBG_IMPORTANT_INFO, - "Local auth only set and matching certificate was found, " - "skipping online auth\n"); - if (preq->pd->cmd == SSS_PAM_PREAUTH) { - preq->pd->pam_status = PAM_SUCCESS; - } else if (preq->pd->cmd == SSS_PAM_AUTHENTICATE - && IS_SC_AUTHTOK(preq->pd->authtok) - && (preq->cert_auth_local - || preq->initial_cert_auth_successful)) { - preq->pd->pam_status = PAM_SUCCESS; - preq->callback = pam_reply; - } - - pam_reply(preq); - return; - } - - /* We are done if we do not have to call the backend */ - if (preq->pd->cmd == SSS_PAM_AUTHENTICATE - && preq->cert_auth_local) { - preq->pd->pam_status = PAM_SUCCESS; - preq->callback = pam_reply; - pam_reply(preq); - return; - } - } else { - if (preq->pd->cmd == SSS_PAM_PREAUTH) { - DEBUG(SSSDBG_TRACE_FUNC, - "User and certificate user do not match, " - "continue with other authentication methods.\n"); - } else { - DEBUG(SSSDBG_CRIT_FAILURE, - "User and certificate user do not match.\n"); - preq->pd->pam_status = PAM_AUTH_ERR; - pam_reply(preq); - return; - } - } - } - - if (local_policy != NULL && strcasecmp(local_policy, "only") == 0) { - talloc_free(tmp_ctx); - DEBUG(SSSDBG_IMPORTANT_INFO, "Local auth only set, skipping online auth\n"); - if (preq->pd->cmd == SSS_PAM_PREAUTH) { - preq->pd->pam_status = PAM_SUCCESS; - } else if (preq->pd->cmd == SSS_PAM_AUTHENTICATE && IS_SC_AUTHTOK(preq->pd->authtok)) { - /* Trigger offline smartcardcard autheitcation */ - preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; - } - - pam_reply(preq); - return; - } - - preq->callback = pam_reply; - ret = pam_dp_send_req(preq); - DEBUG(SSSDBG_CONF_SETTINGS, "pam_dp_send_req returned %d\n", ret); - - talloc_free(tmp_ctx); - - if (ret != EOK) { - preq->pd->pam_status = PAM_SYSTEM_ERR; - pam_reply(preq); - } -} - -static int pam_cmd_authenticate(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_authenticate\n"); - return pam_forwarder(cctx, SSS_PAM_AUTHENTICATE); -} - -static int pam_cmd_setcred(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_setcred\n"); - return pam_forwarder(cctx, SSS_PAM_SETCRED); -} - -static int pam_cmd_acct_mgmt(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_acct_mgmt\n"); - return pam_forwarder(cctx, SSS_PAM_ACCT_MGMT); -} - -static int pam_cmd_open_session(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_open_session\n"); - return pam_forwarder(cctx, SSS_PAM_OPEN_SESSION); -} - -static int pam_cmd_close_session(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_close_session\n"); - return pam_forwarder(cctx, SSS_PAM_CLOSE_SESSION); -} - -static int pam_cmd_chauthtok(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_chauthtok\n"); - return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK); -} - -static int pam_cmd_chauthtok_prelim(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_chauthtok_prelim\n"); - return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK_PRELIM); -} - -static int pam_cmd_preauth(struct cli_ctx *cctx) -{ - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_preauth\n"); - return pam_forwarder(cctx, SSS_PAM_PREAUTH); -} - -struct cli_protocol_version *register_cli_protocol_version(void) -{ - static struct cli_protocol_version pam_cli_protocol_version[] = { - {3, "2009-09-14", "make cli_pid mandatory"}, - {2, "2009-05-12", "new format "}, - {1, "2008-09-05", "initial version, \\0 terminated strings"}, - {0, NULL, NULL} - }; - - return pam_cli_protocol_version; -} - -struct sss_cmd_table *get_pam_cmds(void) -{ - static struct sss_cmd_table sss_cmds[] = { - {SSS_GET_VERSION, sss_cmd_get_version}, - {SSS_PAM_AUTHENTICATE, pam_cmd_authenticate}, - {SSS_PAM_SETCRED, pam_cmd_setcred}, - {SSS_PAM_ACCT_MGMT, pam_cmd_acct_mgmt}, - {SSS_PAM_OPEN_SESSION, pam_cmd_open_session}, - {SSS_PAM_CLOSE_SESSION, pam_cmd_close_session}, - {SSS_PAM_CHAUTHTOK, pam_cmd_chauthtok}, - {SSS_PAM_CHAUTHTOK_PRELIM, pam_cmd_chauthtok_prelim}, - {SSS_PAM_PREAUTH, pam_cmd_preauth}, - {SSS_GSSAPI_INIT, pam_cmd_gssapi_init}, - {SSS_GSSAPI_SEC_CTX, pam_cmd_gssapi_sec_ctx}, - {SSS_CLI_NULL, NULL} - }; - - return sss_cmds; -} - -errno_t -pam_set_last_online_auth_with_curr_token(struct sss_domain_info *domain, - const char *username, - uint64_t value) -{ - TALLOC_CTX *tmp_ctx; - struct sysdb_attrs *attrs; - int ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - attrs = sysdb_new_attrs(tmp_ctx); - if (attrs == NULL) { - ret = ENOMEM; - goto done; - } - - ret = sysdb_attrs_add_time_t(attrs, - SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, - value); - if (ret != EOK) { goto done; } - - ret = sysdb_set_user_attr(domain, username, attrs, SYSDB_MOD_REP); - if (ret != EOK) { goto done; } - -done: - if (ret != EOK) { - DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, sss_strerror(ret)); - } - - talloc_zfree(tmp_ctx); - return ret; -} - -static errno_t -pam_null_last_online_auth_with_curr_token(struct sss_domain_info *domain, - const char *username) -{ - return pam_set_last_online_auth_with_curr_token(domain, username, 0); -} - -static errno_t -pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain, - const char *name, - uint64_t *_value) -{ - TALLOC_CTX *tmp_ctx = NULL; - const char *attrs[] = { SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, NULL }; - struct ldb_message *ldb_msg; - uint64_t value = 0; - errno_t ret; - - if (name == NULL || *name == '\0') { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); - ret = EINVAL; - goto done; - } - - if (domain->sysdb == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing sysdb db context.\n"); - ret = EINVAL; - goto done; - } - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - ret = sysdb_search_user_by_name(tmp_ctx, domain, name, attrs, &ldb_msg); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "sysdb_search_user_by_name failed [%d][%s].\n", - ret, strerror(ret)); - goto done; - } - - /* Check offline_auth_cache_timeout */ - value = ldb_msg_find_attr_as_uint64(ldb_msg, - SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, - 0); - ret = EOK; - -done: - if (ret == EOK) { - *_value = value; - } - - talloc_free(tmp_ctx); - return ret; -} +/* + SSSD + + PAM Responder + + Copyright (C) Simo Sorce 2009 + Copyright (C) Sumit Bose 2009 + + 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 . +*/ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include + +#include "util/util.h" +#include "util/auth_utils.h" +#include "util/find_uid.h" +#include "util/sss_ptr_hash.h" +#include "db/sysdb.h" + +#include "confdb/confdb.h" +#include "responder/common/responder_packet.h" +#include "responder/common/responder.h" +#include "responder/common/negcache.h" +#include "providers/data_provider.h" +#include "responder/pam/pamsrv.h" +#include "responder/pam/pamsrv_passkey.h" +#include "responder/pam/pam_helpers.h" +#include "responder/common/cache_req/cache_req.h" + +enum pam_verbosity { + PAM_VERBOSITY_NO_MESSAGES = 0, + PAM_VERBOSITY_IMPORTANT, + PAM_VERBOSITY_INFO, + PAM_VERBOSITY_DEBUG +}; + +#define DEFAULT_PAM_VERBOSITY PAM_VERBOSITY_IMPORTANT + +struct pam_initgroup_enum_str { + enum pam_initgroups_scheme scheme; + const char *option; +}; + +struct pam_initgroup_enum_str pam_initgroup_enum_str[] = { + { PAM_INITGR_NEVER, "never" }, + { PAM_INITGR_NO_SESSION, "no_session" }, + { PAM_INITGR_ALWAYS, "always" }, + { PAM_INITGR_INVALID, NULL } +}; + +enum pam_initgroups_scheme pam_initgroups_string_to_enum(const char *str) +{ + size_t c; + + for (c = 0 ; pam_initgroup_enum_str[c].option != NULL; c++) { + if (strcasecmp(pam_initgroup_enum_str[c].option, str) == 0) { + return pam_initgroup_enum_str[c].scheme; + } + } + + return PAM_INITGR_INVALID; +} + +const char *pam_initgroup_enum_to_string(enum pam_initgroups_scheme scheme) +{ + size_t c; + + for (c = 0 ; pam_initgroup_enum_str[c].option != NULL; c++) { + if (pam_initgroup_enum_str[c].scheme == scheme) { + return pam_initgroup_enum_str[c].option; + } + } + + return "(NULL)"; +} + +static errno_t +pam_null_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *username); +static errno_t +pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *name, + uint64_t *_value); + +void pam_reply(struct pam_auth_req *preq); + +static errno_t check_cert(TALLOC_CTX *mctx, + struct tevent_context *ev, + struct pam_ctx *pctx, + struct pam_auth_req *preq, + struct pam_data *pd); + +int pam_check_user_done(struct pam_auth_req *preq, int ret); + +static errno_t pack_user_info_msg(TALLOC_CTX *mem_ctx, + const char *user_error_message, + size_t *resp_len, + uint8_t **_resp) +{ + uint32_t resp_type = SSS_PAM_USER_INFO_ACCOUNT_EXPIRED; + size_t err_len; + uint8_t *resp; + size_t p; + + err_len = strlen(user_error_message); + *resp_len = 2 * sizeof(uint32_t) + err_len; + resp = talloc_size(mem_ctx, *resp_len); + if (resp == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + p = 0; + SAFEALIGN_SET_UINT32(&resp[p], resp_type, &p); + SAFEALIGN_SET_UINT32(&resp[p], err_len, &p); + safealign_memcpy(&resp[p], user_error_message, err_len, &p); + if (p != *resp_len) { + DEBUG(SSSDBG_FATAL_FAILURE, "Size mismatch\n"); + } + + *_resp = resp; + return EOK; +} + +static void inform_user(struct pam_data* pd, const char *pam_message) +{ + size_t msg_len; + uint8_t *msg; + errno_t ret; + + ret = pack_user_info_msg(pd, pam_message, &msg_len, &msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "pack_user_info_msg failed.\n"); + } else { + ret = pam_add_response(pd, SSS_PAM_USER_INFO, msg_len, msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + } +} + +static bool is_domain_requested(struct pam_data *pd, const char *domain_name) +{ + int i; + + /* If none specific domains got requested via pam, all domains are allowed. + * Which mimics the default/original behaviour. + */ + if (!pd->requested_domains) { + return true; + } + + for (i = 0; pd->requested_domains[i]; i++) { + if (strcasecmp(domain_name, pd->requested_domains[i])) { + continue; + } + + return true; + } + + return false; +} + +static int extract_authtok_v2(struct sss_auth_token *tok, + size_t data_size, uint8_t *body, size_t blen, + size_t *c) +{ + uint32_t auth_token_type; + uint32_t auth_token_length; + uint8_t *auth_token_data; + int ret = EOK; + + if (data_size < sizeof(uint32_t) || *c+data_size > blen || + SIZE_T_OVERFLOW(*c, data_size)) return EINVAL; + + SAFEALIGN_COPY_UINT32_CHECK(&auth_token_type, &body[*c], blen, c); + auth_token_length = data_size - sizeof(uint32_t); + auth_token_data = body+(*c); + + switch (auth_token_type) { + case SSS_AUTHTOK_TYPE_EMPTY: + sss_authtok_set_empty(tok); + break; + case SSS_AUTHTOK_TYPE_PASSWORD: + if (auth_token_length == 0) { + sss_authtok_set_empty(tok); + } else { + ret = sss_authtok_set_password(tok, (const char *)auth_token_data, + auth_token_length); + } + break; + case SSS_AUTHTOK_TYPE_2FA: + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + case SSS_AUTHTOK_TYPE_SC_PIN: + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_OAUTH2: + case SSS_AUTHTOK_TYPE_PASSKEY: + case SSS_AUTHTOK_TYPE_PASSKEY_KRB: + case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: + case SSS_AUTHTOK_TYPE_PAM_STACKED: + ret = sss_authtok_set(tok, auth_token_type, + auth_token_data, auth_token_length); + break; + default: + return EINVAL; + } + + *c += auth_token_length; + + return ret; +} + +static int extract_string(char **var, size_t size, uint8_t *body, size_t blen, + size_t *c) { + uint8_t *str; + + if (*c+size > blen || SIZE_T_OVERFLOW(*c, size)) return EINVAL; + + str = body+(*c); + + if (str[size-1]!='\0') return EINVAL; + + /* If the string isn't valid UTF-8, fail */ + if (!sss_utf8_check(str, size-1)) { + return EINVAL; + } + + *c += size; + + *var = (char *) str; + + return EOK; +} + +static int extract_uint32_t(uint32_t *var, size_t size, uint8_t *body, + size_t blen, size_t *c) { + + if (size != sizeof(uint32_t) || *c+size > blen || SIZE_T_OVERFLOW(*c, size)) + return EINVAL; + + SAFEALIGN_COPY_UINT32_CHECK(var, &body[*c], blen, c); + + return EOK; +} + +static int pd_set_primary_name(const struct ldb_message *msg,struct pam_data *pd) +{ + const char *name; + + name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + if (!name) { + DEBUG(SSSDBG_CRIT_FAILURE, "A user with no name?\n"); + return EIO; + } + + if (strcmp(pd->user, name)) { + DEBUG(SSSDBG_TRACE_FUNC, "User's primary name is %s\n", name); + talloc_free(pd->user); + pd->user = talloc_strdup(pd, name); + if (!pd->user) return ENOMEM; + } + + return EOK; +} + +static int pam_parse_in_data_v2(struct pam_data *pd, + uint8_t *body, size_t blen) +{ + size_t c; + uint32_t type; + uint32_t size; + int ret; + uint32_t start; + uint32_t terminator; + char *requested_domains; + + if (blen < 4*sizeof(uint32_t)+2) { + DEBUG(SSSDBG_CRIT_FAILURE, "Received data is invalid.\n"); + return EINVAL; + } + + SAFEALIGN_COPY_UINT32(&start, body, NULL); + SAFEALIGN_COPY_UINT32(&terminator, body + blen - sizeof(uint32_t), NULL); + + if (start != SSS_START_OF_PAM_REQUEST + || terminator != SSS_END_OF_PAM_REQUEST) { + DEBUG(SSSDBG_CRIT_FAILURE, "Received data is invalid.\n"); + return EINVAL; + } + + c = sizeof(uint32_t); + do { + SAFEALIGN_COPY_UINT32_CHECK(&type, &body[c], blen, &c); + + if (type == SSS_END_OF_PAM_REQUEST) { + if (c != blen) return EINVAL; + } else { + SAFEALIGN_COPY_UINT32_CHECK(&size, &body[c], blen, &c); + /* the uint32_t end maker SSS_END_OF_PAM_REQUEST does not count to + * the remaining buffer */ + if (size > (blen - c - sizeof(uint32_t))) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid data size.\n"); + return EINVAL; + } + + switch(type) { + case SSS_PAM_ITEM_USER: + ret = extract_string(&pd->logon_name, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_SERVICE: + ret = extract_string(&pd->service, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_TTY: + ret = extract_string(&pd->tty, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_RUSER: + ret = extract_string(&pd->ruser, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_RHOST: + ret = extract_string(&pd->rhost, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_REQUESTED_DOMAINS: + ret = extract_string(&requested_domains, size, body, blen, + &c); + if (ret != EOK) return ret; + + ret = split_on_separator(pd, requested_domains, ',', true, + true, &pd->requested_domains, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse requested_domains list!\n"); + return ret; + } + break; + case SSS_PAM_ITEM_CLI_PID: + ret = extract_uint32_t(&pd->cli_pid, size, + body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_CHILD_PID: + /* This is optional. */ + ret = extract_uint32_t(&pd->child_pid, size, + body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_AUTHTOK: + ret = extract_authtok_v2(pd->authtok, + size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_NEWAUTHTOK: + ret = extract_authtok_v2(pd->newauthtok, + size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_FLAGS: + ret = extract_uint32_t(&pd->cli_flags, size, + body, blen, &c); + if (ret != EOK) return ret; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Ignoring unknown data type [%d].\n", type); + c += size; + } + } + + } while(c < blen); + + return EOK; + +} + +static int pam_parse_in_data_v3(struct pam_data *pd, + uint8_t *body, size_t blen) +{ + int ret; + + ret = pam_parse_in_data_v2(pd, body, blen); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_parse_in_data_v2 failed.\n"); + return ret; + } + + if (pd->cli_pid == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing client PID.\n"); + return EINVAL; + } + + return EOK; +} + +static int extract_authtok_v1(struct sss_auth_token *tok, + uint8_t *body, size_t blen, size_t *c) +{ + uint32_t auth_token_type; + uint32_t auth_token_length; + uint8_t *auth_token_data; + int ret = EOK; + + SAFEALIGN_COPY_UINT32_CHECK(&auth_token_type, &body[*c], blen, c); + SAFEALIGN_COPY_UINT32_CHECK(&auth_token_length, &body[*c], blen, c); + auth_token_data = body+(*c); + + switch (auth_token_type) { + case SSS_AUTHTOK_TYPE_EMPTY: + sss_authtok_set_empty(tok); + break; + case SSS_AUTHTOK_TYPE_PASSWORD: + ret = sss_authtok_set_password(tok, (const char *)auth_token_data, + auth_token_length); + break; + default: + return EINVAL; + } + + *c += auth_token_length; + + return ret; +} + +static int pam_parse_in_data(struct pam_data *pd, + uint8_t *body, size_t blen) +{ + size_t start; + size_t end; + size_t last; + int ret; + + last = blen - 1; + end = 0; + + /* user name */ + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->logon_name = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->service = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->tty = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->ruser = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->rhost = (char *) &body[start]; + + ret = extract_authtok_v1(pd->authtok, body, blen, &end); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid auth token\n"); + return ret; + } + ret = extract_authtok_v1(pd->newauthtok, body, blen, &end); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid new auth token\n"); + return ret; + } + + DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); + + return EOK; +} + +static errno_t +pam_get_local_auth_policy(struct sss_domain_info *domain, + const char *name, + bool *_sc_allow, + bool *_passkey_allow) +{ + TALLOC_CTX *tmp_ctx = NULL; + const char *attrs[] = { SYSDB_LOCAL_SMARTCARD_AUTH, SYSDB_LOCAL_PASSKEY_AUTH, NULL }; + struct ldb_message *ldb_msg; + bool sc_allow = false; + bool passkey_allow = false; + errno_t ret; + + if (name == NULL || *name == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); + ret = EINVAL; + goto done; + } + + if (domain->sysdb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing sysdb db context.\n"); + ret = EINVAL; + goto done; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_user_by_name(tmp_ctx, domain, name, attrs, &ldb_msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_search_user_by_name failed [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + + sc_allow = ldb_msg_find_attr_as_bool(ldb_msg, SYSDB_LOCAL_SMARTCARD_AUTH, + false); + + passkey_allow = ldb_msg_find_attr_as_bool(ldb_msg, SYSDB_LOCAL_PASSKEY_AUTH, + true); + + ret = EOK; + +done: + if (ret == EOK) { + *_sc_allow = sc_allow; + *_passkey_allow = passkey_allow; + } + + talloc_free(tmp_ctx); + return ret; +} +static errno_t set_local_auth_type(struct pam_auth_req *preq, + bool sc_allow, + bool passkey_allow) +{ + struct sysdb_attrs *attrs; + errno_t ret; + + attrs = sysdb_new_attrs(preq); + if (!attrs) { + ret = ENOMEM; + goto fail; + } + + if (sc_allow) { + /* Only set SYSDB_LOCAL_SMARTCARD_AUTH to 'true' but never to + * 'false'. The krb5 backend will only returns that Smartcard + * authentication is available if a Smartcard is present. That means + * if the user authenticates with a different method and a Smartcard + * is not present at this time 'sc_allow' will be 'false' and might + * overwrite a 'true' value written during a previous authentication + * attempt where a Smartcard was present. To avoid this we only write + * 'true' values. Since the default if SYSDB_LOCAL_SMARTCARD_AUTH is + * missing is 'false' local Smartcard authentication (offline) will + * still only be enabled if online Smartcard authentication was + * detected. */ + ret = sysdb_attrs_add_bool(attrs, SYSDB_LOCAL_SMARTCARD_AUTH, sc_allow); + if (ret != EOK) { + goto fail; + } + } + + ret = sysdb_attrs_add_bool(attrs, SYSDB_LOCAL_PASSKEY_AUTH, passkey_allow); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_set_user_attr(preq->domain, preq->pd->user, attrs, + SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "set_local_auth_type failed.\n"); + preq->pd->pam_status = PAM_SYSTEM_ERR; + goto fail; + } + + return EOK; + +fail: + return ret; +} +/*=Save-Last-Login-State===================================================*/ + +static errno_t set_last_login(struct pam_auth_req *preq) +{ + struct sysdb_attrs *attrs; + errno_t ret; + + attrs = sysdb_new_attrs(preq); + if (!attrs) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_ONLINE_AUTH, time(NULL)); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_attrs_add_time_t(attrs, + SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, + time(NULL)); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_LOGIN, time(NULL)); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_set_user_attr(preq->domain, preq->pd->user, attrs, + SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "set_last_login failed.\n"); + preq->pd->pam_status = PAM_SYSTEM_ERR; + goto fail; + } else { + preq->pd->last_auth_saved = true; + } + preq->callback(preq); + + return EOK; + +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 ? var_name : "(NULL)"), + (service ? service : "(NULL)")); + + 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 pam_ctx *pctx, + struct response_data *resp_list, + struct pam_data *pd) +{ + int ret; + struct response_data *resp; + uint32_t user_info_type; + int64_t expire_date = 0; + int pam_verbosity = DEFAULT_PAM_VERBOSITY; + char **new_opts; + size_t c; + const char *default_pam_response_filter[] = { "ENV:KRB5CCNAME:sudo", + "ENV:KRB5CCNAME:sudo-i", + NULL }; + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_VERBOSITY, DEFAULT_PAM_VERBOSITY, + &pam_verbosity); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read PAM verbosity, not fatal.\n"); + pam_verbosity = DEFAULT_PAM_VERBOSITY; + } + + if (pctx->pam_filter_opts == NULL) { + ret = confdb_get_string_as_list(pctx->rctx->cdb, pctx, + CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_RESPONSE_FILTER, + &pctx->pam_filter_opts); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read values of [%s], not fatal.\n", + CONFDB_PAM_RESPONSE_FILTER); + pctx->pam_filter_opts = NULL; + } else { + if (pctx->pam_filter_opts == NULL + || *pctx->pam_filter_opts[0] == '+' + || *pctx->pam_filter_opts[0] == '-') { + ret = mod_defaults_list(pctx, default_pam_response_filter, + pctx->pam_filter_opts, &new_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to modify [%s] defaults.\n", + CONFDB_PAM_RESPONSE_FILTER); + return ret; + } + talloc_free(pctx->pam_filter_opts); + pctx->pam_filter_opts = new_opts; + } + } + + if (pctx->pam_filter_opts == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "No PAM response filter set.\n"); + } else { + /* Make sure there are no '+' or '-' prefixes anymore */ + for (c = 0; pctx->pam_filter_opts[c] != NULL; c++) { + if (*pctx->pam_filter_opts[0] == '+' + || *pctx->pam_filter_opts[0] == '-') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unsupport mix of prefixed and not prefixed " + "values of [%s].\n", CONFDB_PAM_RESPONSE_FILTER); + return EINVAL; + } + DEBUG(SSSDBG_CONF_SETTINGS, + "PAM response filter: [%s].\n", + pctx->pam_filter_opts[c]); + } + } + } + + 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"); + ret = EINVAL; + goto done; + } + + if (pam_verbosity == PAM_VERBOSITY_NO_MESSAGES) { + resp->do_not_send_to_client = true; + resp = resp->next; + continue; + } + + memcpy(&user_info_type, resp->data, sizeof(uint32_t)); + + resp->do_not_send_to_client = false; + switch (user_info_type) { + case SSS_PAM_USER_INFO_OFFLINE_AUTH: + if (resp->len != sizeof(uint32_t) + sizeof(int64_t)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "User info offline auth entry is " + "too short.\n"); + ret = EINVAL; + goto done; + } + memcpy(&expire_date, resp->data + sizeof(uint32_t), + sizeof(int64_t)); + if ((expire_date == 0 && + pam_verbosity < PAM_VERBOSITY_INFO) || + (expire_date > 0 && + pam_verbosity < PAM_VERBOSITY_IMPORTANT)) { + resp->do_not_send_to_client = true; + } + + break; + default: + DEBUG(SSSDBG_TRACE_LIBS, + "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, pctx->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; + } + + resp = resp->next; + } + + ret = EOK; +done: + + return ret; +} + +static void do_not_send_cert_info(struct pam_data *pd) +{ + struct response_data *resp; + + resp = pd->resp_list; + while (resp != NULL) { + switch (resp->type) { + case SSS_PAM_CERT_INFO: + case SSS_PAM_CERT_INFO_WITH_HINT: + resp->do_not_send_to_client = true; + break; + default: + break; + } + resp = resp->next; + } +} + +static void evaluate_pam_resp_list(struct pam_data *pd, + struct pam_resp_auth_type *_auth_types, + bool *_found_cert_info) +{ + struct response_data *resp; + struct pam_resp_auth_type types = {0}; + bool found_cert_info = false; + + resp = pd->resp_list; + while (resp != NULL) { + switch (resp->type) { + case SSS_PAM_OTP_INFO: + types.otp_auth = true; + break; + case SSS_PAM_CERT_INFO: + case SSS_PAM_CERT_INFO_WITH_HINT: + found_cert_info = true; + break; + case SSS_PAM_PASSKEY_INFO: + case SSS_PAM_PASSKEY_KRB_INFO: + types.passkey_auth = true; + break; + case SSS_PASSWORD_PROMPTING: + types.password_auth = true; + break; + case SSS_CERT_AUTH_PROMPTING: + types.cert_auth = true; + break; + default: + break; + } + resp = resp->next; + } + + if (_auth_types != NULL) { + *_auth_types = types; + } + if (_found_cert_info != NULL) { + *_found_cert_info = found_cert_info; + } +} + +static void evalute_sending_cert_info(struct pam_data *pd) +{ + struct pam_resp_auth_type types = {0}; + bool found_cert_info = false; + + evaluate_pam_resp_list(pd, &types, &found_cert_info); + + if (found_cert_info && !types.cert_auth) { + do_not_send_cert_info(pd); + } +} + +errno_t pam_get_auth_types(struct pam_data *pd, + struct pam_resp_auth_type *_auth_types) +{ + int ret; + struct pam_resp_auth_type types = {0}; + + evaluate_pam_resp_list(pd, &types, NULL); + + if (!types.password_auth && !types.otp_auth && !types.cert_auth && !types.passkey_auth) { + /* If the backend cannot determine which authentication types are + * available the default would be to prompt for a password. */ + types.password_auth = true; + types.backend_returned_no_auth_type = true; + } + + DEBUG(SSSDBG_TRACE_ALL, "Authentication types for user [%s] and service " + "[%s]:%s%s%s%s\n", pd->user, pd->service, + types.password_auth ? " password": "", + types.otp_auth ? " two-factor" : "", + types.passkey_auth ? " passkey" : "", + types.cert_auth ? " smartcard" : ""); + + ret = EOK; + + *_auth_types = types; + + return ret; +} + +static errno_t pam_eval_local_auth_policy(TALLOC_CTX *mem_ctx, + struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq, + bool *_sc_allow, + bool *_passkey_allow, + char **_local_policy) { + + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char *domain_cdb; + char *local_policy = NULL; + bool sc_allow = false; + bool passkey_allow = false; + struct pam_resp_auth_type auth_types; + char **opts; + size_t c; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Check local auth policy */ + domain_cdb = talloc_asprintf(tmp_ctx, CONFDB_DOMAIN_PATH_TMPL, preq->domain->name); + if (domain_cdb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = confdb_get_string(pctx->rctx->cdb, tmp_ctx, domain_cdb, + CONFDB_DOMAIN_LOCAL_AUTH_POLICY, + "match", &local_policy); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get the confdb local_auth_policy\n"); + return ret; + } + + /* "only" ignores online methods and allows all local ones */ + if (strcasecmp(local_policy, "only") == 0) { + sc_allow = true; + passkey_allow = true; + /* Match what the KDC supports and provides */ + } else if (strcasecmp(local_policy, "match") == 0) { + /* Don't overwrite the local auth type when offline */ + if (pd->pam_status == PAM_SUCCESS && pd->cmd == SSS_PAM_PREAUTH && + !is_domain_provider(preq->domain, "ldap")) { + ret = pam_get_auth_types(pd, &auth_types); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get authentication types\n"); + goto done; + } + + if (auth_types.cert_auth) { + sc_allow = true; + } else if (auth_types.passkey_auth) { + passkey_allow = true; + } + + /* Store the local auth types, in case we go offline */ + if (!auth_types.backend_returned_no_auth_type) { + ret = set_local_auth_type(preq, sc_allow, passkey_allow); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to evaluate local auth policy\n"); + goto done; + } + } + } + + /* Read the latest auth types */ + ret = pam_get_local_auth_policy(preq->domain, preq->pd->user, + &sc_allow, &passkey_allow); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get PAM local auth policy\n"); + goto done; + } + /* Check for enable */ + } else { + ret = split_on_separator(tmp_ctx, local_policy, ',', true, true, &opts, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d], %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + for (c = 0; opts[c] != NULL; c++) { + if (strcasestr(opts[c], "passkey") != NULL) { + passkey_allow = strstr(opts[c], "enable") ? true : false; + } else if (strcasestr(opts[c], "smartcard") != NULL) { + sc_allow = strstr(opts[c], "enable") ? true : false; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unexpected local auth policy option [%s], " \ + "skipping.\n", opts[c]); + } + } + } + + /* if passkey is enabled but local Smartcard authentication is not but + * possible, the cert info data has to be remove as well if only local + * Smartcard authentication is possible. If Smartcard authentication + * is possible on the server side we have to keep it because the + * 'enable' option should only add local methods but not reject remote + * ones. */ + if (!sc_allow) { + evalute_sending_cert_info(pd); + } + + *_sc_allow = sc_allow; + *_passkey_allow = passkey_allow; + *_local_policy = talloc_steal(mem_ctx, local_policy); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static void pam_reply_delay(struct tevent_context *ev, struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct pam_auth_req *preq; + + DEBUG(SSSDBG_CONF_SETTINGS, "pam_reply_delay get called.\n"); + + preq = talloc_get_type(pvt, struct pam_auth_req); + + pam_reply(preq); +} + +static errno_t get_password_for_cache_auth(struct sss_auth_token *authtok, + const char **password) +{ + int ret; + size_t pw_len; + const char *fa2; + size_t fa2_len; + + switch (sss_authtok_get_type(authtok)) { + case SSS_AUTHTOK_TYPE_PASSWORD: + ret = sss_authtok_get_password(authtok, password, NULL); + break; + case SSS_AUTHTOK_TYPE_2FA: + ret = sss_authtok_get_2fa(authtok, password, &pw_len, &fa2, &fa2_len); + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, "Unsupported auth token type [%d].\n", + sss_authtok_get_type(authtok)); + ret = EINVAL; + } + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get password.\n"); + return ret; + } + + return EOK; +} + +static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd); +static void pam_handle_cached_login(struct pam_auth_req *preq, int ret, + time_t expire_date, time_t delayed_until, bool cached_auth); + +/* + * Add a request to add a variable to the PAM user environment, containing the + * actual (not overridden) user shell, in case session recording is enabled. + */ +static int pam_reply_sr_export_shell(struct pam_auth_req *preq, + const char *var_name) +{ + int ret; + TALLOC_CTX *ctx = NULL; + bool enabled; + const char *enabled_str; + const char *shell; + char *buf; + + /* Create temporary talloc context */ + ctx = talloc_new(NULL); + if (ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + ret = ENOMEM; + goto done; + } + + /* Check if session recording is enabled */ + if (preq->cctx->rctx->sr_conf.scope == + SESSION_RECORDING_SCOPE_NONE) { + enabled = false; + } else { + enabled_str = ldb_msg_find_attr_as_string(preq->user_obj, + SYSDB_SESSION_RECORDING, NULL); + if (enabled_str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "%s attribute not found\n", SYSDB_SESSION_RECORDING); + ret = ENOENT; + goto done; + } else if (strcmp(enabled_str, "TRUE") == 0) { + enabled = true; + } else if (strcmp(enabled_str, "FALSE") == 0) { + enabled = false; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "invalid value of %s attribute: %s\n", + SYSDB_SESSION_RECORDING, enabled_str); + ret = ENOENT; + goto done; + } + } + + /* Export original shell if recording is enabled and so it's overridden */ + if (enabled) { + /* Extract the shell */ + shell = sss_resp_get_shell_override(preq->user_obj, + preq->cctx->rctx, preq->domain); + if (shell == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "user has no shell\n"); + ret = ENOENT; + goto done; + } + + /* Format environment entry */ + buf = talloc_asprintf(ctx, "%s=%s", var_name, shell); + if (buf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + /* Add request to add the entry to user environment */ + ret = pam_add_response(preq->pd, SSS_PAM_ENV_ITEM, + strlen(buf) + 1, (uint8_t *)buf); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(ctx); + return ret; +} + +void pam_reply(struct pam_auth_req *preq) +{ + struct cli_ctx *cctx; + struct cli_protocol *prctx; + uint8_t *body; + size_t blen; + int ret; + int32_t resp_c; + int32_t resp_size; + struct response_data *resp; + int p; + struct timeval tv; + struct tevent_timer *te; + struct pam_data *pd; + char *local_policy = NULL; + struct pam_ctx *pctx; + uint32_t user_info_type; + time_t exp_date = -1; + time_t delay_until = -1; + char* pam_account_expired_message; + char* pam_account_locked_message; + int pam_verbosity; + bool local_sc_auth_allow = false; + bool local_passkey_auth_allow = false; +#ifdef BUILD_PASSKEY + bool pk_preauth_done = false; +#endif /* BUILD_PASSKEY */ + + pd = preq->pd; + cctx = preq->cctx; + pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + prctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_VERBOSITY, DEFAULT_PAM_VERBOSITY, + &pam_verbosity); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read PAM verbosity, not fatal.\n"); + pam_verbosity = DEFAULT_PAM_VERBOSITY; + } + + DEBUG(SSSDBG_TRACE_ALL, + "pam_reply initially called with result [%d]: %s. " + "this result might be changed during processing\n", + pd->pam_status, pam_strerror(NULL, pd->pam_status)); + + if (preq->domain != NULL && preq->domain->name != NULL) { + ret = pam_eval_local_auth_policy(cctx, pctx, pd, preq, + &local_sc_auth_allow, + &local_passkey_auth_allow, + &local_policy); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to evaluate local auth policy\n"); + goto done; + } + } + + /* Ignore local_auth_policy for the files provider, allow local + * smartcard auth (default behavior prior to local_auth_policy) */ + if (is_domain_provider(preq->domain, "files")) { + local_sc_auth_allow = true; + /* For the ldap auth provider we currently only support + * password based authentication */ + } else if (is_domain_provider(preq->domain, "ldap") && local_policy != NULL + && strcasecmp(local_policy, "match") == 0) { + local_passkey_auth_allow = false; + local_sc_auth_allow = false; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Local auth policy allowed: smartcard [%s], passkey [%s]\n", + local_sc_auth_allow ? "True" : "False", + local_passkey_auth_allow ? "True" : "False"); + + if (pd->cmd == SSS_PAM_AUTHENTICATE + && !preq->cert_auth_local + && (pd->pam_status == PAM_AUTHINFO_UNAVAIL + || pd->pam_status == PAM_NO_MODULE_DATA + || pd->pam_status == PAM_BAD_ITEM) + && may_do_cert_auth(pctx, pd)) { + /* We have Smartcard credentials and the backend indicates that it is + * offline (PAM_AUTHINFO_UNAVAIL) or cannot handle the credentials + * (PAM_BAD_ITEM), so let's try authentication against the Smartcard + * PAM_NO_MODULE_DATA is returned by the krb5 backend if no + * authentication method was found at all, this might happen if the + * user has a Smartcard assigned but the pkint plugin is not available + * on the client. */ + DEBUG(SSSDBG_IMPORTANT_INFO, + "Backend cannot handle Smartcard authentication, " + "trying local Smartcard authentication.\n"); + if (local_sc_auth_allow) { + preq->cert_auth_local = true; + ret = check_cert(cctx, cctx->ev, pctx, preq, pd); + pam_check_user_done(preq, ret); + return; + } else { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Local smartcard auth not allowed by local_auth_policy"); + } + } + + if (pd->pam_status == PAM_AUTHINFO_UNAVAIL || preq->use_cached_auth) { + + switch(pd->cmd) { + case SSS_PAM_AUTHENTICATE: + if ((preq->domain != NULL) && + (preq->domain->cache_credentials == true) && + (pd->offline_auth == false)) { + const char *password = NULL; + bool use_cached_auth; + + /* backup value of preq->use_cached_auth*/ + use_cached_auth = preq->use_cached_auth; + /* set to false to avoid entering this branch when pam_reply() + * is recursively called from pam_handle_cached_login() */ + preq->use_cached_auth = false; + + /* do auth with offline credentials */ + pd->offline_auth = true; + + if (preq->domain->sysdb == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Fatal: Sysdb CTX not found for domain" + " [%s]!\n", preq->domain->name); + goto done; + } + + ret = get_password_for_cache_auth(pd->authtok, &password); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "get_password_and_type_for_cache_auth failed.\n"); + goto done; + } + + ret = sysdb_cache_auth(preq->domain, + pd->user, password, + pctx->rctx->cdb, false, + &exp_date, &delay_until); + + pam_handle_cached_login(preq, ret, exp_date, delay_until, + use_cached_auth); + return; + } + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + case SSS_PAM_CHAUTHTOK: + DEBUG(SSSDBG_FUNC_DATA, + "Password change not possible while offline.\n"); + pd->pam_status = PAM_AUTHTOK_ERR; + user_info_type = SSS_PAM_USER_INFO_OFFLINE_CHPASS; + ret = pam_add_response(pd, SSS_PAM_USER_INFO, sizeof(uint32_t), + (const uint8_t *) &user_info_type); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + break; +/* TODO: we need the pam session cookie here to make sure that cached + * authentication was successful */ + case SSS_PAM_PREAUTH: + case SSS_PAM_SETCRED: + case SSS_PAM_ACCT_MGMT: + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_CLOSE_SESSION: + DEBUG(SSSDBG_OP_FAILURE, + "Assuming offline authentication setting status for " + "pam call %d to PAM_SUCCESS.\n", pd->cmd); + pd->pam_status = PAM_SUCCESS; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown PAM call [%d].\n", pd->cmd); + pd->pam_status = PAM_MODULE_UNKNOWN; + } + } + + if (pd->pam_status == PAM_SUCCESS && pd->cmd == SSS_PAM_CHAUTHTOK) { + ret = pam_null_last_online_auth_with_curr_token(preq->domain, + pd->user); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_null_last_online_auth_with_curr_token failed: " + "%s [%d].\n", sss_strerror(ret), ret); + goto done; + } + } + + if (pd->response_delay > 0) { + ret = gettimeofday(&tv, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "gettimeofday failed [%d][%s].\n", + errno, strerror(errno)); + goto done; + } + tv.tv_sec += pd->response_delay; + tv.tv_usec = 0; + pd->response_delay = 0; + + te = tevent_add_timer(cctx->ev, cctx, tv, pam_reply_delay, preq); + if (te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add event pam_reply_delay.\n"); + goto done; + } + + return; + } + + /* If this was a successful login, save the lastLogin time */ + if (pd->cmd == SSS_PAM_AUTHENTICATE && + pd->pam_status == PAM_SUCCESS && + preq->domain && + preq->domain->cache_credentials && + !pd->offline_auth && + !pd->last_auth_saved) { + ret = set_last_login(preq); + if (ret != EOK) { + goto done; + } + return; + } + + ret = sss_packet_new(prctx->creq, 0, sss_packet_get_cmd(prctx->creq->in), + &prctx->creq->out); + if (ret != EOK) { + goto done; + } + +#ifdef BUILD_PASSKEY + if(pd->cmd == SSS_PAM_AUTHENTICATE && + pd->pam_status == PAM_NEW_AUTHTOK_REQD && + sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_REPLY) { + DEBUG(SSSDBG_TRACE_FUNC, "Passkey authentication reply, ignoring " + "new authtok required status\n"); + pd->pam_status = PAM_SUCCESS; + } + + /* Passkey auth user notification if no TGT is granted */ + if (pd->cmd == SSS_PAM_AUTHENTICATE && + pd->pam_status == PAM_SUCCESS && + preq->pd->passkey_local_done) { + user_info_type = SSS_PAM_USER_INFO_NO_KRB_TGT; + pam_add_response(pd, SSS_PAM_USER_INFO, + sizeof(uint32_t), (const uint8_t *) &user_info_type); + DEBUG(SSSDBG_IMPORTANT_INFO, + "User [%s] logged in with local passkey authentication, single " + "sign on ticket is not obtained.\n", pd->user); + } +#endif /* BUILD_PASSKEY */ + + /* Account expiration warning is printed for sshd. If pam_verbosity + * is equal or above PAM_VERBOSITY_INFO then all services are informed + * about account expiration. + */ + if (pd->pam_status == PAM_ACCT_EXPIRED && + ((pd->service != NULL && strcasecmp(pd->service, "sshd") == 0) || + pam_verbosity >= PAM_VERBOSITY_INFO)) { + + ret = confdb_get_string(pctx->rctx->cdb, pd, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_ACCOUNT_EXPIRED_MESSAGE, "", + &pam_account_expired_message); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to get expiration message: %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + inform_user(pd, pam_account_expired_message); + } + + if (pd->account_locked) { + + ret = confdb_get_string(pctx->rctx->cdb, pd, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_ACCOUNT_LOCKED_MESSAGE, "", + &pam_account_locked_message); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to get expiration message: %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + inform_user(pd, pam_account_locked_message); + } + + ret = filter_responses(pctx, pd->resp_list, pd); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "filter_responses failed, not fatal.\n"); + } + + if (pd->domain != NULL) { + ret = pam_add_response(pd, SSS_PAM_DOMAIN_NAME, strlen(pd->domain)+1, + (uint8_t *) pd->domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + } + + 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"); + } + +#ifdef BUILD_PASSKEY + ret = pam_eval_passkey_response(pctx, pd, preq, &pk_preauth_done); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to eval passkey response\n"); + goto done; + } + + if (may_do_passkey_auth(pctx, pd) + && !pk_preauth_done + && preq->passkey_data_exists + && local_passkey_auth_allow) { + ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); + pam_check_user_done(preq, ret); + return; + } +#endif /* BUILD_PASSKEY */ + } + + /* + * Export non-overridden shell to tlog-rec-session when opening the session + */ + if (pd->cmd == SSS_PAM_OPEN_SESSION && pd->pam_status == PAM_SUCCESS) { + ret = pam_reply_sr_export_shell(preq, "TLOG_REC_SESSION_SHELL"); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "failed to export the shell to tlog-rec-session.\n"); + goto done; + } + } + + resp_c = 0; + resp_size = 0; + resp = pd->resp_list; + while(resp != NULL) { + if (!resp->do_not_send_to_client) { + resp_c++; + resp_size += resp->len; + } + resp = resp->next; + } + + ret = sss_packet_grow(prctx->creq->out, sizeof(int32_t) + + sizeof(int32_t) + + resp_c * 2* sizeof(int32_t) + + resp_size); + if (ret != EOK) { + goto done; + } + + sss_packet_get_body(prctx->creq->out, &body, &blen); + DEBUG(SSSDBG_FUNC_DATA, "blen: %zu\n", blen); + p = 0; + + memcpy(&body[p], &pd->pam_status, sizeof(int32_t)); + p += sizeof(int32_t); + + memcpy(&body[p], &resp_c, sizeof(int32_t)); + p += sizeof(int32_t); + + resp = pd->resp_list; + while(resp != NULL) { + if (!resp->do_not_send_to_client) { + memcpy(&body[p], &resp->type, sizeof(int32_t)); + p += sizeof(int32_t); + memcpy(&body[p], &resp->len, sizeof(int32_t)); + p += sizeof(int32_t); + memcpy(&body[p], resp->data, resp->len); + p += resp->len; + } + + resp = resp->next; + } + +done: + DEBUG(SSSDBG_FUNC_DATA, "Returning [%d]: %s to the client\n", + pd->pam_status, pam_strerror(NULL, pd->pam_status)); + sss_cmd_done(cctx, preq); +} + +static void pam_dom_forwarder(struct pam_auth_req *preq); + +static void pam_handle_cached_login(struct pam_auth_req *preq, int ret, + time_t expire_date, time_t delayed_until, + bool use_cached_auth) +{ + uint32_t resp_type; + size_t resp_len; + uint8_t *resp; + int64_t dummy; + + preq->pd->pam_status = cached_login_pam_status(ret); + + switch (preq->pd->pam_status) { + case PAM_SUCCESS: + resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH; + resp_len = sizeof(uint32_t) + sizeof(int64_t); + resp = talloc_size(preq->pd, resp_len); + if (resp == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_size failed, cannot prepare user info.\n"); + } else { + memcpy(resp, &resp_type, sizeof(uint32_t)); + dummy = (int64_t) expire_date; + memcpy(resp+sizeof(uint32_t), &dummy, sizeof(int64_t)); + ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, + (const uint8_t *) resp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + } + break; + case PAM_PERM_DENIED: + if (delayed_until >= 0) { + resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED; + resp_len = sizeof(uint32_t) + sizeof(int64_t); + resp = talloc_size(preq->pd, resp_len); + if (resp == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_size failed, cannot prepare user info.\n"); + } else { + memcpy(resp, &resp_type, sizeof(uint32_t)); + dummy = (int64_t) delayed_until; + memcpy(resp+sizeof(uint32_t), &dummy, sizeof(int64_t)); + ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, + (const uint8_t *) resp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "pam_add_response failed.\n"); + } + } + } + break; + case PAM_AUTH_ERR: + /* Was this attempt to authenticate from cache? */ + if (use_cached_auth) { + /* Don't try cached authentication again, try online check. */ + DEBUG(SSSDBG_FUNC_DATA, + "Cached authentication failed for: %s\n", + preq->pd->user); + preq->cached_auth_failed = true; + pam_dom_forwarder(preq); + return; + } + break; + default: + DEBUG(SSSDBG_TRACE_LIBS, + "cached login returned: %d\n", preq->pd->pam_status); + } + + pam_reply(preq); + return; +} + +static void pam_forwarder_cb(struct tevent_req *req); +static void pam_forwarder_cert_cb(struct tevent_req *req); +int pam_check_user_search(struct pam_auth_req *preq); + + +/* TODO: we should probably return some sort of cookie that is set in the + * PAM_ENVIRONMENT, so that we can save performing some calls and cache + * data. */ + +static errno_t pam_forwarder_parse_data(struct cli_ctx *cctx, struct pam_data *pd) +{ + struct cli_protocol *prctx; + uint8_t *body; + size_t blen; + errno_t ret; + uint32_t terminator; + const char *key_id; + + prctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + + sss_packet_get_body(prctx->creq->in, &body, &blen); + if (blen >= sizeof(uint32_t)) { + SAFEALIGN_COPY_UINT32(&terminator, + body + blen - sizeof(uint32_t), + NULL); + if (terminator != SSS_END_OF_PAM_REQUEST) { + DEBUG(SSSDBG_CRIT_FAILURE, "Received data not terminated.\n"); + ret = EINVAL; + goto done; + } + } + + switch (prctx->cli_protocol_version->version) { + case 1: + ret = pam_parse_in_data(pd, body, blen); + break; + case 2: + ret = pam_parse_in_data_v2(pd, body, blen); + break; + case 3: + ret = pam_parse_in_data_v3(pd, body, blen); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Illegal protocol version [%d].\n", + prctx->cli_protocol_version->version); + ret = EINVAL; + } + if (ret != EOK) { + goto done; + } + + if (pd->logon_name != NULL) { + ret = sss_parse_name_for_domains(pd, cctx->rctx->domains, + cctx->rctx->default_domain, + pd->logon_name, + &pd->domain, &pd->user); + } else { + /* SSS_PAM_PREAUTH request may have a missing name, e.g. if the + * name is determined with the help of a certificate. During + * SSS_PAM_AUTHENTICATE at least a key ID is needed to identify the + * selected certificate. */ + if (pd->cmd == SSS_PAM_AUTHENTICATE + && may_do_cert_auth(talloc_get_type(cctx->rctx->pvt_ctx, + struct pam_ctx), pd) + && (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_SC_PIN + || sss_authtok_get_type(pd->authtok) + == SSS_AUTHTOK_TYPE_SC_KEYPAD)) { + ret = sss_authtok_get_sc(pd->authtok, NULL, NULL, NULL, NULL, NULL, + NULL, &key_id, NULL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_sc failed.\n"); + goto done; + } + + if (key_id == NULL || *key_id == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing logon and Smartcard key ID during " + "authentication.\n"); + ret = ERR_NO_CREDS; + goto done; + } + + ret = EOK; + } else if (pd->cmd == SSS_PAM_PREAUTH + && may_do_cert_auth(talloc_get_type(cctx->rctx->pvt_ctx, + struct pam_ctx), pd)) { + ret = EOK; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing logon name in PAM request.\n"); + ret = ERR_NO_CREDS; + goto done; + } + } + + DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); + +done: + return ret; +} + +static bool is_uid_trusted(struct cli_creds *creds, + size_t trusted_uids_count, + uid_t *trusted_uids) +{ + errno_t ret; + + /* root is always trusted */ + if (client_euid(creds) == 0) { + return true; + } + + /* All uids are allowed */ + if (trusted_uids_count == 0) { + return true; + } + + ret = check_allowed_uids(client_euid(creds), trusted_uids_count, trusted_uids); + if (ret == EOK) return true; + + return false; +} + +static bool is_domain_public(char *name, + char **public_dom_names, + size_t public_dom_names_count) +{ + size_t i; + + for(i=0; i < public_dom_names_count; i++) { + if (strcasecmp(name, public_dom_names[i]) == 0) { + return true; + } + } + return false; +} + +static enum cache_req_dom_type +get_domain_request_type(struct pam_auth_req *preq, + struct pam_ctx *pctx) +{ + enum cache_req_dom_type req_dom_type; + + /* By default, only POSIX domains are to be contacted */ + req_dom_type = CACHE_REQ_POSIX_DOM; + + for (int i = 0; pctx->app_services[i]; i++) { + if (strcmp(pctx->app_services[i], preq->pd->service) == 0) { + req_dom_type = CACHE_REQ_APPLICATION_DOM; + break; + } + } + + return req_dom_type; +} + +static errno_t check_cert(TALLOC_CTX *mctx, + struct tevent_context *ev, + struct pam_ctx *pctx, + struct pam_auth_req *preq, + struct pam_data *pd) +{ + int p11_child_timeout; + int wait_for_card_timeout; + char *cert_verification_opts; + errno_t ret; + struct tevent_req *req; + char *uri = NULL; + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_P11_CHILD_TIMEOUT, + P11_CHILD_TIMEOUT_DEFAULT, + &p11_child_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read p11_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + if ((pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) && pd->priv == 1) { + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_WAIT_FOR_CARD_TIMEOUT, + P11_WAIT_FOR_CARD_TIMEOUT_DEFAULT, + &wait_for_card_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read [%s] from confdb: [%d]: %s\n", + CONFDB_PAM_WAIT_FOR_CARD_TIMEOUT, ret, sss_strerror(ret)); + return ret; + } + + p11_child_timeout += wait_for_card_timeout; + } + + ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_CERT_VERIFICATION, + NULL, &cert_verification_opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read '"CONFDB_PAM_CERT_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (cert_verification_opts == NULL) { + ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_CERT_VERIFICATION, NULL, + &cert_verification_opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read '"CONFDB_MONITOR_CERT_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + } + + ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_P11_URI, NULL, &uri); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read '"CONFDB_PAM_P11_URI"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + req = pam_check_cert_send(mctx, ev, + pctx->ca_db, p11_child_timeout, + cert_verification_opts, pctx->sss_certmap_ctx, + uri, pd); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_check_cert_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(req, pam_forwarder_cert_cb, preq); + return EAGAIN; +} + + +static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd) +{ + struct pam_auth_req *preq; + struct pam_data *pd; + int ret; + struct pam_ctx *pctx = + talloc_get_type(cctx->rctx->pvt_ctx, struct pam_ctx); + struct tevent_req *req; + + preq = talloc_zero(cctx, struct pam_auth_req); + if (!preq) { + return ENOMEM; + } + preq->cctx = cctx; + preq->cert_auth_local = false; + preq->client_id_num = cctx->client_id_num; + + preq->pd = create_pam_data(preq); + if (!preq->pd) { + talloc_free(preq); + return ENOMEM; + } + pd = preq->pd; + + preq->is_uid_trusted = is_uid_trusted(cctx->creds, + pctx->trusted_uids_count, + pctx->trusted_uids); + + if (!preq->is_uid_trusted) { + DEBUG(SSSDBG_MINOR_FAILURE, "uid %"SPRIuid" is not trusted.\n", + client_euid(cctx->creds)); + } + + + pd->cmd = pam_cmd; + pd->priv = cctx->priv; + pd->client_id_num = cctx->client_id_num; + + ret = pam_forwarder_parse_data(cctx, pd); + if (ret == EAGAIN) { + req = sss_dp_get_domains_send(cctx, cctx->rctx, true, pd->domain); + if (req == NULL) { + ret = ENOMEM; + } else { + tevent_req_set_callback(req, pam_forwarder_cb, preq); + ret = EAGAIN; + } + goto done; + } else if (ret != EOK) { + goto done; + } + + /* Determine what domain type to contact */ + preq->req_dom_type = get_domain_request_type(preq, pctx); + + if (pd->cmd == SSS_PAM_AUTHENTICATE + && (pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) + && !IS_SC_AUTHTOK(pd->authtok)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Smartcard authentication required but authentication " + "token [%d][%s] is not suitable.\n", + sss_authtok_get_type(pd->authtok), + sss_authtok_type_to_str(sss_authtok_get_type(pd->authtok))); + ret = ERR_NO_CREDS; + goto done; + } + + /* Try backend first for authentication before doing local Smartcard + * authentication if a logon name is available. Otherwise try to derive + * the logon name from the certificate first. */ + if ((pd->cmd != SSS_PAM_AUTHENTICATE + || (pd->cmd == SSS_PAM_AUTHENTICATE && pd->logon_name == NULL)) + && may_do_cert_auth(pctx, pd)) { + ret = check_cert(cctx, cctx->ev, pctx, preq, pd); + /* Finish here */ + goto done; + } + + /* This is set to false inside passkey_local() if no passkey data is found. + * It is checked in pam_reply() to avoid an endless loop */ + preq->passkey_data_exists = true; + +#ifdef BUILD_PASSKEY + if ((pd->cmd == SSS_PAM_AUTHENTICATE)) { + if (may_do_passkey_auth(pctx, pd)) { + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + ret = passkey_kerberos(pctx, preq->pd, preq); + goto done; + } else if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY) { + /* TYPE_EMPTY is not a valid */ + ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); + goto done; + } + } + } + + if (pd->cmd == SSS_PAM_PASSKEY_PREAUTH) { + +#define PASSKEY_DOPIN "/var/run/passkey-dopin" +#define PASSKEY_PINUV "/var/run/passkey-pinuv" +#define PASSKEY_PINONLY "/var/run/passkey-pinonly" +#define PASSKEY_NODEV "/var/run/passkey-nodev" + + DEBUG(SSSDBG_TRACE_FUNC, "required passkey device information .\n"); + if (may_do_passkey_auth(pctx, pd)) { + const char *devinfo = "nocacheddevinfo"; + + if (pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_PASSKEY_CACHED_DEVINFO) { + struct stat st_nodev; + int e; + DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] requests cached devinfo .\n", getpid()); + + e = stat (PASSKEY_NODEV, &st_nodev); + if (e == 0) { + /* indicator exists */ + if (time(NULL) < st_nodev.st_ctime + 100 /*seconds : configurable ??? */) { + /* the info is still valid */ + devinfo = "nodev"; + } else { + /* but the info is obsolete, we shall retest device existence */ + devinfo = "nodevobsolete"; + (void)remove (PASSKEY_NODEV); + } + } else { + DEBUG(SSSDBG_TRACE_FUNC, "stat nodev: errno=%d\n", errno); + if (access(PASSKEY_DOPIN, F_OK) == 0) { + /* the device requires a PIN to perform next authentication; */ + devinfo = "dopin"; + } else if (access(PASSKEY_PINONLY, F_OK) == 0) { + /* the device ALWAYS requires a PIN to perform authentication; */ + devinfo = "pinonly"; + } else if (access(PASSKEY_PINUV, F_OK) == 0) { + /* the cached device supports UV and does not fallback to PIN using + * PASSKEY_DOPIN indicator. + */ + devinfo = "pinuv"; + } + } + /* remove cache indicators. + * They will be rebuild according the device capabilities + * (see passkey_child) + * during authentication or fresh devinfo request. + * do not remove DOPIN that is updated only during authentication + */ + (void)remove (PASSKEY_PINUV); + (void)remove (PASSKEY_PINONLY); + + } else { + /* remove cache indicators. + * They will be rebuild according the device capabilities + * (see passkey_child) + * during this devinfo request + * do not remove DOPIN that is updated only during authentication + */ + (void)remove (PASSKEY_PINUV); + (void)remove (PASSKEY_PINONLY); + + DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] requests fresh devinfo .\n", getpid()); + /* + * ask the device capabilities + * This assumes the device is connected. + */ + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] calls passkey_kerberos_get_devinfo() \n", getpid()); + ret = passkey_kerberos_get_devinfo(pctx, preq->pd, preq); + DEBUG(SSSDBG_TRACE_FUNC, "passkey_kerberos_get_devinfo() returns %d\n", ret); + goto done; + } else if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_EMPTY) { + + DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] calls passkey_local_get_devinfo() .\n", getpid()); + ret = passkey_local_get_devinfo(cctx, cctx->ev, pctx, preq, pd); + DEBUG(SSSDBG_TRACE_FUNC, "passkey_local_get_devinfo() returns %d\n", ret); + goto done; + } else { + /* note that AUTHTOK_TYPE_PASSKEY is not a valid authtok + * see get_device_info in pam_sss.c + */ + DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] requests fresh with invalid authtok type: [%d]\n", + getpid(),sss_authtok_get_type(pd->authtok) ); + ret = PAM_AUTHTOK_ERR; + goto done; + } + } + + DEBUG(SSSDBG_TRACE_FUNC, "cached devinfo request replies: [%s}\n", devinfo); + + ret = pam_add_response(pd, SSS_PAM_PASSKEY_DEVINFO, strlen(devinfo) + 1, + (const uint8_t *) devinfo); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed. [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + pd->pam_status = PAM_SUCCESS; + pam_reply(preq); + + /* finish */ + return EOK; + } + DEBUG(SSSDBG_TRACE_FUNC, "passkey_get_devinfo requires OK from may_do_passkey_auth .\n"); + } +#endif /* BUILD_PASSKEY */ + + ret = pam_check_user_search(preq); + +done: + return pam_check_user_done(preq, ret); +} + +static errno_t pam_user_by_cert_step(struct pam_auth_req *preq); +static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req); +static void pam_forwarder_cert_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + struct pam_data *pd; + errno_t ret = EOK; + const char *cert; + + ret = pam_check_cert_recv(req, preq, &preq->cert_list); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_cert request failed.\n"); + goto done; + } + + pd = preq->pd; + + cert = sss_cai_get_cert(preq->cert_list); + + if (cert == NULL) { + if (pd->logon_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No certificate found and no logon name given, " \ + "authentication not possible.\n"); + ret = ENOENT; + } else if (pd->cmd == SSS_PAM_PREAUTH + && (pd->cli_flags & PAM_CLI_FLAGS_TRY_CERT_AUTH)) { + DEBUG(SSSDBG_TRACE_ALL, + "try_cert_auth flag set but no certificate available, " + "request finished.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + pam_reply(preq); + return; + } else { + if (pd->cmd == SSS_PAM_AUTHENTICATE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No certificate returned, authentication failed.\n"); + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + return; + } else { + ret = pam_check_user_search(preq); + } + + } + goto done; + } + + preq->current_cert = preq->cert_list; + ret = pam_user_by_cert_step(preq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n"); + goto done; + } + + return; + +done: + pam_check_user_done(preq, ret); +} + +static errno_t pam_user_by_cert_step(struct pam_auth_req *preq) +{ + struct cli_ctx *cctx = preq->cctx; + struct tevent_req *req; + struct pam_ctx *pctx = + talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + if (preq->current_cert == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate data.\n"); + return EINVAL; + } + + req = cache_req_user_by_cert_send(preq, cctx->ev, cctx->rctx, + pctx->rctx->ncache, 0, + preq->req_dom_type, NULL, + sss_cai_get_cert(preq->current_cert)); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(req, pam_forwarder_lookup_by_cert_done, preq); + return EOK; +} + +static errno_t get_results_from_all_domains(TALLOC_CTX *mem_ctx, + struct cache_req_result **results, + struct ldb_result **ldb_results) +{ + int ret; + size_t count = 0; + size_t c; + size_t d; + size_t r = 0; + struct ldb_result *res; + + for (d = 0; results != NULL && results[d] != NULL; d++) { + count += results[d]->count; + } + + res = talloc_zero(mem_ctx, struct ldb_result); + if (res == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + + if (count == 0) { + *ldb_results = res; + return EOK; + } + + res->msgs = talloc_zero_array(res, struct ldb_message *, count); + if (res->msgs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + return ENOMEM; + } + res->count = count; + + for (d = 0; results != NULL && results[d] != NULL; d++) { + for (c = 0; c < results[d]->count; c++) { + if (r >= count) { + DEBUG(SSSDBG_CRIT_FAILURE, + "More results found then counted before.\n"); + ret = EINVAL; + goto done; + } + res->msgs[r++] = talloc_steal(res->msgs, results[d]->msgs[c]); + } + } + + *ldb_results = res; + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(res); + } + + return ret; +} + +/* Return true if hint is set for at least one domain */ +static bool get_user_name_hint(struct sss_domain_info *domains) +{ + struct sss_domain_info *d; + + DLIST_FOR_EACH(d, domains) { + if (d->user_name_hint == true) { + return true; + } + } + + return false; +} + +static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req) +{ + int ret; + struct cache_req_result **results; + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + const char *cert_user = NULL; + size_t cert_count = 0; + size_t cert_user_count = 0; + struct ldb_result *cert_user_objs; + + ret = cache_req_recv(preq, req, &results); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert request failed.\n"); + goto done; + } + + if (ret == EOK) { + ret = get_results_from_all_domains(preq, results, + &cert_user_objs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_results_from_all_domains failed.\n"); + goto done; + } + + sss_cai_set_cert_user_objs(preq->current_cert, cert_user_objs); + } + + preq->current_cert = sss_cai_get_next(preq->current_cert); + if (preq->current_cert != NULL) { + ret = pam_user_by_cert_step(preq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n"); + goto done; + } + return; + } + + sss_cai_check_users(&preq->cert_list, &cert_count, &cert_user_count); + DEBUG(SSSDBG_TRACE_ALL, + "Found [%zu] certificates and [%zu] related users.\n", + cert_count, cert_user_count); + + if (cert_user_count == 0) { + if (preq->pd->logon_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing logon name and no certificate user found.\n"); + ret = ENOENT; + goto done; + } + } else { + + if (preq->pd->logon_name == NULL) { + if (preq->pd->cmd != SSS_PAM_PREAUTH + && preq->pd->cmd != SSS_PAM_AUTHENTICATE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing logon name only allowed during (pre-)auth.\n"); + ret = ENOENT; + goto done; + } + /* Multiple certificates are only expected during pre-auth */ + if (cert_count > 1 && preq->pd->cmd == SSS_PAM_PREAUTH) { + for (preq->current_cert = preq->cert_list; + preq->current_cert != NULL; + preq->current_cert = sss_cai_get_next(preq->current_cert)) { + + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, "", + preq->current_cert, + get_user_name_hint(preq->cctx->rctx->domains) + ? SSS_PAM_CERT_INFO_WITH_HINT + : SSS_PAM_CERT_INFO); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + } + + ret = EOK; + preq->pd->pam_status = PAM_SUCCESS; + pam_reply(preq); + goto done; + } + + if (cert_user_count == 1) { + cert_user_objs = sss_cai_get_cert_user_objs(preq->cert_list); + if (cert_user_objs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate user.\n"); + ret = ENOENT; + goto done; + } + + cert_user = ldb_msg_find_attr_as_string( + cert_user_objs->msgs[0], + SYSDB_NAME, NULL); + if (cert_user == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Certificate user object has not name.\n"); + ret = ENOENT; + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, + "Found certificate user [%s].\n", cert_user); + + ret = sss_parse_name_for_domains(preq->pd, + preq->cctx->rctx->domains, + preq->cctx->rctx->default_domain, + cert_user, + &preq->pd->domain, + &preq->pd->user); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_parse_name_for_domains failed.\n"); + goto done; + } + } + + if (get_user_name_hint(preq->cctx->rctx->domains) + && preq->pd->cmd == SSS_PAM_PREAUTH) { + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, cert_user, + preq->cert_list, + SSS_PAM_CERT_INFO_WITH_HINT); + preq->pd->pam_status = PAM_SUCCESS; + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + ret = EOK; + pam_reply(preq); + goto done; + } + + /* Without user name hints the certificate must map to single user + * if no login name was given */ + if (cert_user == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "More than one user mapped to certificate.\n"); + ret = ERR_NO_CREDS; + goto done; + } + + /* If logon_name was not given during authentication add a + * SSS_PAM_CERT_INFO message to send the name to the caller. + * Additionally initial_cert_auth_successful is set to + * indicate that the user is already authenticated. */ + if (preq->pd->cmd == SSS_PAM_AUTHENTICATE + && preq->pd->logon_name == NULL) { + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, cert_user, + preq->cert_list, + SSS_PAM_CERT_INFO); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + goto done; + } + + preq->initial_cert_auth_successful = true; + } + + /* cert_user will be returned to the PAM client as user name, so + * we can use it here already e.g. to set in initgroups timeout */ + preq->pd->logon_name = talloc_strdup(preq->pd, cert_user); + if (preq->pd->logon_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } + } + + if (preq->user_obj == NULL) { + ret = pam_check_user_search(preq); + } else { + ret = EOK; + } + + if (ret == EOK) { + pam_dom_forwarder(preq); + } + +done: + pam_check_user_done(preq, ret); +} + +static void pam_forwarder_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + struct cli_ctx *cctx = preq->cctx; + struct pam_data *pd; + errno_t ret = EOK; + struct pam_ctx *pctx = + talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + ret = sss_dp_get_domains_recv(req); + talloc_free(req); + if (ret != EOK) { + goto done; + } + + ret = p11_refresh_certmap_ctx(pctx, pctx->rctx->domains); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "p11_refresh_certmap_ctx failed, " + "certificate matching might not work as expected"); + } + + pd = preq->pd; + + ret = pam_forwarder_parse_data(cctx, pd); + if (ret == EAGAIN) { + DEBUG(SSSDBG_TRACE_FUNC, "Assuming %s is a UPN\n", pd->logon_name); + /* If not, cache_req will error out later */ + pd->user = talloc_strdup(pd, pd->logon_name); + if (pd->user == NULL) { + ret = ENOMEM; + goto done; + } + pd->domain = NULL; + } else if (ret != EOK) { + ret = EINVAL; + goto done; + } + + /* try backend first for authentication before doing local Smartcard + * authentication */ + if (pd->cmd != SSS_PAM_AUTHENTICATE && may_do_cert_auth(pctx, pd)) { + ret = check_cert(cctx, cctx->ev, pctx, preq, pd); + /* Finish here */ + goto done; + } + +#ifdef BUILD_PASSKEY + /* This is set to false inside passkey_local() if no passkey data is found. + * It is checked in pam_reply() to avoid an endless loop */ + preq->passkey_data_exists = true; + + if ((pd->cmd == SSS_PAM_AUTHENTICATE)) { + if (may_do_passkey_auth(pctx, pd)) { + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + ret = passkey_kerberos(pctx, preq->pd, preq); + goto done; + } else if ((sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY) || + (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_EMPTY)) { + ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); + goto done; + } + } + } +#endif /* BUILD_PASSKEY */ + + ret = pam_check_user_search(preq); + +done: + pam_check_user_done(preq, ret); +} + +static void pam_check_user_search_next(struct tevent_req *req); +static void pam_check_user_search_lookup(struct tevent_req *req); +static void pam_check_user_search_done(struct pam_auth_req *preq, int ret, + struct cache_req_result *result); + +/* lookup the user uid from the cache first, + * then we'll refresh initgroups if needed */ +int pam_check_user_search(struct pam_auth_req *preq) +{ + struct tevent_req *dpreq; + struct cache_req_data *data; + + data = cache_req_data_name(preq, + CACHE_REQ_INITGROUPS, + preq->pd->logon_name); + if (data == NULL) { + return ENOMEM; + } + + cache_req_data_set_bypass_cache(data, false); + cache_req_data_set_bypass_dp(data, true); + cache_req_data_set_requested_domains(data, preq->pd->requested_domains); + + dpreq = cache_req_send(preq, + preq->cctx->rctx->ev, + preq->cctx->rctx, + preq->cctx->rctx->ncache, + 0, + preq->req_dom_type, + NULL, + data); + if (!dpreq) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Out of memory sending data provider request\n"); + return ENOMEM; + } + + tevent_req_set_callback(dpreq, pam_check_user_search_next, preq); + + /* tell caller we are in an async call */ + return EAGAIN; +} + +static void pam_check_user_search_next(struct tevent_req *req) +{ + struct pam_auth_req *preq; + struct pam_ctx *pctx; + struct cache_req_result *result = NULL; + struct cache_req_data *data; + struct tevent_req *dpreq; + int ret; + + preq = tevent_req_callback_data(req, struct pam_auth_req); + pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + ret = cache_req_single_domain_recv(preq, req, &result); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Cache lookup failed, trying to get fresh " + "data from the backend.\n"); + } + + DEBUG(SSSDBG_TRACE_ALL, "PAM initgroups scheme [%s].\n", + pam_initgroup_enum_to_string(pctx->initgroups_scheme)); + + if (ret == EOK) { + bool user_has_session = false; + + if (pctx->initgroups_scheme == PAM_INITGR_NO_SESSION) { + uid_t uid = ldb_msg_find_attr_as_uint64(result->msgs[0], + SYSDB_UIDNUM, 0); + if (!uid) { + DEBUG(SSSDBG_CRIT_FAILURE, "A user with no UID?\n"); + talloc_zfree(preq->cctx); + return; + } + + /* If a user already has a session on the system, we take the + * cache for granted and do not force an online lookup. This is + * because in most cases the user is just trying to authenticate + * but not create a new session (sudo, lockscreen, polkit, etc.) + * An online refresh in this situation would just delay operations + * without providing any useful additional information. + */ + (void)check_if_uid_is_active(uid, &user_has_session); + + DEBUG(SSSDBG_TRACE_ALL, "Found %s session for uid %"SPRIuid".\n", + user_has_session ? "a" : "no", uid); + } + + /* The initgr cache is used to make sure that during a single PAM + * session (auth, acct_mgtm, ....) the backend is contacted only + * once. logon_name is the name provided by the PAM client and + * will not be modified during the request, so it makes sense to + * use it here instead od the pd->user. + */ + ret = pam_initgr_check_timeout(pctx->id_table, preq->pd->logon_name); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Could not look up initgroup timeout\n"); + } + + if ((ret == EOK) || user_has_session + || pctx->initgroups_scheme == PAM_INITGR_NEVER) { + DEBUG(SSSDBG_TRACE_ALL, "No new initgroups needed because:\n"); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_ALL, "PAM initgr cache still valid.\n"); + } else if (user_has_session) { + DEBUG(SSSDBG_TRACE_ALL, "there is a active session for " + "user [%s].\n", preq->pd->logon_name); + } else if (pctx->initgroups_scheme == PAM_INITGR_NEVER) { + DEBUG(SSSDBG_TRACE_ALL, "initgroups scheme is 'never'.\n"); + } + pam_check_user_search_done(preq, EOK, result); + return; + } + } + + /* If we get here it means the user was not found or does not have a + * session, or initgr has not been cached before, so we force a new + * online lookup */ + data = cache_req_data_name(preq, + CACHE_REQ_INITGROUPS, + preq->pd->logon_name); + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory\n"); + talloc_zfree(preq->cctx); + return; + } + cache_req_data_set_bypass_cache(data, true); + cache_req_data_set_bypass_dp(data, false); + cache_req_data_set_requested_domains(data, preq->pd->requested_domains); + + dpreq = cache_req_send(preq, + preq->cctx->rctx->ev, + preq->cctx->rctx, + preq->cctx->rctx->ncache, + 0, + preq->req_dom_type, + NULL, + data); + if (!dpreq) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Out of memory sending data provider request\n"); + talloc_zfree(preq->cctx); + return; + } + + tevent_req_set_callback(dpreq, pam_check_user_search_lookup, preq); +} + +static void pam_check_user_search_lookup(struct tevent_req *req) +{ + struct cache_req_result *result; + struct pam_auth_req *preq; + int ret; + + preq = tevent_req_callback_data(req, struct pam_auth_req); + + ret = cache_req_single_domain_recv(preq, req, &result); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Fatal error, killing connection!\n"); + talloc_zfree(preq->cctx); + return; + } + + pam_check_user_search_done(preq, ret, result); +} + +static void pam_check_user_search_done(struct pam_auth_req *preq, int ret, + struct cache_req_result *result) +{ + struct pam_ctx *pctx; + + pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + if (ret == EOK) { + preq->user_obj = result->msgs[0]; + pd_set_primary_name(preq->user_obj, preq->pd); + preq->domain = result->domain; + + ret = pam_initgr_cache_set(pctx->rctx->ev, + pctx->id_table, + preq->pd->logon_name, + pctx->id_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not save initgr timestamp." + "Proceeding with PAM actions\n"); + } + + pam_dom_forwarder(preq); + } + + ret = pam_check_user_done(preq, ret); + if (ret != EOK) { + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } +} + +int pam_check_user_done(struct pam_auth_req *preq, int ret) +{ + switch (ret) { + case EOK: + break; + + case EAGAIN: + /* performing async request, just return */ + break; + + case ENOENT: + preq->pd->pam_status = PAM_USER_UNKNOWN; + pam_reply(preq); + break; + + case ERR_P11_PIN_LOCKED: + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + break; + + case ERR_NO_CREDS: + preq->pd->pam_status = PAM_CRED_INSUFFICIENT; + pam_reply(preq); + break; + + default: + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + break; + } + + return EOK; +} + +static errno_t pam_is_last_online_login_fresh(struct sss_domain_info *domain, + const char* user, + int cached_auth_timeout, + bool *_result) +{ + errno_t ret; + bool result = true; + uint64_t last_login; + + ret = pam_get_last_online_auth_with_curr_token(domain, user, &last_login); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sysdb_get_last_online_auth_with_curr_token failed: %s:[%d]\n", + sss_strerror(ret), ret); + goto done; + } + + result = time(NULL) < (last_login + cached_auth_timeout); + ret = EOK; + +done: + if (ret == EOK) { + *_result = result; + } + return ret; +} + +static bool pam_is_authtok_cachable(struct sss_auth_token *authtok) +{ + enum sss_authtok_type type; + bool cachable = false; + + type = sss_authtok_get_type(authtok); + if (type == SSS_AUTHTOK_TYPE_PASSWORD) { + cachable = true; + } else { + DEBUG(SSSDBG_TRACE_LIBS, "Authentication token can't be cached\n"); + } + + return cachable; +} + +static bool pam_can_user_cache_auth(struct sss_domain_info *domain, + int pam_cmd, + struct sss_auth_token *authtok, + const char* user, + bool cached_auth_failed) +{ + errno_t ret; + bool result = false; + + if (cached_auth_failed) { + /* Do not retry indefinitely */ + return false; + } + + if (!domain->cache_credentials || domain->cached_auth_timeout <= 0) { + return false; + } + + if (pam_cmd == SSS_PAM_PREAUTH + || (pam_cmd == SSS_PAM_AUTHENTICATE + && pam_is_authtok_cachable(authtok))) { + + ret = pam_is_last_online_login_fresh(domain, user, + domain->cached_auth_timeout, + &result); + if (ret != EOK) { + /* non-critical, consider fail as 'non-fresh value' */ + DEBUG(SSSDBG_MINOR_FAILURE, + "pam_is_last_online_login_fresh failed: %s:[%d]\n", + sss_strerror(ret), ret); + } + } + + return result; +} + +static void pam_dom_forwarder(struct pam_auth_req *preq) +{ + TALLOC_CTX *tmp_ctx = NULL; + int ret; + struct pam_ctx *pctx = + talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + const char *cert_user; + struct ldb_result *cert_user_objs; + bool sc_auth; + bool passkey_auth; + size_t c; + char *local_policy = NULL; + bool found = false; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return; + } + + if (!preq->pd->domain) { + preq->pd->domain = preq->domain->name; + } + + /* Untrusted users can access only public domains. */ + if (!preq->is_uid_trusted && + !is_domain_public(preq->pd->domain, pctx->public_domains, + pctx->public_domains_count)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Untrusted user %"SPRIuid" cannot access non-public domain %s.\n", + client_euid(preq->cctx->creds), preq->pd->domain); + preq->pd->pam_status = PAM_PERM_DENIED; + pam_reply(preq); + return; + } + + /* skip this domain if not requested and the user is trusted + * as untrusted users can't request a domain */ + if (preq->is_uid_trusted && + !is_domain_requested(preq->pd, preq->pd->domain)) { + preq->pd->pam_status = PAM_USER_UNKNOWN; + pam_reply(preq); + return; + } + + if (pam_can_user_cache_auth(preq->domain, + preq->pd->cmd, + preq->pd->authtok, + preq->pd->user, + preq->cached_auth_failed)) { + preq->use_cached_auth = true; + pam_reply(preq); + return; + } + + /* Skip online auth when local auth policy = only */ +#ifdef BUILD_PASSKEY + if (may_do_cert_auth(pctx, preq->pd) || may_do_passkey_auth(pctx, preq->pd)) { +#else + if (may_do_cert_auth(pctx, preq->pd)) { +#endif /* BUILD_PASSKEY */ + if (preq->domain->name != NULL) { + ret = pam_eval_local_auth_policy(preq->cctx, pctx, preq->pd, preq, + &sc_auth, + &passkey_auth, + &local_policy); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to evaluate local auth policy\n"); + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + return; + } + } + } + + if (may_do_cert_auth(pctx, preq->pd) && preq->cert_list != NULL) { + /* Check if user matches certificate user */ + found = false; + for (preq->current_cert = preq->cert_list; + preq->current_cert != NULL; + preq->current_cert = sss_cai_get_next(preq->current_cert)) { + + cert_user_objs = sss_cai_get_cert_user_objs(preq->current_cert); + if (cert_user_objs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Unexpected missing certificate user, " + "trying next certificate.\n"); + continue; + } + + for (c = 0; c < cert_user_objs->count; c++) { + cert_user = ldb_msg_find_attr_as_string(cert_user_objs->msgs[c], + SYSDB_NAME, NULL); + if (cert_user == NULL) { + /* Even if there might be other users mapped to the + * certificate a missing SYSDB_NAME indicates some critical + * condition which justifies that the whole request is aborted + * */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Certificate user object has no name.\n"); + preq->pd->pam_status = PAM_USER_UNKNOWN; + pam_reply(preq); + return; + } + + if (ldb_dn_compare(cert_user_objs->msgs[c]->dn, + preq->user_obj->dn) == 0) { + found = true; + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + ret = sss_authtok_set_sc(preq->pd->authtok, + SSS_AUTHTOK_TYPE_SC_PIN, NULL, 0, + sss_cai_get_token_name(preq->current_cert), 0, + sss_cai_get_module_name(preq->current_cert), 0, + sss_cai_get_key_id(preq->current_cert), 0, + sss_cai_get_label(preq->current_cert), 0); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_authtok_set_sc failed, Smartcard " + "authentication detection might fail in " + "the backend.\n"); + } + + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, + cert_user, + preq->current_cert, + SSS_PAM_CERT_INFO); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + } + + } + } + } + + if (found) { + if (local_policy != NULL && strcasecmp(local_policy, "only") == 0) { + talloc_free(tmp_ctx); + DEBUG(SSSDBG_IMPORTANT_INFO, + "Local auth only set and matching certificate was found, " + "skipping online auth\n"); + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + preq->pd->pam_status = PAM_SUCCESS; + } else if (preq->pd->cmd == SSS_PAM_AUTHENTICATE + && IS_SC_AUTHTOK(preq->pd->authtok) + && (preq->cert_auth_local + || preq->initial_cert_auth_successful)) { + preq->pd->pam_status = PAM_SUCCESS; + preq->callback = pam_reply; + } + + pam_reply(preq); + return; + } + + /* We are done if we do not have to call the backend */ + if (preq->pd->cmd == SSS_PAM_AUTHENTICATE + && preq->cert_auth_local) { + preq->pd->pam_status = PAM_SUCCESS; + preq->callback = pam_reply; + pam_reply(preq); + return; + } + } else { + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + DEBUG(SSSDBG_TRACE_FUNC, + "User and certificate user do not match, " + "continue with other authentication methods.\n"); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "User and certificate user do not match.\n"); + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + return; + } + } + } + + if (local_policy != NULL && strcasecmp(local_policy, "only") == 0) { + talloc_free(tmp_ctx); + DEBUG(SSSDBG_IMPORTANT_INFO, "Local auth only set, skipping online auth\n"); + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + preq->pd->pam_status = PAM_SUCCESS; + } else if (preq->pd->cmd == SSS_PAM_AUTHENTICATE && IS_SC_AUTHTOK(preq->pd->authtok)) { + /* Trigger offline smartcardcard autheitcation */ + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + + pam_reply(preq); + return; + } + + preq->callback = pam_reply; + ret = pam_dp_send_req(preq); + DEBUG(SSSDBG_CONF_SETTINGS, "pam_dp_send_req returned %d\n", ret); + + talloc_free(tmp_ctx); + + if (ret != EOK) { + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } +} + +static int pam_cmd_authenticate(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_authenticate\n"); + return pam_forwarder(cctx, SSS_PAM_AUTHENTICATE); +} + +static int pam_cmd_setcred(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_setcred\n"); + return pam_forwarder(cctx, SSS_PAM_SETCRED); +} + +static int pam_cmd_acct_mgmt(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_acct_mgmt\n"); + return pam_forwarder(cctx, SSS_PAM_ACCT_MGMT); +} + +static int pam_cmd_open_session(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_open_session\n"); + return pam_forwarder(cctx, SSS_PAM_OPEN_SESSION); +} + +static int pam_cmd_close_session(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_close_session\n"); + return pam_forwarder(cctx, SSS_PAM_CLOSE_SESSION); +} + +static int pam_cmd_chauthtok(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_chauthtok\n"); + return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK); +} + +static int pam_cmd_chauthtok_prelim(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_chauthtok_prelim\n"); + return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK_PRELIM); +} + +static int pam_cmd_preauth(struct cli_ctx *cctx) +{ + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_preauth\n"); + return pam_forwarder(cctx, SSS_PAM_PREAUTH); +} + +static int pam_cmd_passkey_preauth(struct cli_ctx *cctx) +{ + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_passkey_preauth\n"); + return pam_forwarder(cctx, SSS_PAM_PASSKEY_PREAUTH); +} + +struct cli_protocol_version *register_cli_protocol_version(void) +{ + static struct cli_protocol_version pam_cli_protocol_version[] = { + {3, "2009-09-14", "make cli_pid mandatory"}, + {2, "2009-05-12", "new format "}, + {1, "2008-09-05", "initial version, \\0 terminated strings"}, + {0, NULL, NULL} + }; + + return pam_cli_protocol_version; +} + +struct sss_cmd_table *get_pam_cmds(void) +{ + static struct sss_cmd_table sss_cmds[] = { + {SSS_GET_VERSION, sss_cmd_get_version}, + {SSS_PAM_AUTHENTICATE, pam_cmd_authenticate}, + {SSS_PAM_SETCRED, pam_cmd_setcred}, + {SSS_PAM_ACCT_MGMT, pam_cmd_acct_mgmt}, + {SSS_PAM_OPEN_SESSION, pam_cmd_open_session}, + {SSS_PAM_CLOSE_SESSION, pam_cmd_close_session}, + {SSS_PAM_CHAUTHTOK, pam_cmd_chauthtok}, + {SSS_PAM_CHAUTHTOK_PRELIM, pam_cmd_chauthtok_prelim}, + {SSS_PAM_PREAUTH, pam_cmd_preauth}, + {SSS_PAM_PASSKEY_PREAUTH, pam_cmd_passkey_preauth}, + {SSS_GSSAPI_INIT, pam_cmd_gssapi_init}, + {SSS_GSSAPI_SEC_CTX, pam_cmd_gssapi_sec_ctx}, + {SSS_CLI_NULL, NULL} + }; + + return sss_cmds; +} + +errno_t +pam_set_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *username, + uint64_t value) +{ + TALLOC_CTX *tmp_ctx; + struct sysdb_attrs *attrs; + int ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + attrs = sysdb_new_attrs(tmp_ctx); + if (attrs == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_time_t(attrs, + SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, + value); + if (ret != EOK) { goto done; } + + ret = sysdb_set_user_attr(domain, username, attrs, SYSDB_MOD_REP); + if (ret != EOK) { goto done; } + +done: + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, sss_strerror(ret)); + } + + talloc_zfree(tmp_ctx); + return ret; +} + +static errno_t +pam_null_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *username) +{ + return pam_set_last_online_auth_with_curr_token(domain, username, 0); +} + +static errno_t +pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *name, + uint64_t *_value) +{ + TALLOC_CTX *tmp_ctx = NULL; + const char *attrs[] = { SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, NULL }; + struct ldb_message *ldb_msg; + uint64_t value = 0; + errno_t ret; + + if (name == NULL || *name == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); + ret = EINVAL; + goto done; + } + + if (domain->sysdb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing sysdb db context.\n"); + ret = EINVAL; + goto done; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_user_by_name(tmp_ctx, domain, name, attrs, &ldb_msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_search_user_by_name failed [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + + /* Check offline_auth_cache_timeout */ + value = ldb_msg_find_attr_as_uint64(ldb_msg, + SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, + 0); + ret = EOK; + +done: + if (ret == EOK) { + *_value = value; + } + + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/responder/pam/pamsrv_passkey.c b/src/responder/pam/pamsrv_passkey.c index 83f36793fe0..c40515633b3 100644 --- a/src/responder/pam/pamsrv_passkey.c +++ b/src/responder/pam/pamsrv_passkey.c @@ -1,1463 +1,1879 @@ -/* - SSSD - - PAM Responder - passkey related requests - - Copyright (C) Justin Stephenson 2022 - - 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 . -*/ - -#include "util/child_common.h" -#include "util/authtok.h" -#include "db/sysdb.h" -#include "db/sysdb_passkey_user_verification.h" -#include "responder/pam/pamsrv.h" - -#include "responder/pam/pamsrv_passkey.h" - -struct pam_passkey_verification_enum_str { - enum passkey_user_verification verification; - const char *option; -}; - -struct pam_passkey_table_data { - hash_table_t *table; - char *key; - struct pk_child_user_data *data; -}; - -struct pam_passkey_verification_enum_str pam_passkey_verification_enum_str[] = { - { PAM_PASSKEY_VERIFICATION_ON, "on" }, - { PAM_PASSKEY_VERIFICATION_OFF, "off" }, - { PAM_PASSKEY_VERIFICATION_OMIT, "unset" }, - { PAM_PASSKEY_VERIFICATION_INVALID, NULL } -}; - -#define PASSKEY_PREFIX "passkey:" -#define USER_VERIFICATION "user_verification=" -#define USER_VERIFICATION_LEN (sizeof(USER_VERIFICATION) -1) - -const char *pam_passkey_verification_enum_to_string(enum passkey_user_verification verification) -{ - size_t c; - - for (c = 0 ; pam_passkey_verification_enum_str[c].option != NULL; c++) { - if (pam_passkey_verification_enum_str[c].verification == verification) { - return pam_passkey_verification_enum_str[c].option; - } - } - - return "(NULL)"; -} - -struct passkey_ctx { - struct pam_ctx *pam_ctx; - struct tevent_context *ev; - struct pam_data *pd; - struct pam_auth_req *preq; -}; - -void pam_forwarder_passkey_cb(struct tevent_req *req); - -errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, - struct pk_child_user_data *pk_data, - bool kerberos_pa, - char **_result_kh, - char **_result_ph); - -struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - struct passkey_ctx *pctx); -void pam_passkey_get_user_done(struct tevent_req *req); -void pam_passkey_get_mapping_done(struct tevent_req *req); -errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, - struct tevent_req *req, - struct cache_req_result **_result); - -struct passkey_get_mapping_state { - struct pam_data *pd; - struct cache_req_result *result; -}; - -void passkey_kerberos_cb(struct tevent_req *req) -{ - struct pam_auth_req *preq = tevent_req_callback_data(req, - struct pam_auth_req); - errno_t ret = EOK; - int child_status; - - ret = pam_passkey_auth_recv(req, &child_status); - talloc_free(req); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); - - pam_check_user_search(preq); - -done: - pam_check_user_done(preq, ret); -} - -errno_t passkey_kerberos(struct pam_ctx *pctx, - struct pam_data *pd, - struct pam_auth_req *preq) -{ - errno_t ret; - const char *prompt; - const char *key; - const char *pin; - size_t pin_len; - struct pk_child_user_data *data; - struct tevent_req *req; - int timeout; - char *verify_opts; - bool debug_libfido2; - enum passkey_user_verification verification; - - ret = sss_authtok_get_passkey(preq, preq->pd->authtok, - &prompt, &key, &pin, &pin_len); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failure to get passkey authtok\n"); - return EIO; - } - - if (prompt == NULL || key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "Passkey prompt and key are missing or invalid.\n"); - return EIO; - } - - data = sss_ptr_hash_lookup(pctx->pk_table_data->table, key, - struct pk_child_user_data); - if (data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to lookup passkey authtok\n"); - return EIO; - } - - ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, - &timeout); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - ret = confdb_get_string(pctx->rctx->cdb, preq, CONFDB_MONITOR_CONF_ENTRY, - CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, - &verify_opts); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - /* Always use verification sent from passkey krb5 plugin */ - if (strcasecmp(data->user_verification, "false") == 0) { - verification = PAM_PASSKEY_VERIFICATION_OFF; - } else { - verification = PAM_PASSKEY_VERIFICATION_ON; - } - - ret = confdb_get_bool(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, - &debug_libfido2); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - req = pam_passkey_auth_send(preq->cctx, preq->cctx->ev, timeout, debug_libfido2, - verification, pd, data, true); - if (req == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "passkey auth send failed [%d]: [%s]\n", - ret, sss_strerror(ret)); - goto done; - } - - tevent_req_set_callback(req, passkey_kerberos_cb, preq); - - ret = EAGAIN; - -done: - - return ret; - -} - - -errno_t passkey_local(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - struct pam_ctx *pam_ctx, - struct pam_auth_req *preq, - struct pam_data *pd) -{ - struct tevent_req *req; - struct passkey_ctx *pctx; - errno_t ret; - - pctx = talloc_zero(mem_ctx, struct passkey_ctx); - if (pctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "pctx == NULL\n"); - return ENOMEM; - } - - pctx->pd = pd; - pctx->pam_ctx = pam_ctx; - pctx->ev = ev; - pctx->preq = preq; - - DEBUG(SSSDBG_TRACE_FUNC, "Checking for passkey authentication data\n"); - - req = pam_passkey_get_mapping_send(mem_ctx, pctx->ev, pctx); - if (req == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_get_mapping_send failed.\n"); - ret = ENOMEM; - goto done; - } - - tevent_req_set_callback(req, pam_passkey_get_user_done, pctx); - - ret = EAGAIN; - -done: - if (ret != EAGAIN) { - talloc_free(pctx); - } - - return ret; -} - -struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - struct passkey_ctx *pk_ctx) -{ - - struct passkey_get_mapping_state *state; - struct tevent_req *req; - struct tevent_req *subreq; - int ret; - static const char *attrs[] = { SYSDB_NAME, SYSDB_USER_PASSKEY, NULL }; - - req = tevent_req_create(mem_ctx, &state, struct passkey_get_mapping_state); - if (req == NULL) { - ret = ENOMEM; - goto done; - } - - subreq = cache_req_user_by_name_attrs_send(state, pk_ctx->ev, - pk_ctx->pam_ctx->rctx, - pk_ctx->pam_ctx->rctx->ncache, 0, - pk_ctx->pd->domain, - pk_ctx->pd->user, attrs); - if (subreq == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); - ret = ENOMEM; - goto done; - } - - tevent_req_set_callback(subreq, pam_passkey_get_mapping_done, req); - - return req; - -done: - tevent_req_error(req, ret); - tevent_req_post(req, ev); - - return req; -} - -void pam_passkey_get_mapping_done(struct tevent_req *subreq) -{ - struct cache_req_result *result; - struct tevent_req *req; - struct passkey_get_mapping_state *state; - - errno_t ret; - - req = tevent_req_callback_data(subreq, struct tevent_req); - state = tevent_req_data(req, struct passkey_get_mapping_state); - - ret = cache_req_user_by_name_attrs_recv(state, subreq, &result); - state->result = result; - - talloc_zfree(subreq); - if (ret != EOK) { - tevent_req_error(req, ret); - return; - } - - tevent_req_done(req); - return; -} - -errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, - struct tevent_req *req, - struct cache_req_result **_result) -{ - struct passkey_get_mapping_state *state = NULL; - - state = tevent_req_data(req, struct passkey_get_mapping_state); - - TEVENT_REQ_RETURN_ON_ERROR(req); - - *_result = talloc_steal(mem_ctx, state->result); - return EOK; -} - -errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, - const char *verify_opts, - enum passkey_user_verification *_user_verification) -{ - int ret; - TALLOC_CTX *tmp_ctx; - char **opts; - size_t c; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); - return ENOMEM; - } - - if (verify_opts == NULL) { - ret = EOK; - goto done; - } - - ret = split_on_separator(tmp_ctx, verify_opts, ',', true, true, &opts, - NULL); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d], %s.\n", - ret, sss_strerror(ret)); - goto done; - } - - for (c = 0; opts[c] != NULL; c++) { - if (strncasecmp(opts[c], USER_VERIFICATION, USER_VERIFICATION_LEN) == 0) { - if (strcasecmp("true", &opts[c][USER_VERIFICATION_LEN]) == 0) { - DEBUG(SSSDBG_TRACE_FUNC, "user_verification set to true.\n"); - *_user_verification = PAM_PASSKEY_VERIFICATION_ON; - } else if (strcasecmp("false", &opts[c][USER_VERIFICATION_LEN]) == 0) { - DEBUG(SSSDBG_TRACE_FUNC, "user_verification set to false.\n"); - *_user_verification = PAM_PASSKEY_VERIFICATION_OFF; - } - } else { - DEBUG(SSSDBG_MINOR_FAILURE, - "Unsupported passkey verification option [%s], " \ - "skipping.\n", opts[c]); - } - } - - ret = EOK; - -done: - talloc_free(tmp_ctx); - - return ret; -} - -static errno_t passkey_local_verification(TALLOC_CTX *mem_ctx, - struct passkey_ctx *pctx, - struct confdb_ctx *cdb, - struct sysdb_ctx *sysdb, - const char *domain_name, - struct pk_child_user_data *pk_data, - enum passkey_user_verification *_user_verification, - bool *_debug_libfido2) -{ - TALLOC_CTX *tmp_ctx; - errno_t ret; - const char *verification_from_ldap; - char *verify_opts = NULL; - bool debug_libfido2 = false; - enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - return ENOMEM; - } - - ret = sysdb_domain_get_passkey_user_verification(tmp_ctx, sysdb, domain_name, - &verification_from_ldap); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read passkeyUserVerification from sysdb: [%d]: %s\n", - ret, sss_strerror(ret)); - /* This is expected for AD and LDAP */ - ret = EOK; - goto done; - } - - ret = confdb_get_bool(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, - &debug_libfido2); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - /* If require user verification setting is set in LDAP, use it */ - if (verification_from_ldap != NULL) { - if (strcasecmp(verification_from_ldap, "true") == 0) { - verification = PAM_PASSKEY_VERIFICATION_ON; - } else if (strcasecmp(verification_from_ldap, "false") == 0) { - verification = PAM_PASSKEY_VERIFICATION_OFF; - } - DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification is being enforced from LDAP\n"); - } else { - /* No verification set in LDAP, fallback to local sssd.conf setting */ - ret = confdb_get_string(pctx->pam_ctx->rctx->cdb, tmp_ctx, CONFDB_MONITOR_CONF_ENTRY, - CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, - &verify_opts); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - - ret = read_passkey_conf_verification(tmp_ctx, verify_opts, &verification); - if (ret != EOK) { - DEBUG(SSSDBG_MINOR_FAILURE, "Unable to parse passkey verificaton options.\n"); - /* Continue anyway */ - } - DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification is being enforced from local configuration\n"); - } - DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification setting [%s]\n", - pam_passkey_verification_enum_to_string(verification)); - - *_user_verification = verification; - *_debug_libfido2 = debug_libfido2; - - ret = EOK; - -done: - talloc_free(tmp_ctx); - - return ret; -} - -static bool mapping_is_passkey(TALLOC_CTX *tmp_ctx, - const char *mapping_str) -{ - int ret; - char **mappings; - - if (mapping_str == NULL) { - return false; - } - - ret = split_on_separator(tmp_ctx, (const char *) mapping_str, ':', true, true, - &mappings, NULL); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Incorrectly formatted passkey data [%d]: %s\n", - ret, sss_strerror(ret)); - return false; - } - - if (strcasecmp(mappings[0], "passkey") != 0) { - DEBUG(SSSDBG_TRACE_FUNC, "Mapping data found is not passkey related\n"); - return false; - } - - return true; -} - -errno_t process_passkey_data(TALLOC_CTX *mem_ctx, - struct ldb_message *user_mesg, - const char *domain, - struct pk_child_user_data *_data) -{ - struct ldb_message_element *el; - TALLOC_CTX *tmp_ctx; - int ret; - int num_creds = 0; - char **mappings; - const char **kh_mappings; - const char **public_keys; - const char *domain_name; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ERROR("talloc_new() failed\n"); - return ENOMEM; - } - - el = ldb_msg_find_element(user_mesg, SYSDB_USER_PASSKEY); - if (el == NULL) { - DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found\n"); - ret = ENOENT; - goto done; - } - - kh_mappings = talloc_zero_array(tmp_ctx, const char *, el->num_values + 1); - if (kh_mappings == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); - ret = ENOMEM; - goto done; - } - - public_keys = talloc_zero_array(tmp_ctx, const char *, el->num_values + 1); - if (public_keys == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); - ret = ENOMEM; - goto done; - } - - for (int i = 0; i < el->num_values; i++) { - /* This attribute may contain other mapping data unrelated to passkey. In that case - * let's skip it. For example, AD user altSecurityIdentities may store ssh public key - * or smart card mapping data */ - if ((mapping_is_passkey(tmp_ctx, (const char *)el->values[i].data)) == false) { - continue; - } - ret = split_on_separator(tmp_ctx, (const char *) el->values[i].data, ',', true, true, - &mappings, NULL); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Incorrectly formatted passkey data [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - kh_mappings[num_creds] = talloc_strdup(kh_mappings, mappings[0] + strlen(PASSKEY_PREFIX)); - if (kh_mappings[num_creds] == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup key handle failed.\n"); - ret = ENOMEM; - goto done; - } - - public_keys[num_creds] = talloc_strdup(public_keys, mappings[1]); - if (public_keys[num_creds] == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup public key failed.\n"); - ret = ENOMEM; - goto done; - } - - num_creds++; - } - - if (num_creds == 0) { - ret = ENOENT; - goto done; - } - - domain_name = talloc_strdup(tmp_ctx, domain); - if (domain_name == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup domain failed.\n"); - ret = ENOMEM; - goto done; - } - - _data->domain = talloc_steal(mem_ctx, domain_name); - _data->key_handles = talloc_steal(mem_ctx, kh_mappings); - _data->public_keys = talloc_steal(mem_ctx, public_keys); - _data->num_credentials = num_creds; - - ret = EOK; -done: - talloc_free(tmp_ctx); - - return ret; -} - -void pam_forwarder_passkey_cb(struct tevent_req *req) -{ - struct pam_auth_req *preq = tevent_req_callback_data(req, - struct pam_auth_req); - errno_t ret = EOK; - int child_status; - - ret = pam_passkey_auth_recv(req, &child_status); - talloc_free(req); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - preq->pd->passkey_local_done = true; - - DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); - preq->pd->pam_status = PAM_SUCCESS; - pam_reply(preq); - - return; - -done: - pam_check_user_done(preq, ret); -} - -void pam_passkey_get_user_done(struct tevent_req *req) -{ - int ret; - struct passkey_ctx *pctx; - bool debug_libfido2 = false; - char *domain_name = NULL; - int timeout; - struct cache_req_result *result = NULL; - struct pk_child_user_data *pk_data = NULL; - enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; - - pctx = tevent_req_callback_data(req, struct passkey_ctx); - - ret = pam_passkey_get_mapping_recv(pctx, req, &result); - talloc_zfree(req); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_name_attrs_recv failed [%d]: %s.\n", - ret, sss_strerror(ret)); - goto done; - } - - if (result == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "cache req result == NULL\n"); - ret = ENOMEM; - goto done; - } - - pk_data = talloc_zero(pctx, struct pk_child_user_data); - if (!pk_data) { - DEBUG(SSSDBG_CRIT_FAILURE, "pk_data == NULL\n"); - ret = ENOMEM; - goto done; - } - - /* Use dns_name for AD/IPA - for LDAP fallback to domain->name */ - if (result->domain != NULL) { - domain_name = result->domain->dns_name; - if (domain_name == NULL) { - domain_name = result->domain->name; - } - } - - if (domain_name == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Invalid or missing domain name\n"); - ret = EIO; - goto done; - } - - /* Get passkey data */ - DEBUG(SSSDBG_TRACE_ALL, "Processing passkey data\n"); - ret = process_passkey_data(pk_data, result->msgs[0], domain_name, pk_data); - if (ret != EOK) { - DEBUG(SSSDBG_TRACE_FUNC, - "process_passkey_data failed: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - /* timeout */ - ret = confdb_get_int(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, - &timeout); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - ret = passkey_local_verification(pctx, pctx, pctx->pam_ctx->rctx->cdb, - result->domain->sysdb, result->domain->dns_name, - pk_data, &verification, &debug_libfido2); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to check passkey verification [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - /* Preauth respond with prompt_pin true or false based on user verification */ - if (pctx->pd->cmd == SSS_PAM_PREAUTH) { - const char *prompt_pin = verification == PAM_PASSKEY_VERIFICATION_OFF ? "false" : "true"; - - ret = pam_add_response(pctx->pd, SSS_PAM_PASSKEY_INFO, strlen(prompt_pin) + 1, - (const uint8_t *) prompt_pin); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed. [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - pctx->pd->pam_status = PAM_SUCCESS; - pam_reply(pctx->preq); - talloc_free(pk_data); - return; - } - - req = pam_passkey_auth_send(pctx, pctx->ev, timeout, debug_libfido2, - verification, pctx->pd, pk_data, false); - if (req == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_send failed [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - tevent_req_set_callback(req, pam_forwarder_passkey_cb, pctx->preq); - -done: - if (pk_data != NULL) { - talloc_free(pk_data); - } - - if (ret == ENOENT) { - /* No passkey data, continue through to typical auth flow */ - DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found, skipping passkey auth\n"); - pctx->preq->passkey_data_exists = false; - pam_check_user_search(pctx->preq); - } else if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Unexpected passkey error [%d]: %s.\n", - ret, sss_strerror(ret)); - pctx->preq->passkey_data_exists = false; - pctx->preq->pd->pam_status = PAM_SYSTEM_ERR; - pam_reply(pctx->preq); - } - - return; -} - - -struct pam_passkey_auth_send_state { - struct pam_data *pd; - struct tevent_context *ev; - struct tevent_timer *timeout_handler; - struct sss_child_ctx_old *child_ctx; - struct child_io_fds *io; - const char *logfile; - const char **extra_args; - char *verify_opts; - int timeout; - int child_status; - bool kerberos_pa; -}; - -static errno_t passkey_child_exec(struct tevent_req *req); -static void pam_passkey_auth_done(int child_status, - struct tevent_signal *sige, - void *pvt); - -static int pin_destructor(void *ptr) -{ - uint8_t *pin = talloc_get_type(ptr, uint8_t); - if (pin == NULL) return EOK; - - sss_erase_talloc_mem_securely(pin); - - return EOK; -} - -errno_t get_passkey_child_write_buffer(TALLOC_CTX *mem_ctx, - struct pam_data *pd, - uint8_t **_buf, size_t *_len) -{ - int ret; - uint8_t *buf; - size_t len; - const char *pin = NULL; - - if (pd == NULL || pd->authtok == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing authtok.\n"); - return EINVAL; - } - - if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY || - sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { - ret = sss_authtok_get_passkey_pin(pd->authtok, &pin, &len); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_passkey_pin failed [%d]: %s\n", - ret, sss_strerror(ret)); - return ret; - } - - if (pin == NULL || len == 0) { - DEBUG(SSSDBG_OP_FAILURE, "Missing PIN.\n"); - return EINVAL; - } - - buf = talloc_size(mem_ctx, len); - if (buf == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); - return ENOMEM; - } - - talloc_set_destructor((void *) buf, pin_destructor); - - safealign_memcpy(buf, pin, len, NULL); - } else { - DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported authtok type [%d].\n", - sss_authtok_get_type(pd->authtok)); - return EINVAL; - } - - *_len = len; - *_buf = buf; - - return EOK; -} - -static void pam_passkey_child_read_data(struct tevent_req *subreq) -{ - uint8_t *buf; - ssize_t buf_len; - char *str; - struct tevent_req *req = tevent_req_callback_data(subreq, - struct tevent_req); - struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); - int ret; - - ret = read_pipe_recv(subreq, state, &buf, &buf_len); - talloc_zfree(subreq); - if (ret != EOK) { - tevent_req_error(req, ret); - return; - } - - str = malloc(sizeof(char) * buf_len); - if (str == NULL) { - return; - } - - snprintf(str, buf_len, "%s", buf); - - sss_authtok_set_passkey_reply(state->pd->authtok, str, 0); - - free(str); - - tevent_req_done(req); - return; -} - -static void passkey_child_write_done(struct tevent_req *subreq) -{ - struct tevent_req *req = tevent_req_callback_data(subreq, - struct tevent_req); - struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); - - int ret; - - DEBUG(SSSDBG_TRACE_LIBS, "Sending passkey data complete\n"); - - ret = write_pipe_recv(subreq); - talloc_zfree(subreq); - if (ret != EOK) { - tevent_req_error(req, ret); - return; - } - - PIPE_FD_CLOSE(state->io->write_to_child_fd); - - if (state->kerberos_pa) { - /* Read data back from passkey child */ - subreq = read_pipe_send(state, state->ev, state->io->read_from_child_fd); - if (subreq == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "read_pipe_send failed.\n"); - return; - } - - tevent_req_set_callback(subreq, pam_passkey_child_read_data, req); - } -} - -errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, - struct pk_child_user_data *pk_data, - bool kerberos_pa, - char **_result_kh, - char **_result_pk) -{ - errno_t ret; - char *result_kh = NULL; - char *result_pk = NULL; - - result_kh = talloc_strdup(mem_ctx, pk_data->key_handles[0]); - if (!kerberos_pa) { - result_pk = talloc_strdup(mem_ctx, pk_data->public_keys[0]); - } - - for (int i = 1; i < pk_data->num_credentials; i++) { - result_kh = talloc_strdup_append(result_kh, ","); - if (result_kh == NULL) { - ret = ENOMEM; - goto done; - } - - result_kh = talloc_strdup_append(result_kh, pk_data->key_handles[i]); - if (result_kh == NULL) { - ret = ENOMEM; - goto done; - } - - if (!kerberos_pa) { - result_pk = talloc_strdup_append(result_pk, ","); - if (result_pk == NULL) { - ret = ENOMEM; - goto done; - } - - result_pk = talloc_strdup_append(result_pk, pk_data->public_keys[i]); - if (result_kh == NULL || result_pk == NULL) { - ret = ENOMEM; - goto done; - } - } - } - - *_result_kh = result_kh; - *_result_pk = result_pk; - - ret = EOK; -done: - return ret; -} - -struct tevent_req * -pam_passkey_auth_send(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - int timeout, - bool debug_libfido2, - enum passkey_user_verification verification, - struct pam_data *pd, - struct pk_child_user_data *pk_data, - bool kerberos_pa) -{ - struct tevent_req *req; - struct pam_passkey_auth_send_state *state; - size_t arg_c = 0; - char *result_kh; - char *result_pk; - int num_args; - int ret; - - req = tevent_req_create(mem_ctx, &state, struct pam_passkey_auth_send_state); - if (req == NULL) { - return NULL; - } - - state->pd = pd; - state->ev = ev; - - state->timeout = timeout; - state->kerberos_pa = kerberos_pa; - state->logfile = PASSKEY_CHILD_LOG_FILE; - state->io = talloc(state, struct child_io_fds); - if (state->io == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc child fds failed.\n"); - ret = ENOMEM; - goto done; - } - state->io->write_to_child_fd = -1; - state->io->read_from_child_fd = -1; - talloc_set_destructor((void *) state->io, child_io_destructor); - - num_args = 11; - state->extra_args = talloc_zero_array(state, const char *, num_args + 1); - if (state->extra_args == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); - ret = ENOMEM; - goto done; - } - - switch (verification) { - case PAM_PASSKEY_VERIFICATION_ON: - state->extra_args[arg_c++] = "--user-verification=true"; - DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification true\n"); - break; - case PAM_PASSKEY_VERIFICATION_OFF: - state->extra_args[arg_c++] = "--user-verification=false"; - DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification false\n"); - break; - default: - DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification unset\n"); - break; - } - - ret = pam_passkey_concatenate_keys(state, pk_data, state->kerberos_pa, - &result_kh, &result_pk); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_concatenate keys failed - [%d]: [%s]\n", - ret, sss_strerror(ret)); - goto done; - } - - - if (state->kerberos_pa) { - state->extra_args[arg_c++] = pk_data->crypto_challenge; - state->extra_args[arg_c++] = "--cryptographic-challenge"; - state->extra_args[arg_c++] = result_kh; - state->extra_args[arg_c++] = "--key-handle"; - state->extra_args[arg_c++] = pk_data->domain; - state->extra_args[arg_c++] = "--domain"; - state->extra_args[arg_c++] = "--get-assert"; - } else { - state->extra_args[arg_c++] = result_pk; - state->extra_args[arg_c++] = "--public-key"; - state->extra_args[arg_c++] = result_kh; - state->extra_args[arg_c++] = "--key-handle"; - state->extra_args[arg_c++] = pk_data->domain; - state->extra_args[arg_c++] = "--domain"; - state->extra_args[arg_c++] = state->pd->user; - state->extra_args[arg_c++] = "--username"; - state->extra_args[arg_c++] = "--authenticate"; - } - - ret = passkey_child_exec(req); - -done: - if (ret == EOK) { - tevent_req_done(req); - tevent_req_post(req, ev); - } else if (ret != EAGAIN) { - tevent_req_error(req, ret); - tevent_req_post(req, ev); - } - - return req; -} - -static void -passkey_child_timeout(struct tevent_context *ev, - struct tevent_timer *te, - struct timeval tv, void *pvt) -{ - struct tevent_req *req = - talloc_get_type(pvt, struct tevent_req); - struct pam_passkey_auth_send_state *state = - tevent_req_data(req, struct pam_passkey_auth_send_state); - - DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for passkey child, " - "consider increasing passkey_child_timeout\n"); - child_handler_destroy(state->child_ctx); - state->child_ctx = NULL; - state->child_status = ETIMEDOUT; - tevent_req_error(req, ERR_PASSKEY_CHILD_TIMEOUT); -} - -static errno_t passkey_child_exec(struct tevent_req *req) -{ - struct pam_passkey_auth_send_state *state; - struct tevent_req *subreq; - int pipefd_from_child[2] = PIPE_INIT; - int pipefd_to_child[2] = PIPE_INIT; - pid_t child_pid; - uint8_t *write_buf = NULL; - size_t write_buf_len = 0; - struct timeval tv; - int ret; - - state = tevent_req_data(req, struct pam_passkey_auth_send_state); - - ret = pipe(pipefd_from_child); - if (ret == -1) { - ret = errno; - DEBUG(SSSDBG_CRIT_FAILURE, - "pipe failed [%d][%s].\n", ret, strerror(ret)); - goto done; - } - ret = pipe(pipefd_to_child); - if (ret == -1) { - ret = errno; - DEBUG(SSSDBG_CRIT_FAILURE, - "pipe failed [%d][%s].\n", ret, strerror(ret)); - goto done; - } - - child_pid = fork(); - if (child_pid == 0) { /* child */ - exec_child_ex(state, pipefd_to_child, pipefd_from_child, - PASSKEY_CHILD_PATH, state->logfile, state->extra_args, - false, STDIN_FILENO, STDOUT_FILENO); - /* We should never get here */ - ret = errno; - DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec passkey child\n"); - goto done; - } else if (child_pid > 0) { /* parent */ - state->io->read_from_child_fd = pipefd_from_child[0]; - PIPE_FD_CLOSE(pipefd_from_child[1]); - sss_fd_nonblocking(state->io->read_from_child_fd); - - state->io->write_to_child_fd = pipefd_to_child[1]; - PIPE_FD_CLOSE(pipefd_to_child[0]); - sss_fd_nonblocking(state->io->write_to_child_fd); - - /* Set up SIGCHLD handler */ - if (state->kerberos_pa) { - ret = child_handler_setup(state->ev, child_pid, NULL, req, &state->child_ctx); - } else { - ret = child_handler_setup(state->ev, child_pid, - pam_passkey_auth_done, req, - &state->child_ctx); - } - - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", - ret, sss_strerror(ret)); - ret = ERR_PASSKEY_CHILD; - goto done; - } - - /* Set up timeout handler */ - tv = tevent_timeval_current_ofs(state->timeout, 0); - state->timeout_handler = tevent_add_timer(state->ev, req, tv, - passkey_child_timeout, req); - if (state->timeout_handler == NULL) { - ret = ERR_PASSKEY_CHILD; - goto done; - } - - /* PIN is needed */ - if (sss_authtok_get_type(state->pd->authtok) != SSS_AUTHTOK_TYPE_EMPTY) { - ret = get_passkey_child_write_buffer(state, state->pd, &write_buf, - &write_buf_len); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "get_passkey_child_write_buffer failed [%d]: %s.\n", - ret, sss_strerror(ret)); - goto done; - } - } - - if (write_buf_len != 0) { - subreq = write_pipe_send(state, state->ev, write_buf, write_buf_len, - state->io->write_to_child_fd); - if (subreq == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "write_pipe_send failed.\n"); - ret = ERR_PASSKEY_CHILD; - goto done; - } - tevent_req_set_callback(subreq, passkey_child_write_done, req); - } - /* Now either wait for the timeout to fire or the child to finish */ - } else { /* error */ - ret = errno; - DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d][%s].\n", - ret, sss_strerror(ret)); - goto done; - } - - return EAGAIN; - -done: - if (ret != EOK) { - PIPE_CLOSE(pipefd_from_child); - PIPE_CLOSE(pipefd_to_child); - } - - return ret; -} - -errno_t pam_passkey_auth_recv(struct tevent_req *req, - int *child_status) -{ - struct pam_passkey_auth_send_state *state = - tevent_req_data(req, struct pam_passkey_auth_send_state); - - *child_status = state->child_status; - - TEVENT_REQ_RETURN_ON_ERROR(req); - - return EOK; -} - -errno_t decode_pam_passkey_msg(TALLOC_CTX *mem_ctx, - uint8_t *buf, - size_t len, - struct pk_child_user_data **_data) -{ - - size_t p = 0; - size_t pctr = 0; - errno_t ret; - size_t offset; - struct pk_child_user_data *data = NULL; - TALLOC_CTX *tmp_ctx; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - return ENOMEM; - } - - data = talloc_zero(tmp_ctx, struct pk_child_user_data); - if (data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to talloc passkey data.\n"); - ret = ENOMEM; - goto done; - } - - data->user_verification = talloc_strdup(data, (char *) &buf[p]); - if (data->user_verification == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey prompt.\n"); - ret = ENOMEM; - goto done; - } - - offset = strlen(data->user_verification) + 1; - if (offset >= len) { - DEBUG(SSSDBG_OP_FAILURE, "passkey prompt offset failure.\n"); - ret = EIO; - goto done; - } - - data->crypto_challenge = talloc_strdup(data, (char *) &buf[p + offset]); - if (data->crypto_challenge == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey challenge.\n"); - ret = ENOMEM; - goto done; - } - - offset += strlen(data->crypto_challenge) + 1; - if (offset >= len) { - DEBUG(SSSDBG_OP_FAILURE, "passkey challenge offset failure.\n"); - ret = EIO; - goto done; - } - - - data->domain = talloc_strdup(data, (char *) &buf[p] + offset); - if (data->domain == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey domain.\n"); - ret = ENOMEM; - goto done; - } - - offset += strlen(data->domain) + 1; - if (offset >= len) { - DEBUG(SSSDBG_OP_FAILURE, "passkey domain offset failure.\n"); - ret = EIO; - goto done; - } - - SAFEALIGN_COPY_UINT32(&data->num_credentials, &buf[p + offset], &pctr); - size_t list_sz = (size_t) data->num_credentials; - - offset += sizeof(uint32_t); - - data->key_handles = talloc_zero_array(data, const char *, list_sz); - - for (int i = 0; i < list_sz; i++) { - data->key_handles[i] = talloc_strdup(data->key_handles, (char *) &buf[p + offset]); - if (data->key_handles[i] == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey list.\n"); - ret = ENOMEM; - goto done; - } - - offset += strlen(data->key_handles[i]) + 1; - } - - *_data = talloc_steal(mem_ctx, data); - - ret = EOK; -done: - talloc_free(tmp_ctx); - return ret; -} - -errno_t save_passkey_data(TALLOC_CTX *mem_ctx, - struct pam_ctx *pctx, - struct pk_child_user_data *data, - struct pam_auth_req *preq) -{ - char *pk_key; - errno_t ret; - TALLOC_CTX *tmp_ctx; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - return ENOMEM; - } - - /* Passkey data (pk_table_data) is stolen onto client ctx, it will - * be freed when the client closes, and the sss_ptr_hash interface - * takes care of automatically removing it from the hash table then */ - pctx->pk_table_data = talloc_zero(tmp_ctx, struct pam_passkey_table_data); - if (pctx->pk_table_data == NULL) { - return ENOMEM; - } - - if (pctx->pk_table_data->table == NULL) { - pctx->pk_table_data->table = sss_ptr_hash_create(pctx->pk_table_data, - NULL, NULL); - if (pctx->pk_table_data->table == NULL) { - ret = ENOMEM; - goto done; - } - } - - pk_key = talloc_asprintf(tmp_ctx, "%s", data->crypto_challenge); - if (pk_key == NULL) { - ret = ENOMEM; - goto done; - } - - pctx->pk_table_data->key = talloc_strdup(pctx->pk_table_data, pk_key); - if (pctx->pk_table_data->key == NULL) { - ret = ENOMEM; - goto done; - } - - ret = sss_ptr_hash_add(pctx->pk_table_data->table, pk_key, data, - struct pk_child_user_data); - if (ret == EEXIST) { - DEBUG(SSSDBG_TRACE_FUNC, "pk_table key [%s] already exists\n", - pk_key); - goto done; - } else if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Unable to add pk data to hash table " - "[%d]: %s\n", ret, sss_strerror(ret)); - goto done; - } - - talloc_steal(mem_ctx, pctx->pk_table_data); - pctx->pk_table_data->data = talloc_steal(mem_ctx, data); - - ret = EOK; - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t pam_eval_passkey_response(struct pam_ctx *pctx, - struct pam_data *pd, - struct pam_auth_req *preq, - bool *_pk_preauth_done) -{ - struct response_data *pk_resp; - struct pk_child_user_data *pk_data; - errno_t ret; - TALLOC_CTX *tmp_ctx; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - return ENOMEM; - } - - pk_resp = pd->resp_list; - - while (pk_resp != NULL) { - switch (pk_resp->type) { - case SSS_PAM_PASSKEY_KRB_INFO: - if (!pctx->passkey_auth) { - /* Passkey auth is disabled. To avoid passkey prompts appearing, - * don't send SSS_PAM_PASSKEY_KRB_INFO to the client and - * add a dummy response to fallback to normal auth */ - pk_resp->do_not_send_to_client = true; - ret = pam_add_response(pd, SSS_OTP, 0, NULL); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); - goto done; - } - break; - } - ret = decode_pam_passkey_msg(tmp_ctx, pk_resp->data, pk_resp->len, &pk_data); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to decode passkey msg\n"); - ret = EIO; - goto done; - } - - ret = save_passkey_data(preq->cctx, pctx, pk_data, preq); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to save passkey msg\n"); - ret = EIO; - goto done; - } - break; - /* Passkey non-kerberos preauth has already run */ - case SSS_PAM_PASSKEY_INFO: - *_pk_preauth_done = true; - default: - break; - } - pk_resp = pk_resp->next; - } - - ret = EOK; -done: - talloc_free(tmp_ctx); - - return ret; -} - - -static void -pam_passkey_auth_done(int child_status, - struct tevent_signal *sige, - void *pvt) -{ - struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); - - struct pam_passkey_auth_send_state *state = - tevent_req_data(req, struct pam_passkey_auth_send_state); - state->child_status = WEXITSTATUS(child_status); - if (WIFEXITED(child_status)) { - if (WEXITSTATUS(child_status) != 0) { - DEBUG(SSSDBG_OP_FAILURE, - PASSKEY_CHILD_PATH " failed with status [%d]. Check passkey_child" - " logs for more information.\n", - WEXITSTATUS(child_status)); - tevent_req_error(req, ERR_PASSKEY_CHILD); - return; - } - } else if (WIFSIGNALED(child_status)) { - DEBUG(SSSDBG_OP_FAILURE, - PASSKEY_CHILD_PATH " was terminated by signal [%d]. Check passkey_child" - " logs for more information.\n", - WTERMSIG(child_status)); - tevent_req_error(req, ECHILD); - return; - } - - DEBUG(SSSDBG_TRACE_FUNC, "passkey data is valid. Mark done\n"); - - tevent_req_done(req); - return; -} - -bool may_do_passkey_auth(struct pam_ctx *pctx, - struct pam_data *pd) -{ -#ifndef BUILD_PASSKEY - DEBUG(SSSDBG_TRACE_FUNC, "Passkey auth not possible, SSSD built without passkey support!\n"); - return false; -#else - if (!pctx->passkey_auth) { - return false; - } - - if (pd->cmd != SSS_PAM_PREAUTH && pd->cmd != SSS_PAM_AUTHENTICATE) { - return false; - } - - if (pd->service == NULL || *pd->service == '\0') { - return false; - } - - return true; -#endif /* BUILD_PASSKEY */ -} +/* + SSSD + + PAM Responder - passkey related requests + + Copyright (C) Justin Stephenson 2022 + + 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 . +*/ + +#include +#include "util/child_common.h" +#include "util/authtok.h" +#include "db/sysdb.h" +#include "db/sysdb_passkey_user_verification.h" +#include "responder/pam/pamsrv.h" + +#include "responder/pam/pamsrv_passkey.h" + +struct pam_passkey_verification_enum_str { + enum passkey_user_verification verification; + const char *option; +}; + +struct pam_passkey_table_data { + hash_table_t *table; + char *key; + struct pk_child_user_data *data; +}; + +struct pam_passkey_verification_enum_str pam_passkey_verification_enum_str[] = { + { PAM_PASSKEY_VERIFICATION_ON, "on" }, + { PAM_PASSKEY_VERIFICATION_OFF, "off" }, + { PAM_PASSKEY_VERIFICATION_OMIT, "unset" }, + { PAM_PASSKEY_VERIFICATION_INVALID, NULL } +}; + +#define PASSKEY_PREFIX "passkey:" +#define USER_VERIFICATION "user_verification=" +#define USER_VERIFICATION_LEN (sizeof(USER_VERIFICATION) -1) + +const char *pam_passkey_verification_enum_to_string(enum passkey_user_verification verification) +{ + size_t c; + + for (c = 0 ; pam_passkey_verification_enum_str[c].option != NULL; c++) { + if (pam_passkey_verification_enum_str[c].verification == verification) { + return pam_passkey_verification_enum_str[c].option; + } + } + + return "(NULL)"; +} + +struct passkey_ctx { + struct pam_ctx *pam_ctx; + struct tevent_context *ev; + struct pam_data *pd; + struct pam_auth_req *preq; +}; + +void pam_forwarder_passkey_cb(struct tevent_req *req); + +void pam_forwarder_get_devinfo_cb(struct tevent_req *req); +void pam_passkey_get_devinfo(struct tevent_req *req); + +errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, + struct pk_child_user_data *pk_data, + enum passkey_auth_action auth_action, + char **_result_kh, + char **_result_ph); + +struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct passkey_ctx *pctx); +void pam_passkey_get_user_done(struct tevent_req *req); +void pam_passkey_get_mapping_done(struct tevent_req *req); +errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req_result **_result); + +struct passkey_get_mapping_state { + struct pam_data *pd; + struct cache_req_result *result; +}; + +void passkey_kerberos_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + errno_t ret = EOK; + int child_status; + + ret = pam_passkey_auth_recv(req, &child_status); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); + + pam_check_user_search(preq); + +done: + pam_check_user_done(preq, ret); +} + +errno_t passkey_kerberos(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq) +{ + errno_t ret; + const char *prompt; + const char *key; + const char *pin; + size_t pin_len; + struct pk_child_user_data *data; + struct tevent_req *req; + int timeout; + char *verify_opts; + bool debug_libfido2; + enum passkey_user_verification verification; + + ret = sss_authtok_get_passkey(preq, preq->pd->authtok, + &prompt, &key, &pin, &pin_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failure to get passkey authtok\n"); + return EIO; + } + + if (prompt == NULL || key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Passkey prompt and key are missing or invalid.\n"); + return EIO; + } + + data = sss_ptr_hash_lookup(pctx->pk_table_data->table, key, + struct pk_child_user_data); + if (data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to lookup passkey authtok\n"); + return EIO; + } + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_string(pctx->rctx->cdb, preq, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, + &verify_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Always use verification sent from passkey krb5 plugin */ + if (strcasecmp(data->user_verification, "false") == 0) { + verification = PAM_PASSKEY_VERIFICATION_OFF; + } else { + verification = PAM_PASSKEY_VERIFICATION_ON; + } + + ret = confdb_get_bool(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, + &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + req = pam_passkey_auth_send(preq->cctx, preq->cctx->ev, timeout, debug_libfido2, + verification, pd, data, + PASSKEY_KERBEROS_AUTH ); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "passkey auth send failed [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, passkey_kerberos_cb, preq); + + ret = EAGAIN; + +done: + + return ret; + +} + + +errno_t passkey_kerberos_get_devinfo(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq) +{ + errno_t ret; + const char *prompt; + const char *key; + const char *pin; + size_t pin_len; + struct pk_child_user_data *data; + struct tevent_req *req; + int timeout; + char *verify_opts; + bool debug_libfido2; + enum passkey_user_verification verification; + + if (pd->cmd != SSS_PAM_PASSKEY_PREAUTH) { + DEBUG(SSSDBG_OP_FAILURE, "passkey_kerberos_get_devinfo : invalid command\n"); + ret = PAM_SYSTEM_ERR; + goto done; + } + + ret = sss_authtok_get_passkey(preq, preq->pd->authtok, + &prompt, &key, &pin, &pin_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failure to get passkey authtok\n"); + return EIO; + } + + if (prompt == NULL || key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Passkey prompt and key are missing or invalid.\n"); + return EIO; + } + + DEBUG(SSSDBG_TRACE_FUNC, "lookup passkey auth keys: %s\nprompt: %s\n", + key ? key : "NULL", prompt ? prompt : "NULL"); + + data = sss_ptr_hash_lookup(pctx->pk_table_data->table, key, + struct pk_child_user_data); + if (data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to lookup passkey authtok\n"); + return EIO; + } + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_string(pctx->rctx->cdb, preq, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, + &verify_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Always use verification sent from passkey krb5 plugin */ + if (strcasecmp(data->user_verification, "false") == 0) { + verification = PAM_PASSKEY_VERIFICATION_OFF; + } else { + verification = PAM_PASSKEY_VERIFICATION_ON; + } + + ret = confdb_get_bool(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, + &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + req = pam_passkey_auth_send(preq->cctx, preq->cctx->ev, timeout, debug_libfido2, + verification, pd, data, + PASSKEY_GET_DEVINFO); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "passkey auth send failed [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, pam_forwarder_get_devinfo_cb, preq); + + ret = EAGAIN; + +done: + + return ret; + +} + +errno_t passkey_local(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_ctx *pam_ctx, + struct pam_auth_req *preq, + struct pam_data *pd) +{ + struct tevent_req *req; + struct passkey_ctx *pctx; + errno_t ret; + + pctx = talloc_zero(mem_ctx, struct passkey_ctx); + if (pctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pctx == NULL\n"); + return ENOMEM; + } + + pctx->pd = pd; + pctx->pam_ctx = pam_ctx; + pctx->ev = ev; + pctx->preq = preq; + + DEBUG(SSSDBG_TRACE_FUNC, "Checking for passkey authentication data\n"); + + req = pam_passkey_get_mapping_send(mem_ctx, pctx->ev, pctx); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_get_mapping_send failed.\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, pam_passkey_get_user_done, pctx); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + talloc_free(pctx); + } + + return ret; +} + +errno_t passkey_local_get_devinfo(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_ctx *pam_ctx, + struct pam_auth_req *preq, + struct pam_data *pd) +{ + struct tevent_req *req; + struct passkey_ctx *pctx; + errno_t ret; + + pctx = talloc_zero(mem_ctx, struct passkey_ctx); + if (pctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pctx == NULL\n"); + return ENOMEM; + } + + pctx->pd = pd; + pctx->pam_ctx = pam_ctx; + pctx->ev = ev; + pctx->preq = preq; + + if (pctx->pd->cmd != SSS_PAM_PASSKEY_PREAUTH) { + DEBUG(SSSDBG_OP_FAILURE, "passkey_local_get_devinfo : invalid command\n"); + ret = PAM_SYSTEM_ERR; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Checking for passkey authentication data\n"); + + req = pam_passkey_get_mapping_send(mem_ctx, pctx->ev, pctx); + if (req == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "pam_passkey_get_mapping_send failed.\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, pam_passkey_get_devinfo, pctx); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + talloc_free(pctx); + } + + return ret; +} +struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct passkey_ctx *pk_ctx) +{ + + struct passkey_get_mapping_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + int ret; + static const char *attrs[] = { SYSDB_NAME, SYSDB_USER_PASSKEY, NULL }; + + req = tevent_req_create(mem_ctx, &state, struct passkey_get_mapping_state); + if (req == NULL) { + ret = ENOMEM; + goto done; + } + + subreq = cache_req_user_by_name_attrs_send(state, pk_ctx->ev, + pk_ctx->pam_ctx->rctx, + pk_ctx->pam_ctx->rctx->ncache, 0, + pk_ctx->pd->domain, + pk_ctx->pd->user, attrs); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, pam_passkey_get_mapping_done, req); + + return req; + +done: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +void pam_passkey_get_mapping_done(struct tevent_req *subreq) +{ + struct cache_req_result *result; + struct tevent_req *req; + struct passkey_get_mapping_state *state; + + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct passkey_get_mapping_state); + + ret = cache_req_user_by_name_attrs_recv(state, subreq, &result); + state->result = result; + + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req_result **_result) +{ + struct passkey_get_mapping_state *state = NULL; + + state = tevent_req_data(req, struct passkey_get_mapping_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_result = talloc_steal(mem_ctx, state->result); + return EOK; +} + +errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, + const char *verify_opts, + enum passkey_user_verification *_user_verification) +{ + int ret; + TALLOC_CTX *tmp_ctx; + char **opts; + size_t c; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + if (verify_opts == NULL) { + ret = EOK; + goto done; + } + + ret = split_on_separator(tmp_ctx, verify_opts, ',', true, true, &opts, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d], %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + for (c = 0; opts[c] != NULL; c++) { + if (strncasecmp(opts[c], USER_VERIFICATION, USER_VERIFICATION_LEN) == 0) { + if (strcasecmp("true", &opts[c][USER_VERIFICATION_LEN]) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "user_verification set to true.\n"); + *_user_verification = PAM_PASSKEY_VERIFICATION_ON; + } else if (strcasecmp("false", &opts[c][USER_VERIFICATION_LEN]) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "user_verification set to false.\n"); + *_user_verification = PAM_PASSKEY_VERIFICATION_OFF; + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unsupported passkey verification option [%s], " \ + "skipping.\n", opts[c]); + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t passkey_local_verification(TALLOC_CTX *mem_ctx, + struct passkey_ctx *pctx, + struct confdb_ctx *cdb, + struct sysdb_ctx *sysdb, + const char *domain_name, + struct pk_child_user_data *pk_data, + enum passkey_user_verification *_user_verification, + bool *_debug_libfido2) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char *verification_from_ldap; + char *verify_opts = NULL; + bool debug_libfido2 = false; + enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_domain_get_passkey_user_verification(tmp_ctx, sysdb, domain_name, + &verification_from_ldap); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkeyUserVerification from sysdb: [%d]: %s\n", + ret, sss_strerror(ret)); + /* This is expected for AD and LDAP */ + ret = EOK; + goto done; + } + + ret = confdb_get_bool(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, + &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* If require user verification setting is set in LDAP, use it */ + if (verification_from_ldap != NULL) { + if (strcasecmp(verification_from_ldap, "true") == 0) { + verification = PAM_PASSKEY_VERIFICATION_ON; + } else if (strcasecmp(verification_from_ldap, "false") == 0) { + verification = PAM_PASSKEY_VERIFICATION_OFF; + } + DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification is being enforced from LDAP\n"); + } else { + /* No verification set in LDAP, fallback to local sssd.conf setting */ + ret = confdb_get_string(pctx->pam_ctx->rctx->cdb, tmp_ctx, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, + &verify_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + + ret = read_passkey_conf_verification(tmp_ctx, verify_opts, &verification); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to parse passkey verificaton options.\n"); + /* Continue anyway */ + } + DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification is being enforced from local configuration\n"); + } + DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification setting [%s]\n", + pam_passkey_verification_enum_to_string(verification)); + + *_user_verification = verification; + *_debug_libfido2 = debug_libfido2; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static bool mapping_is_passkey(TALLOC_CTX *tmp_ctx, + const char *mapping_str) +{ + int ret; + char **mappings; + + if (mapping_str == NULL) { + return false; + } + + ret = split_on_separator(tmp_ctx, (const char *) mapping_str, ':', true, true, + &mappings, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Incorrectly formatted passkey data [%d]: %s\n", + ret, sss_strerror(ret)); + return false; + } + + if (strcasecmp(mappings[0], "passkey") != 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Mapping data found is not passkey related\n"); + return false; + } + + return true; +} + +errno_t process_passkey_data(TALLOC_CTX *mem_ctx, + struct ldb_message *user_mesg, + const char *domain, + struct pk_child_user_data *_data) +{ + struct ldb_message_element *el; + TALLOC_CTX *tmp_ctx; + int ret; + int num_creds = 0; + char **mappings; + const char **kh_mappings; + const char **public_keys; + const char *domain_name; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ERROR("talloc_new() failed\n"); + return ENOMEM; + } + + el = ldb_msg_find_element(user_mesg, SYSDB_USER_PASSKEY); + if (el == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found\n"); + ret = ENOENT; + goto done; + } + + kh_mappings = talloc_zero_array(tmp_ctx, const char *, el->num_values + 1); + if (kh_mappings == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + public_keys = talloc_zero_array(tmp_ctx, const char *, el->num_values + 1); + if (public_keys == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + for (int i = 0; i < el->num_values; i++) { + /* This attribute may contain other mapping data unrelated to passkey. In that case + * let's skip it. For example, AD user altSecurityIdentities may store ssh public key + * or smart card mapping data */ + if ((mapping_is_passkey(tmp_ctx, (const char *)el->values[i].data)) == false) { + continue; + } + ret = split_on_separator(tmp_ctx, (const char *) el->values[i].data, ',', true, true, + &mappings, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Incorrectly formatted passkey data [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + kh_mappings[num_creds] = talloc_strdup(kh_mappings, mappings[0] + strlen(PASSKEY_PREFIX)); + if (kh_mappings[num_creds] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup key handle failed.\n"); + ret = ENOMEM; + goto done; + } + + public_keys[num_creds] = talloc_strdup(public_keys, mappings[1]); + if (public_keys[num_creds] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup public key failed.\n"); + ret = ENOMEM; + goto done; + } + + num_creds++; + } + + if (num_creds == 0) { + ret = ENOENT; + goto done; + } + + domain_name = talloc_strdup(tmp_ctx, domain); + if (domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup domain failed.\n"); + ret = ENOMEM; + goto done; + } + + _data->domain = talloc_steal(mem_ctx, domain_name); + _data->key_handles = talloc_steal(mem_ctx, kh_mappings); + _data->public_keys = talloc_steal(mem_ctx, public_keys); + _data->num_credentials = num_creds; + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + +void pam_forwarder_passkey_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + errno_t ret = EOK; + int child_status; + + ret = pam_passkey_auth_recv(req, &child_status); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + preq->pd->passkey_local_done = true; + + DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); + preq->pd->pam_status = PAM_SUCCESS; + pam_reply(preq); + + return; + +done: + pam_check_user_done(preq, ret); +} + +#define PASSKEY_PINUV "/var/run/passkey-pinuv" +#define PASSKEY_PINONLY "/var/run/passkey-pinonly" +#define PASSKEY_NODEV "/var/run/passkey-nodev" + +void pam_forwarder_get_devinfo_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + errno_t ret = EOK; + const char* devinfo = "errdev"; + int child_status; + + ret = pam_passkey_auth_recv(req, &child_status); + talloc_free(req); + if (ret != EOK) + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_recv for devinfo failed [%d]: %s\n", + ret, sss_strerror(ret)); + + DEBUG(SSSDBG_TRACE_FUNC, "passkey child devinfo finished with status [%d]\n", child_status); + + if (child_status != 0) { + int fd; + devinfo = "nodev"; + fd = creat(PASSKEY_NODEV, 0000); + close(fd); + } else { + (void)remove (PASSKEY_NODEV); + if (access(PASSKEY_PINUV, F_OK) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "devinfo pinuv file indicator\n"); + devinfo = "pinuv"; + } else if (access(PASSKEY_PINONLY, F_OK) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "devinfo pinonly file indicator\n"); + devinfo = "pinonly"; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "devinfo invalid file indicator\n"); + devinfo = "errdev"; + } + } + + /* remove indicators + * They will be rebuild according the device capabilities + * (see passkey_child) + * during authentication request. + */ + (void)remove (PASSKEY_PINUV); + (void)remove (PASSKEY_PINONLY); + + ret = pam_add_response(preq->pd, SSS_PAM_PASSKEY_DEVINFO, strlen(devinfo) + 1, + (const uint8_t *) devinfo); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "devinfo: pam_add_response failed. [%d]: %s\n", + ret, sss_strerror(ret)); + } + + + preq->pd->pam_status = PAM_SUCCESS; + + pam_reply(preq); + + return; +} + +void pam_passkey_get_user_done(struct tevent_req *req) +{ + int ret; + struct passkey_ctx *pctx; + bool debug_libfido2 = false; + char *domain_name = NULL; + int timeout; + struct cache_req_result *result = NULL; + struct pk_child_user_data *pk_data = NULL; + enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; + + pctx = tevent_req_callback_data(req, struct passkey_ctx); + + ret = pam_passkey_get_mapping_recv(pctx, req, &result); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_name_attrs_recv failed [%d]: %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + if (result == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "cache req result == NULL\n"); + ret = ENOMEM; + goto done; + } + + pk_data = talloc_zero(pctx, struct pk_child_user_data); + if (!pk_data) { + DEBUG(SSSDBG_CRIT_FAILURE, "pk_data == NULL\n"); + ret = ENOMEM; + goto done; + } + + /* Use dns_name for AD/IPA - for LDAP fallback to domain->name */ + if (result->domain != NULL) { + domain_name = result->domain->dns_name; + if (domain_name == NULL) { + domain_name = result->domain->name; + } + } + + if (domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid or missing domain name\n"); + ret = EIO; + goto done; + } + + /* Get passkey data */ + DEBUG(SSSDBG_TRACE_ALL, "Processing passkey data\n"); + ret = process_passkey_data(pk_data, result->msgs[0], domain_name, pk_data); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "process_passkey_data failed: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* timeout */ + ret = confdb_get_int(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = passkey_local_verification(pctx, pctx, pctx->pam_ctx->rctx->cdb, + result->domain->sysdb, result->domain->dns_name, + pk_data, &verification, &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to check passkey verification [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Preauth respond with prompt_pin true or false based on user verification */ + if (pctx->pd->cmd == SSS_PAM_PREAUTH) { + const char *prompt_pin = verification == PAM_PASSKEY_VERIFICATION_OFF ? "false" : "true"; + + ret = pam_add_response(pctx->pd, SSS_PAM_PASSKEY_INFO, strlen(prompt_pin) + 1, + (const uint8_t *) prompt_pin); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed. [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + pctx->pd->pam_status = PAM_SUCCESS; + pam_reply(pctx->preq); + talloc_free(pk_data); + return; + } + + req = pam_passkey_auth_send(pctx, pctx->ev, timeout, debug_libfido2, + verification, pctx->pd, pk_data, + PASSKEY_LOCAL_AUTH); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_send failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, pam_forwarder_passkey_cb, pctx->preq); + +done: + if (pk_data != NULL) { + talloc_free(pk_data); + } + + if (ret == ENOENT) { + /* No passkey data, continue through to typical auth flow */ + DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found, skipping passkey auth\n"); + pctx->preq->passkey_data_exists = false; + pam_check_user_search(pctx->preq); + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected passkey error [%d]: %s.\n", + ret, sss_strerror(ret)); + pctx->preq->passkey_data_exists = false; + pctx->preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(pctx->preq); + } + + return; +} + +void pam_passkey_get_devinfo(struct tevent_req *req) +{ + int ret; + struct passkey_ctx *pctx; + bool debug_libfido2 = false; + char *domain_name = NULL; + int timeout; + struct cache_req_result *result = NULL; + struct pk_child_user_data *pk_data = NULL; + enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; + + + DEBUG(SSSDBG_TRACE_ALL, "pam_passkey_get_devinfo... \n"); + + pctx = tevent_req_callback_data(req, struct passkey_ctx); + + + ret = pam_passkey_get_mapping_recv(pctx, req, &result); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "devinfo cache_req_user_by_name_attrs_recv failed [%d]: %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + if (result == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "idevinfo cache req result == NULL\n"); + ret = ENOMEM; + goto done; + } + + pk_data = talloc_zero(pctx, struct pk_child_user_data); + if (!pk_data) { + DEBUG(SSSDBG_CRIT_FAILURE, "devinfo pk_data == NULL\n"); + ret = ENOMEM; + goto done; + } + + /* Use dns_name for AD/IPA - for LDAP fallback to domain->name */ + if (result->domain != NULL) { + domain_name = result->domain->dns_name; + if (domain_name == NULL) { + domain_name = result->domain->name; + } + } + + if (domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "devinfo Invalid or missing domain name\n"); + ret = EIO; + goto done; + } + + /* Get passkey data */ + DEBUG(SSSDBG_TRACE_ALL, "devinfo Processing passkey data\n"); + ret = process_passkey_data(pk_data, result->msgs[0], domain_name, pk_data); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "process_passkey_data for devinfo failed: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* timeout */ + ret = confdb_get_int(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "devinfo Failed to read passkey_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = passkey_local_verification(pctx, pctx, pctx->pam_ctx->rctx->cdb, + result->domain->sysdb, result->domain->dns_name, + pk_data, &verification, &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to check passkey devinfo verification [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + req = pam_passkey_auth_send(pctx, pctx->ev, timeout, debug_libfido2, + verification, pctx->pd, pk_data, + PASSKEY_GET_DEVINFO); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_send devinfo failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, pam_forwarder_get_devinfo_cb, pctx->preq); + + ret = EOK; + +done: + if (pk_data != NULL) { + talloc_free(pk_data); + } + + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_ALL, "Unexpected passkey devinfo error [%d]: %s.\n", + ret, sss_strerror(ret)); + /* the error does not affect existence of passkey_data + * pctx->preq->passkey_data_exists = false; + */ + pctx->preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(pctx->preq); + } + + return; +} + +struct pam_passkey_auth_send_state { + struct pam_data *pd; + struct tevent_context *ev; + struct tevent_timer *timeout_handler; + struct sss_child_ctx_old *child_ctx; + struct child_io_fds *io; + const char *logfile; + const char **extra_args; + char *verify_opts; + int timeout; + int child_status; + enum passkey_auth_action auth_action; +}; + +static errno_t passkey_child_exec(struct tevent_req *req); +static void pam_passkey_auth_done(int child_status, + struct tevent_signal *sige, + void *pvt); + +static void pam_passkey_get_devinfo_done(int child_status, + struct tevent_signal *sige, + void *pvt); + +static int pin_destructor(void *ptr) +{ + uint8_t *pin = talloc_get_type(ptr, uint8_t); + if (pin == NULL) return EOK; + + sss_erase_talloc_mem_securely(pin); + + return EOK; +} + +errno_t get_passkey_child_write_buffer(TALLOC_CTX *mem_ctx, + struct pam_data *pd, + uint8_t **_buf, size_t *_len) +{ + int ret; + uint8_t *buf; + size_t len; + const char *pin = NULL; + + if (pd == NULL || pd->authtok == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing authtok.\n"); + return EINVAL; + } + + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY || + sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + ret = sss_authtok_get_passkey_pin(pd->authtok, &pin, &len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_passkey_pin failed [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (pin == NULL || len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "Missing PIN.\n"); + return EINVAL; + } + + buf = talloc_size(mem_ctx, len); + if (buf == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + talloc_set_destructor((void *) buf, pin_destructor); + + safealign_memcpy(buf, pin, len, NULL); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported authtok type [%d].\n", + sss_authtok_get_type(pd->authtok)); + return EINVAL; + } + + *_len = len; + *_buf = buf; + + return EOK; +} + +static void pam_passkey_child_read_data(struct tevent_req *subreq) +{ + uint8_t *buf; + ssize_t buf_len; + char *str; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); + int ret; + + ret = read_pipe_recv(subreq, state, &buf, &buf_len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + str = malloc(sizeof(char) * buf_len); + if (str == NULL) { + return; + } + + snprintf(str, buf_len, "%s", buf); + + sss_authtok_set_passkey_reply(state->pd->authtok, str, 0); + + free(str); + + tevent_req_done(req); + return; +} + +static void passkey_child_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); + + int ret; + + DEBUG(SSSDBG_TRACE_LIBS, "Sending passkey data complete\n"); + + ret = write_pipe_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + PIPE_FD_CLOSE(state->io->write_to_child_fd); + + if (state->auth_action == PASSKEY_KERBEROS_AUTH) { + /* Read data back from passkey child */ + subreq = read_pipe_send(state, state->ev, state->io->read_from_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "read_pipe_send failed.\n"); + return; + } + + tevent_req_set_callback(subreq, pam_passkey_child_read_data, req); + } +} + +errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, + struct pk_child_user_data *pk_data, + enum passkey_auth_action auth_action, + char **_result_kh, + char **_result_pk) +{ + errno_t ret; + char *result_kh = NULL; + char *result_pk = NULL; + + int index = pk_data->num_credentials - 1; + /* start we last credential because it is the newer one + * therefore te mos accurate */ + + if (index < 0) { + ret = EINVAL; + goto done; + } + + result_kh = talloc_strdup(mem_ctx, pk_data->key_handles[index]); + if (auth_action == PASSKEY_LOCAL_AUTH) { + result_pk = talloc_strdup(mem_ctx, pk_data->public_keys[index]); + if (result_pk == NULL) { + ret = ENOMEM; + goto done; + } + } + + for (int i = index - 1; i >= 0; i--) { + result_kh = talloc_strdup_append(result_kh, ","); + if (result_kh == NULL) { + ret = ENOMEM; + goto done; + } + + result_kh = talloc_strdup_append(result_kh, pk_data->key_handles[i]); + if (result_kh == NULL) { + ret = ENOMEM; + goto done; + } + + if (auth_action == PASSKEY_LOCAL_AUTH) { + result_pk = talloc_strdup_append(result_pk, ","); + if (result_pk == NULL) { + ret = ENOMEM; + goto done; + } + + result_pk = talloc_strdup_append(result_pk, pk_data->public_keys[i]); + if (result_kh == NULL || result_pk == NULL) { + ret = ENOMEM; + goto done; + } + } + } + + *_result_kh = result_kh; + *_result_pk = result_pk; + + ret = EOK; +done: + return ret; +} + +struct tevent_req * +pam_passkey_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int timeout, + bool debug_libfido2, + enum passkey_user_verification verification, + struct pam_data *pd, + struct pk_child_user_data *pk_data, + enum passkey_auth_action auth_action) +{ + struct tevent_req *req; + struct pam_passkey_auth_send_state *state; + size_t arg_c = 0; + char *result_kh; + char *result_pk; + int num_args; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct pam_passkey_auth_send_state); + if (req == NULL) { + return NULL; + } + + state->pd = pd; + state->ev = ev; + + state->timeout = timeout; + state->auth_action = auth_action; + state->logfile = PASSKEY_CHILD_LOG_FILE; + state->io = talloc(state, struct child_io_fds); + if (state->io == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc child fds failed.\n"); + ret = ENOMEM; + goto done; + } + state->io->write_to_child_fd = -1; + state->io->read_from_child_fd = -1; + talloc_set_destructor((void *) state->io, child_io_destructor); + + num_args = 11; + state->extra_args = talloc_zero_array(state, const char *, num_args + 1); + if (state->extra_args == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + switch (verification) { + case PAM_PASSKEY_VERIFICATION_ON: + state->extra_args[arg_c++] = "--user-verification=true"; + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification true\n"); + break; + case PAM_PASSKEY_VERIFICATION_OFF: + state->extra_args[arg_c++] = "--user-verification=false"; + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification false\n"); + break; + default: + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification unset\n"); + break; + } + + ret = pam_passkey_concatenate_keys(state, pk_data, state->auth_action, + &result_kh, &result_pk); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_concatenate keys failed - [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto done; + } + + switch (auth_action) { + + case PASSKEY_KERBEROS_AUTH: { + DEBUG(SSSDBG_TRACE_FUNC, "auth_action: PASSKEY_KERBEROS_AUTH\n"); + state->extra_args[arg_c++] = pk_data->crypto_challenge; + state->extra_args[arg_c++] = "--cryptographic-challenge"; + state->extra_args[arg_c++] = result_kh; + state->extra_args[arg_c++] = "--key-handle"; + state->extra_args[arg_c++] = pk_data->domain; + state->extra_args[arg_c++] = "--domain"; + state->extra_args[arg_c++] = "--get-assert"; + break; + } + case PASSKEY_LOCAL_AUTH: { + DEBUG(SSSDBG_TRACE_FUNC, "auth_action: PASSKEY_LOCAL_AUTH\n"); + state->extra_args[arg_c++] = result_pk; + state->extra_args[arg_c++] = "--public-key"; + state->extra_args[arg_c++] = result_kh; + state->extra_args[arg_c++] = "--key-handle"; + state->extra_args[arg_c++] = pk_data->domain; + state->extra_args[arg_c++] = "--domain"; + state->extra_args[arg_c++] = state->pd->user; + state->extra_args[arg_c++] = "--username"; + state->extra_args[arg_c++] = "--authenticate"; + break; + } + /* ADDED VALMIDO */ + case PASSKEY_GET_DEVINFO: { + DEBUG(SSSDBG_TRACE_FUNC, "auth_action: PASSKEY_GET_DEVINFO\n"); + state->extra_args[arg_c++] = result_kh; + state->extra_args[arg_c++] = "--key-handle"; + state->extra_args[arg_c++] = pk_data->domain; + state->extra_args[arg_c++] = "--domain"; + state->extra_args[arg_c++] = "--get-device-info"; + break; + } + /* END VALMIDO */ + default: { + ret = ERR_INTERNAL; + DEBUG(SSSDBG_OP_FAILURE, "auth_send invalid action]\n"); + goto done; + } + } + + ret = passkey_child_exec(req); + +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void +passkey_child_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = + talloc_get_type(pvt, struct tevent_req); + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + + DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for passkey child, " + "consider increasing passkey_child_timeout\n"); + child_handler_destroy(state->child_ctx); + state->child_ctx = NULL; + state->child_status = ETIMEDOUT; + tevent_req_error(req, ERR_PASSKEY_CHILD_TIMEOUT); +} + +static errno_t passkey_child_exec(struct tevent_req *req) +{ + struct pam_passkey_auth_send_state *state; + struct tevent_req *subreq; + int pipefd_from_child[2] = PIPE_INIT; + int pipefd_to_child[2] = PIPE_INIT; + pid_t child_pid; + uint8_t *write_buf = NULL; + size_t write_buf_len = 0; + struct timeval tv; + int ret; + + state = tevent_req_data(req, struct pam_passkey_auth_send_state); + + ret = pipe(pipefd_from_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + child_pid = fork(); + if (child_pid == 0) { /* child */ + exec_child_ex(state, pipefd_to_child, pipefd_from_child, + PASSKEY_CHILD_PATH, state->logfile, state->extra_args, + false, STDIN_FILENO, STDOUT_FILENO); + /* We should never get here */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec passkey child\n"); + goto done; + } else if (child_pid > 0) { /* parent */ + state->io->read_from_child_fd = pipefd_from_child[0]; + PIPE_FD_CLOSE(pipefd_from_child[1]); + sss_fd_nonblocking(state->io->read_from_child_fd); + + state->io->write_to_child_fd = pipefd_to_child[1]; + PIPE_FD_CLOSE(pipefd_to_child[0]); + sss_fd_nonblocking(state->io->write_to_child_fd); + + /* Set up SIGCHLD handler */ + if (state->auth_action == PASSKEY_KERBEROS_AUTH) + ret = child_handler_setup(state->ev, child_pid, NULL, req, &state->child_ctx); + else if (state->auth_action == PASSKEY_LOCAL_AUTH) + ret = child_handler_setup(state->ev, child_pid, + pam_passkey_auth_done, req, + &state->child_ctx); + else if (state->auth_action == PASSKEY_GET_DEVINFO) + ret = child_handler_setup(state->ev, child_pid, + pam_passkey_get_devinfo_done, req, + &state->child_ctx); + else { + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: internal error - invalid auth_action\n"); + ret = ERR_INTERNAL; + goto done; + } + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_PASSKEY_CHILD; + goto done; + } + + /* Set up timeout handler */ + tv = tevent_timeval_current_ofs(state->timeout, 0); + state->timeout_handler = tevent_add_timer(state->ev, req, tv, + passkey_child_timeout, req); + if (state->timeout_handler == NULL) { + ret = ERR_PASSKEY_CHILD; + goto done; + } + + if (state->auth_action != PASSKEY_GET_DEVINFO) { + if (sss_authtok_get_type(state->pd->authtok) != SSS_AUTHTOK_TYPE_EMPTY) { + /* PIN is needed */ + ret = get_passkey_child_write_buffer(state, state->pd, &write_buf, + &write_buf_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "get_passkey_child_write_buffer failed [%d]: %s.\n", + ret, sss_strerror(ret)); + goto done; + } + } + } + + if (write_buf_len != 0) { + subreq = write_pipe_send(state, state->ev, write_buf, write_buf_len, + state->io->write_to_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "write_pipe_send failed.\n"); + ret = ERR_PASSKEY_CHILD; + goto done; + } + tevent_req_set_callback(subreq, passkey_child_write_done, req); + } + /* Now either wait for the timeout to fire or the child to finish */ + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + return EAGAIN; + +done: + if (ret != EOK) { + PIPE_CLOSE(pipefd_from_child); + PIPE_CLOSE(pipefd_to_child); + } + + return ret; +} + +static void +pam_passkey_get_devinfo_done(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + state->child_status = WEXITSTATUS(child_status); + if (WIFEXITED(child_status)) { + if (WEXITSTATUS(child_status) != 0) { + DEBUG(SSSDBG_OP_FAILURE, + PASSKEY_CHILD_PATH " devinfo failed with status [%d]. Check passkey_child" + " logs for more information.\n", + WEXITSTATUS(child_status)); + tevent_req_error(req, ERR_PASSKEY_CHILD); + return; + } + } else if (WIFSIGNALED(child_status)) { + DEBUG(SSSDBG_OP_FAILURE, + PASSKEY_CHILD_PATH " devinfo was terminated by signal [%d]. Check passkey_child" + " logs for more information.\n", + WTERMSIG(child_status)); + tevent_req_error(req, ECHILD); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "devinfo data is valid. Mark done\n"); + tevent_req_done(req); + return; +} + +errno_t pam_passkey_auth_recv(struct tevent_req *req, + int *child_status) +{ + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + + *child_status = state->child_status; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +errno_t decode_pam_passkey_msg(TALLOC_CTX *mem_ctx, + uint8_t *buf, + size_t len, + struct pk_child_user_data **_data) +{ + + size_t p = 0; + size_t pctr = 0; + errno_t ret; + size_t offset; + struct pk_child_user_data *data = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + data = talloc_zero(tmp_ctx, struct pk_child_user_data); + if (data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to talloc passkey data.\n"); + ret = ENOMEM; + goto done; + } + + data->user_verification = talloc_strdup(data, (char *) &buf[p]); + if (data->user_verification == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey prompt.\n"); + ret = ENOMEM; + goto done; + } + + offset = strlen(data->user_verification) + 1; + if (offset >= len) { + DEBUG(SSSDBG_OP_FAILURE, "passkey prompt offset failure.\n"); + ret = EIO; + goto done; + } + + data->crypto_challenge = talloc_strdup(data, (char *) &buf[p + offset]); + if (data->crypto_challenge == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey challenge.\n"); + ret = ENOMEM; + goto done; + } + + offset += strlen(data->crypto_challenge) + 1; + if (offset >= len) { + DEBUG(SSSDBG_OP_FAILURE, "passkey challenge offset failure.\n"); + ret = EIO; + goto done; + } + + + data->domain = talloc_strdup(data, (char *) &buf[p] + offset); + if (data->domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey domain.\n"); + ret = ENOMEM; + goto done; + } + + offset += strlen(data->domain) + 1; + if (offset >= len) { + DEBUG(SSSDBG_OP_FAILURE, "passkey domain offset failure.\n"); + ret = EIO; + goto done; + } + + SAFEALIGN_COPY_UINT32(&data->num_credentials, &buf[p + offset], &pctr); + size_t list_sz = (size_t) data->num_credentials; + + offset += sizeof(uint32_t); + + data->key_handles = talloc_zero_array(data, const char *, list_sz); + + for (int i = 0; i < list_sz; i++) { + data->key_handles[i] = talloc_strdup(data->key_handles, (char *) &buf[p + offset]); + if (data->key_handles[i] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey list.\n"); + ret = ENOMEM; + goto done; + } + + offset += strlen(data->key_handles[i]) + 1; + } + + *_data = talloc_steal(mem_ctx, data); + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t save_passkey_data(TALLOC_CTX *mem_ctx, + struct pam_ctx *pctx, + struct pk_child_user_data *data, + struct pam_auth_req *preq) +{ + char *pk_key; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Passkey data (pk_table_data) is stolen onto client ctx, it will + * be freed when the client closes, and the sss_ptr_hash interface + * takes care of automatically removing it from the hash table then */ + pctx->pk_table_data = talloc_zero(tmp_ctx, struct pam_passkey_table_data); + if (pctx->pk_table_data == NULL) { + return ENOMEM; + } + + if (pctx->pk_table_data->table == NULL) { + pctx->pk_table_data->table = sss_ptr_hash_create(pctx->pk_table_data, + NULL, NULL); + if (pctx->pk_table_data->table == NULL) { + ret = ENOMEM; + goto done; + } + } + + pk_key = talloc_asprintf(tmp_ctx, "%s", data->crypto_challenge); + if (pk_key == NULL) { + ret = ENOMEM; + goto done; + } + + pctx->pk_table_data->key = talloc_strdup(pctx->pk_table_data, pk_key); + if (pctx->pk_table_data->key == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_ptr_hash_add(pctx->pk_table_data->table, pk_key, data, + struct pk_child_user_data); + if (ret == EEXIST) { + DEBUG(SSSDBG_TRACE_FUNC, "pk_table key [%s] already exists\n", + pk_key); + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to add pk data to hash table " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + talloc_steal(mem_ctx, pctx->pk_table_data); + pctx->pk_table_data->data = talloc_steal(mem_ctx, data); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t pam_eval_passkey_response(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq, + bool *_pk_preauth_done) +{ + struct response_data *pk_resp; + struct pk_child_user_data *pk_data; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + pk_resp = pd->resp_list; + + while (pk_resp != NULL) { + switch (pk_resp->type) { + case SSS_PAM_PASSKEY_KRB_INFO: + if (!pctx->passkey_auth) { + /* Passkey auth is disabled. To avoid passkey prompts appearing, + * don't send SSS_PAM_PASSKEY_KRB_INFO to the client and + * add a dummy response to fallback to normal auth */ + pk_resp->do_not_send_to_client = true; + ret = pam_add_response(pd, SSS_OTP, 0, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + break; + } + ret = decode_pam_passkey_msg(tmp_ctx, pk_resp->data, pk_resp->len, &pk_data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to decode passkey msg\n"); + ret = EIO; + goto done; + } + + ret = save_passkey_data(preq->cctx, pctx, pk_data, preq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to save passkey msg\n"); + ret = EIO; + goto done; + } + break; + /* Passkey non-kerberos preauth has already run */ + case SSS_PAM_PASSKEY_INFO: + *_pk_preauth_done = true; + default: + break; + } + pk_resp = pk_resp->next; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + + +static void +pam_passkey_auth_done(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + state->child_status = WEXITSTATUS(child_status); + if (WIFEXITED(child_status)) { + if (WEXITSTATUS(child_status) != 0) { + DEBUG(SSSDBG_OP_FAILURE, + PASSKEY_CHILD_PATH " failed with status [%d]. Check passkey_child" + " logs for more information.\n", + WEXITSTATUS(child_status)); + tevent_req_error(req, ERR_PASSKEY_CHILD); + return; + } + } else if (WIFSIGNALED(child_status)) { + DEBUG(SSSDBG_OP_FAILURE, + PASSKEY_CHILD_PATH " was terminated by signal [%d]. Check passkey_child" + " logs for more information.\n", + WTERMSIG(child_status)); + tevent_req_error(req, ECHILD); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "passkey data is valid. Mark done\n"); + + tevent_req_done(req); + return; +} + +bool may_do_passkey_auth(struct pam_ctx *pctx, + struct pam_data *pd) +{ +#ifndef BUILD_PASSKEY + DEBUG(SSSDBG_TRACE_FUNC, "Passkey auth not possible, SSSD built without passkey support!\n"); + return false; +#else + if (!pctx->passkey_auth) { + return false; + } + + if (pd->cmd != SSS_PAM_PREAUTH && + pd->cmd != SSS_PAM_AUTHENTICATE && + pd->cmd != SSS_PAM_PASSKEY_PREAUTH) { + return false; + } + + if (pd->service == NULL || *pd->service == '\0') { + return false; + } + + return true; +#endif /* BUILD_PASSKEY */ +} diff --git a/src/responder/pam/pamsrv_passkey.h b/src/responder/pam/pamsrv_passkey.h index 48074d04263..dad5ea0ad3f 100644 --- a/src/responder/pam/pamsrv_passkey.h +++ b/src/responder/pam/pamsrv_passkey.h @@ -1,87 +1,102 @@ -/* - Authors: - Justin Stephenson - - Copyright (C) 2022 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 . -*/ - -#ifndef __PAMSRV_PASSKEY_H__ -#define __PAMSRV_PASSKEY_H__ - -#include -#include "util/util.h" -#include "util/sss_ptr_hash.h" -#include "responder/common/responder.h" -#include "responder/common/cache_req/cache_req.h" -#include "responder/pam/pamsrv.h" -#include "lib/certmap/sss_certmap.h" - -enum passkey_user_verification { - PAM_PASSKEY_VERIFICATION_ON, - PAM_PASSKEY_VERIFICATION_OFF, - PAM_PASSKEY_VERIFICATION_OMIT, - PAM_PASSKEY_VERIFICATION_INVALID -}; - -errno_t passkey_local(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - struct pam_ctx *pam_ctx, - struct pam_auth_req *preq, - struct pam_data *pd); -errno_t passkey_kerberos(struct pam_ctx *pctx, - struct pam_data *pd, - struct pam_auth_req *preq); - -struct pk_child_user_data { - /* Both Kerberos and non-kerberos */ - const char *domain; - size_t num_credentials; - const char *user_verification; - const char **key_handles; - /* Kerberos PA only */ - const char *crypto_challenge; - /* Non-kerberos only */ - const char *user; - const char **public_keys; -}; - -errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, - const char *verify_opts, - enum passkey_user_verification *_user_verification); - -void pam_forwarder_passkey_cb(struct tevent_req *req); -struct tevent_req *pam_passkey_auth_send(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - int timeout, - bool debug_libfido2, - enum passkey_user_verification verification, - struct pam_data *pd, - struct pk_child_user_data *pk_data, - bool kerberos_pa); -errno_t pam_passkey_auth_recv(struct tevent_req *req, - int *child_status); -errno_t pam_eval_passkey_response(struct pam_ctx *pctx, - struct pam_data *pd, - struct pam_auth_req *preq, - bool *_pk_preauth_done); -errno_t process_passkey_data(TALLOC_CTX *mem_ctx, - struct ldb_message *user_mesg, - const char *domain, - struct pk_child_user_data *_data); -bool may_do_passkey_auth(struct pam_ctx *pctx, - struct pam_data *pd); - -#endif /* __PAMSRV_PASSKEY_H__ */ +/* + Authors: + Justin Stephenson + + Copyright (C) 2022 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 . +*/ + +#ifndef __PAMSRV_PASSKEY_H__ +#define __PAMSRV_PASSKEY_H__ + +#include +#include "util/util.h" +#include "util/sss_ptr_hash.h" +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/pam/pamsrv.h" +#include "lib/certmap/sss_certmap.h" + +enum passkey_user_verification { + PAM_PASSKEY_VERIFICATION_ON, + PAM_PASSKEY_VERIFICATION_OFF, + PAM_PASSKEY_VERIFICATION_OMIT, + PAM_PASSKEY_VERIFICATION_INVALID +}; + +enum passkey_auth_action { + PASSKEY_KERBEROS_AUTH, + PASSKEY_LOCAL_AUTH, + PASSKEY_GET_DEVINFO +}; + +errno_t passkey_local(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_ctx *pam_ctx, + struct pam_auth_req *preq, + struct pam_data *pd); +errno_t passkey_kerberos(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq); + +errno_t passkey_local_get_devinfo(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_ctx *pam_ctx, + struct pam_auth_req *preq, + struct pam_data *pd); + +errno_t passkey_kerberos_get_devinfo(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq); +struct pk_child_user_data { + /* Both Kerberos and non-kerberos */ + const char *domain; + size_t num_credentials; + const char *user_verification; + const char **key_handles; + /* Kerberos PA only */ + const char *crypto_challenge; + /* Non-kerberos only */ + const char *user; + const char **public_keys; +}; + +errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, + const char *verify_opts, + enum passkey_user_verification *_user_verification); + +void pam_forwarder_passkey_cb(struct tevent_req *req); +struct tevent_req *pam_passkey_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int timeout, + bool debug_libfido2, + enum passkey_user_verification verification, + struct pam_data *pd, + struct pk_child_user_data *pk_data, + enum passkey_auth_action auth_action); +errno_t pam_passkey_auth_recv(struct tevent_req *req, + int *child_status); +errno_t pam_eval_passkey_response(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq, + bool *_pk_preauth_done); +errno_t process_passkey_data(TALLOC_CTX *mem_ctx, + struct ldb_message *user_mesg, + const char *domain, + struct pk_child_user_data *_data); +bool may_do_passkey_auth(struct pam_ctx *pctx, + struct pam_data *pd); + +#endif /* __PAMSRV_PASSKEY_H__ */ diff --git a/src/sss_client/pam_message.h b/src/sss_client/pam_message.h index d6fb254f208..57d34b23daa 100644 --- a/src/sss_client/pam_message.h +++ b/src/sss_client/pam_message.h @@ -1,80 +1,81 @@ -/* - Authors: - Sumit Bose - - Copyright (C) 2015 Red Hat - - PAM client - create message blob - - 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 . -*/ - -#ifndef _PAM_MESSAGE_H_ -#define _PAM_MESSAGE_H_ - -#include -#include -#include - -#include "sss_client/sss_cli.h" - -struct cert_auth_info; - -struct pam_items { - const char *pam_service; - const char *pam_user; - const char *pam_tty; - const char *pam_ruser; - const char *pam_rhost; - char *pam_authtok; - char *pam_newauthtok; - const char *pamstack_authtok; - const char *pamstack_oldauthtok; - size_t pam_service_size; - size_t pam_user_size; - size_t pam_tty_size; - size_t pam_ruser_size; - size_t pam_rhost_size; - enum sss_authtok_type pam_authtok_type; - size_t pam_authtok_size; - enum sss_authtok_type pam_newauthtok_type; - size_t pam_newauthtok_size; - pid_t cli_pid; - pid_t child_pid; - uint32_t flags; - const char *login_name; - char *domain_name; - const char *requested_domains; - size_t requested_domains_size; - char *otp_vendor; - char *otp_token_id; - char *otp_challenge; - char *oauth2_url; - char *oauth2_url_complete; - char *oauth2_pin; - char *first_factor; - char *passkey_key; - char *passkey_prompt_pin; - bool password_prompting; - - 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); - -#endif /* _PAM_MESSAGE_H_ */ +/* + Authors: + Sumit Bose + + Copyright (C) 2015 Red Hat + + PAM client - create message blob + + 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 . +*/ + +#ifndef _PAM_MESSAGE_H_ +#define _PAM_MESSAGE_H_ + +#include +#include +#include + +#include "sss_client/sss_cli.h" + +struct cert_auth_info; + +struct pam_items { + const char *pam_service; + const char *pam_user; + const char *pam_tty; + const char *pam_ruser; + const char *pam_rhost; + char *pam_authtok; + char *pam_newauthtok; + const char *pamstack_authtok; + const char *pamstack_oldauthtok; + size_t pam_service_size; + size_t pam_user_size; + size_t pam_tty_size; + size_t pam_ruser_size; + size_t pam_rhost_size; + enum sss_authtok_type pam_authtok_type; + size_t pam_authtok_size; + enum sss_authtok_type pam_newauthtok_type; + size_t pam_newauthtok_size; + pid_t cli_pid; + pid_t child_pid; + uint32_t flags; + const char *login_name; + char *domain_name; + const char *requested_domains; + size_t requested_domains_size; + char *otp_vendor; + char *otp_token_id; + char *otp_challenge; + char *oauth2_url; + char *oauth2_url_complete; + char *oauth2_pin; + char *first_factor; + char *passkey_key; + char *passkey_prompt_pin; + char *passkey_devinfo; + bool password_prompting; + + 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); + +#endif /* _PAM_MESSAGE_H_ */ diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c index 3f2317f3eb7..d1b3f246ca1 100644 --- a/src/sss_client/pam_sss.c +++ b/src/sss_client/pam_sss.c @@ -1,3270 +1,3438 @@ -/* - Authors: - Sumit Bose - - Copyright (C) 2009 Red Hat - Copyright (C) 2010, rhafer@suse.de, Novell Inc. - - 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 . -*/ - -#include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#ifdef HAVE_GDM_PAM_EXTENSIONS -#include -#endif - -#include "sss_pam_compat.h" -#include "sss_pam_macros.h" - -#include "sss_cli.h" -#include "pam_message.h" -#include "util/atomic_io.h" -#include "util/authtok-utils.h" -#include "util/dlinklist.h" -#include "util/memory_erase.h" - -#include -#define _(STRING) dgettext (PACKAGE, STRING) -#define _n(SINGULAR, PLURAL, VALUE) dngettext(PACKAGE, SINGULAR, PLURAL, VALUE) - -#define PWEXP_FLAG "pam_sss:password_expired_flag" -#define FD_DESTRUCTOR "pam_sss:fd_destructor" -#define PAM_SSS_AUTHOK_TYPE "pam_sss:authtok_type" -#define PAM_SSS_AUTHOK_SIZE "pam_sss:authtok_size" -#define PAM_SSS_AUTHOK_DATA "pam_sss:authtok_data" - -#define PW_RESET_MSG_FILENAME_TEMPLATE SSSD_CONF_DIR"/customize/%s/pam_sss_pw_reset_message.%s" -#define PW_RESET_MSG_MAX_SIZE 4096 - -#define OPT_RETRY_KEY "retry=" -#define OPT_DOMAINS_KEY "domains=" - -#define EXP_ACC_MSG _("Permission denied. ") -#define SRV_MSG _("Server message: ") -#define PASSKEY_LOCAL_AUTH_MSG _("Kerberos TGT will not be granted upon login, user experience will be affected.") -#define PASSKEY_DEFAULT_PIN_MSG _("Enter PIN:") - -#define DEBUG_MGS_LEN 1024 -#define MAX_AUTHTOK_SIZE (1024*1024) -#define CHECK_AND_RETURN_PI_STRING(s) ((s != NULL && *s != '\0')? s : "(not available)") -#define SERVICE_IS_GDM_SMARTCARD(pitem) (strcmp((pitem)->pam_service, \ - "gdm-smartcard") == 0) - -static void logger(pam_handle_t *pamh, int level, const char *fmt, ...) { - va_list ap; - - va_start(ap, fmt); - -#ifdef DEBUG - va_list apd; - char debug_msg[DEBUG_MGS_LEN]; - int ret; - va_copy(apd, ap); - - ret = vsnprintf(debug_msg, DEBUG_MGS_LEN, fmt, apd); - if (ret >= DEBUG_MGS_LEN) { - D(("the following message is truncated: %s", debug_msg)); - } else if (ret < 0) { - D(("vsnprintf failed to format debug message!")); - } else { - D((debug_msg)); - } - - va_end(apd); -#endif - - pam_vsyslog(pamh, LOG_AUTHPRIV|level, fmt, ap); - - va_end(ap); -} - -static void free_exp_data(pam_handle_t *pamh, void *ptr, int err) -{ - free(ptr); -} - -static void close_fd(pam_handle_t *pamh, void *ptr, int err) -{ -#ifdef PAM_DATA_REPLACE - if (err & PAM_DATA_REPLACE) { - /* Nothing to do */ - return; - } -#endif /* PAM_DATA_REPLACE */ - - D(("Closing the fd")); - - sss_pam_lock(); - sss_cli_close_socket(); - sss_pam_unlock(); -} - -struct cert_auth_info { - char *cert_user; - char *cert; - char *token_name; - char *module_name; - char *key_id; - char *label; - char *prompt_str; - char *pam_cert_user; - char *choice_list_id; - struct cert_auth_info *prev; - struct cert_auth_info *next; -}; - -static void free_cai(struct cert_auth_info *cai) -{ - if (cai != NULL) { - free(cai->cert_user); - free(cai->cert); - free(cai->token_name); - free(cai->module_name); - free(cai->key_id); - free(cai->label); - free(cai->prompt_str); - free(cai->choice_list_id); - free(cai); - } -} - -static void free_cert_list(struct cert_auth_info *list) -{ - struct cert_auth_info *cai; - struct cert_auth_info *cai_next; - - if (list != NULL) { - DLIST_FOR_EACH_SAFE(cai, cai_next, list) { - DLIST_REMOVE(list, cai); - free_cai(cai); - } - } -} - -static void overwrite_and_free_authtoks(struct pam_items *pi) -{ - if (pi->pam_authtok != NULL) { - sss_erase_mem_securely((void *)pi->pam_authtok, pi->pam_authtok_size); - free((void *)pi->pam_authtok); - pi->pam_authtok = NULL; - } - - if (pi->pam_newauthtok != NULL) { - sss_erase_mem_securely((void *)pi->pam_newauthtok, pi->pam_newauthtok_size); - free((void *)pi->pam_newauthtok); - pi->pam_newauthtok = NULL; - } - - if (pi->first_factor != NULL) { - sss_erase_mem_securely((void *)pi->first_factor, strlen(pi->first_factor)); - free((void *)pi->first_factor); - pi->first_factor = NULL; - } - - pi->pamstack_authtok = NULL; - pi->pamstack_oldauthtok = NULL; -} - -static void overwrite_and_free_pam_items(struct pam_items *pi) -{ - overwrite_and_free_authtoks(pi); - - free(pi->domain_name); - pi->domain_name = NULL; - - free(pi->otp_vendor); - pi->otp_vendor = NULL; - - free(pi->otp_token_id); - pi->otp_token_id = NULL; - - free(pi->otp_challenge); - pi->otp_challenge = NULL; - - free(pi->passkey_key); - pi->passkey_key = NULL; - - free(pi->passkey_prompt_pin); - pi->passkey_prompt_pin = NULL; - - 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) { - if (s1 == NULL && s2 == NULL) return 0; - if (s1 == NULL && s2 != NULL) return -1; - if (s1 != NULL && s2 == NULL) return 1; - return strcmp(s1, s2); -} - -enum { - SSS_PAM_CONV_DONE = 0, - SSS_PAM_CONV_STD, - SSS_PAM_CONV_REENTER, -}; - -static int do_pam_conversation(pam_handle_t *pamh, const int msg_style, - const char *msg, - const char *reenter_msg, - char **_answer) -{ - int ret; - int state = SSS_PAM_CONV_STD; - const struct pam_conv *conv; - const struct pam_message *mesg[1]; - struct pam_message *pam_msg; - struct pam_response *resp=NULL; - char *answer = NULL; - - if ((msg_style == PAM_TEXT_INFO || msg_style == PAM_ERROR_MSG) && - msg == NULL) return PAM_SYSTEM_ERR; - - if ((msg_style == PAM_PROMPT_ECHO_OFF || - msg_style == PAM_PROMPT_ECHO_ON) && - (msg == NULL || _answer == NULL)) return PAM_SYSTEM_ERR; - - if (msg_style == PAM_TEXT_INFO || msg_style == PAM_ERROR_MSG) { - logger(pamh, LOG_INFO, "User %s message: %s", - msg_style == PAM_TEXT_INFO ? "info" : "error", - msg); - } - - ret=pam_get_item(pamh, PAM_CONV, (const void **) &conv); - if (ret != PAM_SUCCESS) return ret; - if (conv == NULL || conv->conv == NULL) { - logger(pamh, LOG_ERR, "No conversation function"); - return PAM_SYSTEM_ERR; - } - - do { - pam_msg = malloc(sizeof(struct pam_message)); - if (pam_msg == NULL) { - D(("Malloc failed.")); - ret = PAM_SYSTEM_ERR; - goto failed; - } - - pam_msg->msg_style = msg_style; - if (state == SSS_PAM_CONV_REENTER) { - pam_msg->msg = reenter_msg; - } else { - pam_msg->msg = msg; - } - - mesg[0] = (const struct pam_message *) pam_msg; - - ret=conv->conv(1, mesg, &resp, - conv->appdata_ptr); - free(pam_msg); - if (ret != PAM_SUCCESS) { - D(("Conversation failure: %s.", pam_strerror(pamh,ret))); - goto failed; - } - - if (msg_style == PAM_PROMPT_ECHO_OFF || - msg_style == PAM_PROMPT_ECHO_ON) { - if (resp == NULL) { - D(("response expected, but resp==NULL")); - ret = PAM_SYSTEM_ERR; - goto failed; - } - - if (state == SSS_PAM_CONV_REENTER) { - if (null_strcmp(answer, resp[0].resp) != 0) { - logger(pamh, LOG_NOTICE, "Passwords do not match."); - sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); - free(resp[0].resp); - if (answer != NULL) { - sss_erase_mem_securely((void *) answer, strlen(answer)); - free(answer); - answer = NULL; - } - ret = do_pam_conversation(pamh, PAM_ERROR_MSG, - _("Passwords do not match"), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - ret = PAM_SYSTEM_ERR; - goto failed; - } - ret = PAM_CRED_ERR; - goto failed; - } - sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); - free(resp[0].resp); - } else { - if (resp[0].resp == NULL) { - D(("Empty password")); - answer = NULL; - } else { - answer = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); - sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); - free(resp[0].resp); - if(answer == NULL) { - D(("strndup failed")); - ret = PAM_BUF_ERR; - goto failed; - } - } - } - free(resp); - resp = NULL; - } - - if (reenter_msg != NULL && state == SSS_PAM_CONV_STD) { - state = SSS_PAM_CONV_REENTER; - } else { - state = SSS_PAM_CONV_DONE; - } - } while (state != SSS_PAM_CONV_DONE); - - if (_answer) *_answer = answer; - return PAM_SUCCESS; - -failed: - free(answer); - return ret; - -} - -static errno_t display_pw_reset_message(pam_handle_t *pamh, - const char *domain_name, - const char *suffix) -{ - int ret; - struct stat stat_buf; - char *msg_buf = NULL; - int fd = -1; - size_t size; - size_t total_len; - char *filename = NULL; - - if (strchr(suffix, '/') != NULL || strchr(domain_name, '/') != NULL) { - D(("Suffix [%s] or domain name [%s] contain illegal character.", suffix, - domain_name)); - return EINVAL; - } - - size = sizeof(PW_RESET_MSG_FILENAME_TEMPLATE) + strlen(domain_name) + - strlen(suffix); - filename = malloc(size); - if (filename == NULL) { - D(("malloc failed.")); - ret = ENOMEM; - goto done; - } - ret = snprintf(filename, size, PW_RESET_MSG_FILENAME_TEMPLATE, domain_name, - suffix); - if (ret < 0 || ret >= size) { - D(("snprintf failed.")); - ret = EFAULT; - goto done; - } - - fd = open(filename, O_RDONLY); - if (fd == -1) { - ret = errno; - D(("open failed [%d][%s].\n", ret, strerror(ret))); - goto done; - } - - ret = fstat(fd, &stat_buf); - if (ret == -1) { - ret = errno; - D(("fstat failed [%d][%s].", ret, strerror(ret))); - goto done; - } - - if (!S_ISREG(stat_buf.st_mode)) { - logger(pamh, LOG_ERR, - "Password reset message file is not a regular file."); - ret = EINVAL; - goto done; - } - - if (stat_buf.st_uid != 0 || stat_buf.st_gid != 0 || - (stat_buf.st_mode & ~S_IFMT) != 0644) { - logger(pamh, LOG_ERR,"Permission error, " - "file [%s] must be owned by root with permissions 0644.", - filename); - ret = EPERM; - goto done; - } - - if (stat_buf.st_size > PW_RESET_MSG_MAX_SIZE) { - logger(pamh, LOG_ERR, "Password reset message file is too large."); - ret = EFBIG; - goto done; - } - - msg_buf = malloc(stat_buf.st_size + 1); - if (msg_buf == NULL) { - D(("malloc failed.")); - ret = ENOMEM; - goto done; - } - - errno = 0; - total_len = sss_atomic_read_s(fd, msg_buf, stat_buf.st_size); - if (total_len == -1) { - ret = errno; - D(("read failed [%d][%s].", ret, strerror(ret))); - goto done; - } - - ret = close(fd); - fd = -1; - if (ret == -1) { - ret = errno; - D(("close failed [%d][%s].", ret, strerror(ret))); - } - - if (total_len != stat_buf.st_size) { - D(("read fewer bytes [%d] than expected [%d].", total_len, - stat_buf.st_size)); - ret = EIO; - goto done; - } - - msg_buf[stat_buf.st_size] = '\0'; - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, msg_buf, NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - } - -done: - if (fd != -1) { - close(fd); - } - free(msg_buf); - free(filename); - - return ret; -} - -static errno_t select_pw_reset_message(pam_handle_t *pamh, struct pam_items *pi) -{ - int ret; - char *locale; - const char *domain_name; - - domain_name = pi->domain_name; - if (domain_name == NULL || *domain_name == '\0') { - D(("Domain name is unknown.")); - return EINVAL; - } - - locale = setlocale(LC_MESSAGES, NULL); - - ret = -1; - if (locale != NULL) { - ret = display_pw_reset_message(pamh, domain_name, locale); - } - - if (ret != 0) { - ret = display_pw_reset_message(pamh, domain_name, "txt"); - } - - if (ret != 0) { - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, - _("Password reset by root is not supported."), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - } - } - - return ret; -} - -static int user_info_offline_auth(pam_handle_t *pamh, size_t buflen, - uint8_t *buf) -{ - int ret; - int64_t expire_date; - struct tm tm; - char expire_str[128]; - char user_msg[256]; - - expire_str[0] = '\0'; - - if (buflen != sizeof(uint32_t) + sizeof(int64_t)) { - D(("User info response data has the wrong size")); - return PAM_BUF_ERR; - } - - memcpy(&expire_date, buf + sizeof(uint32_t), sizeof(int64_t)); - - if (expire_date > 0) { - if (localtime_r((time_t *) &expire_date, &tm) != NULL) { - ret = strftime(expire_str, sizeof(expire_str), "%c", &tm); - if (ret == 0) { - D(("strftime failed.")); - expire_str[0] = '\0'; - } - } else { - D(("localtime_r failed")); - } - } - - ret = snprintf(user_msg, sizeof(user_msg), "%s%s%s.", - _("Authenticated with cached credentials"), - expire_str[0] ? _(", your cached password will expire at: ") : "", - expire_str[0] ? expire_str : ""); - if (ret < 0 || ret >= sizeof(user_msg)) { - D(("snprintf failed.")); - return PAM_SYSTEM_ERR; - } - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_grace_login(pam_handle_t *pamh, - size_t buflen, - uint8_t *buf) -{ - int ret; - uint32_t grace; - char user_msg[256]; - - if (buflen != 2* sizeof(uint32_t)) { - D(("User info response data has the wrong size")); - return PAM_BUF_ERR; - } - memcpy(&grace, buf + sizeof(uint32_t), sizeof(uint32_t)); - ret = snprintf(user_msg, sizeof(user_msg), - _("Your password has expired. " - "You have %1$d grace login(s) remaining."), - grace); - if (ret < 0 || ret >= sizeof(user_msg)) { - D(("snprintf failed.")); - return PAM_SYSTEM_ERR; - } - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); - - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -#define MINSEC 60 -#define HOURSEC (60*MINSEC) -#define DAYSEC (24*HOURSEC) -static int user_info_expire_warn(pam_handle_t *pamh, - size_t buflen, - uint8_t *buf) -{ - int ret; - uint32_t expire; - char user_msg[256]; - const char* unit; - - if (buflen != 2* sizeof(uint32_t)) { - D(("User info response data has the wrong size")); - return PAM_BUF_ERR; - } - memcpy(&expire, buf + sizeof(uint32_t), sizeof(uint32_t)); - /* expire == 0 indicates the password expired */ - if (expire != 0) { - if (expire >= DAYSEC) { - expire /= DAYSEC; - unit = _n("day", "days", expire); - } else if (expire >= HOURSEC) { - expire /= HOURSEC; - unit = _n("hour", "hours", expire); - } else if (expire >= MINSEC) { - expire /= MINSEC; - unit = _n("minute", "minutes", expire); - } else { - unit = _n("second", "seconds", expire); - } - - ret = snprintf(user_msg, sizeof(user_msg), - _("Your password will expire in %1$d %2$s."), expire, unit); - } else { - ret = snprintf(user_msg, sizeof(user_msg), - _("Your password has expired.")); - } - - if (ret < 0 || ret >= sizeof(user_msg)) { - D(("snprintf failed.")); - return PAM_SYSTEM_ERR; - } - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); - - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_offline_auth_delayed(pam_handle_t *pamh, size_t buflen, - uint8_t *buf) -{ - int ret; - int64_t delayed_until; - struct tm tm; - char delay_str[128]; - char user_msg[256]; - - delay_str[0] = '\0'; - - if (buflen != sizeof(uint32_t) + sizeof(int64_t)) { - D(("User info response data has the wrong size")); - return PAM_BUF_ERR; - } - - memcpy(&delayed_until, buf + sizeof(uint32_t), sizeof(int64_t)); - - if (delayed_until <= 0) { - D(("User info response data has an invalid value")); - return PAM_BUF_ERR; - } - - if (localtime_r((time_t *) &delayed_until, &tm) != NULL) { - ret = strftime(delay_str, sizeof(delay_str), "%c", &tm); - if (ret == 0) { - D(("strftime failed.")); - delay_str[0] = '\0'; - } - } else { - D(("localtime_r failed")); - } - - ret = snprintf(user_msg, sizeof(user_msg), "%s%s.", - _("Authentication is denied until: "), - delay_str); - if (ret < 0 || ret >= sizeof(user_msg)) { - D(("snprintf failed.")); - return PAM_SYSTEM_ERR; - } - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_offline_chpass(pam_handle_t *pamh) -{ - int ret; - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, - _("System is offline, password change not possible"), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_otp_chpass(pam_handle_t *pamh) -{ - int ret; - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, - _("After changing the OTP password, you need to " - "log out and back in order to acquire a ticket"), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_pin_locked(pam_handle_t *pamh) -{ - int ret; - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, _("PIN locked"), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_no_krb_tgt(pam_handle_t *pamh) -{ - int ret; - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, - _("No Kerberos TGT granted as " - "the server does not support this method. " - "Your single-sign on(SSO) experience will " - "be affected."), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_account_expired(pam_handle_t *pamh, size_t buflen, - uint8_t *buf) -{ - int ret; - uint32_t msg_len; - char *user_msg; - size_t bufsize = 0; - - /* resp_type and length of message are expected to be in buf */ - if (buflen < 2* sizeof(uint32_t)) { - D(("User info response data is too short")); - return PAM_BUF_ERR; - } - - /* msg_len = legth of message */ - memcpy(&msg_len, buf + sizeof(uint32_t), sizeof(uint32_t)); - - if (buflen != 2* sizeof(uint32_t) + msg_len) { - D(("User info response data has the wrong size")); - return PAM_BUF_ERR; - } - - bufsize = strlen(EXP_ACC_MSG) + 1; - - if (msg_len > 0) { - bufsize += strlen(SRV_MSG) + msg_len; - } - - user_msg = (char *)malloc(sizeof(char) * bufsize); - if (!user_msg) { - D(("Out of memory.")); - return PAM_SYSTEM_ERR; - } - - ret = snprintf(user_msg, bufsize, "%s%s%.*s", - EXP_ACC_MSG, - msg_len > 0 ? SRV_MSG : "", - (int)msg_len, - msg_len > 0 ? (char *)(buf + 2 * sizeof(uint32_t)) : "" ); - if (ret < 0 || ret > bufsize) { - D(("snprintf failed.")); - - free(user_msg); - return PAM_SYSTEM_ERR; - } - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); - free(user_msg); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_chpass_error(pam_handle_t *pamh, size_t buflen, - uint8_t *buf) -{ - int ret; - uint32_t msg_len; - char *user_msg; - size_t bufsize = 0; - - if (buflen < 2* sizeof(uint32_t)) { - D(("User info response data is too short")); - return PAM_BUF_ERR; - } - - memcpy(&msg_len, buf + sizeof(uint32_t), sizeof(uint32_t)); - - if (buflen != 2* sizeof(uint32_t) + msg_len) { - D(("User info response data has the wrong size")); - return PAM_BUF_ERR; - } - - bufsize = strlen(_("Password change failed. ")) + 1; - - if (msg_len > 0) { - bufsize += strlen(_("Server message: ")) + msg_len; - } - - user_msg = (char *)malloc(sizeof(char) * bufsize); - if (!user_msg) { - D(("Out of memory.")); - return PAM_SYSTEM_ERR; - } - - ret = snprintf(user_msg, bufsize, "%s%s%.*s", - _("Password change failed. "), - msg_len > 0 ? _("Server message: ") : "", - (int)msg_len, - msg_len > 0 ? (char *)(buf + 2 * sizeof(uint32_t)) : "" ); - if (ret < 0 || ret > bufsize) { - D(("snprintf failed.")); - - free(user_msg); - return PAM_SYSTEM_ERR; - } - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); - free(user_msg); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int eval_user_info_response(pam_handle_t *pamh, size_t buflen, - uint8_t *buf) -{ - int ret; - uint32_t type; - - if (buflen < sizeof(uint32_t)) { - D(("User info response data is too short")); - return PAM_BUF_ERR; - } - - memcpy(&type, buf, sizeof(uint32_t)); - - switch(type) { - case SSS_PAM_USER_INFO_OFFLINE_AUTH: - ret = user_info_offline_auth(pamh, buflen, buf); - break; - case SSS_PAM_USER_INFO_GRACE_LOGIN: - ret = user_info_grace_login(pamh, buflen, buf); - break; - case SSS_PAM_USER_INFO_EXPIRE_WARN: - ret = user_info_expire_warn(pamh, buflen, buf); - break; - case SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED: - ret = user_info_offline_auth_delayed(pamh, buflen, buf); - break; - case SSS_PAM_USER_INFO_OFFLINE_CHPASS: - ret = user_info_offline_chpass(pamh); - break; - case SSS_PAM_USER_INFO_OTP_CHPASS: - ret = user_info_otp_chpass(pamh); - break; - case SSS_PAM_USER_INFO_CHPASS_ERROR: - ret = user_info_chpass_error(pamh, buflen, buf); - break; - case SSS_PAM_USER_INFO_PIN_LOCKED: - ret = user_info_pin_locked(pamh); - break; - case SSS_PAM_USER_INFO_ACCOUNT_EXPIRED: - ret = user_info_account_expired(pamh, buflen, buf); - break; - case SSS_PAM_USER_INFO_NO_KRB_TGT: - ret = user_info_no_krb_tgt(pamh); - break; - default: - D(("Unknown user info type [%d]", type)); - ret = PAM_SYSTEM_ERR; - } - - return ret; -} - -static int parse_cert_info(struct pam_items *pi, uint8_t *buf, size_t len, - size_t *p, const char **cert_user, - const char **pam_cert_user) -{ - struct cert_auth_info *cai = NULL; - size_t offset; - int ret; - - if (buf[*p + (len - 1)] != '\0') { - D(("cert info does not end with \\0.")); - return EINVAL; - } - - cai = calloc(1, sizeof(struct cert_auth_info)); - if (cai == NULL) { - return ENOMEM; - } - - cai->cert_user = strdup((char *) &buf[*p]); - if (cai->cert_user == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - if (cert_user != NULL) { - *cert_user = cai->cert_user; - } - - offset = strlen(cai->cert_user) + 1; - if (offset >= len) { - D(("Cert message size mismatch")); - ret = EINVAL; - goto done; - } - - cai->token_name = strdup((char *) &buf[*p + offset]); - if (cai->token_name == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - - offset += strlen(cai->token_name) + 1; - if (offset >= len) { - D(("Cert message size mismatch")); - ret = EINVAL; - goto done; - } - - cai->module_name = strdup((char *) &buf[*p + offset]); - if (cai->module_name == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - - offset += strlen(cai->module_name) + 1; - if (offset >= len) { - D(("Cert message size mismatch")); - ret = EINVAL; - goto done; - } - - cai->key_id = strdup((char *) &buf[*p + offset]); - if (cai->key_id == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - - offset += strlen(cai->key_id) + 1; - if (offset >= len) { - D(("Cert message size mismatch")); - ret = EINVAL; - goto done; - } - - cai->label = strdup((char *) &buf[*p + offset]); - if (cai->label == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - - offset += strlen(cai->label) + 1; - if (offset >= len) { - D(("Cert message size mismatch")); - ret = EINVAL; - goto done; - } - - cai->prompt_str = strdup((char *) &buf[*p + offset]); - if (cai->prompt_str == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - - offset += strlen(cai->prompt_str) + 1; - if (offset >= len) { - D(("Cert message size mismatch")); - ret = EINVAL; - goto done; - } - - cai->pam_cert_user = strdup((char *) &buf[*p + offset]); - if (cai->pam_cert_user == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - if (pam_cert_user != NULL) { - *pam_cert_user = cai->pam_cert_user; - } - - D(("cert user: [%s] token name: [%s] module: [%s] key id: [%s] " - "prompt: [%s] pam cert user: [%s]", - cai->cert_user, cai->token_name, cai->module_name, - cai->key_id, cai->prompt_str, cai->pam_cert_user)); - - DLIST_ADD(pi->cert_list, cai); - ret = 0; - -done: - if (ret != 0) { - free_cai(cai); - } - - return ret; -} - -static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf, - struct pam_items *pi) -{ - int ret; - size_t p=0; - char *env_item; - int32_t c; - int32_t type; - int32_t len; - int32_t pam_status; - size_t offset; - const char *cert_user; - const char *pam_cert_user; - - if (buflen < (2*sizeof(int32_t))) { - D(("response buffer is too small")); - return PAM_BUF_ERR; - } - - memcpy(&pam_status, buf+p, sizeof(int32_t)); - p += sizeof(int32_t); - - - memcpy(&c, buf+p, sizeof(int32_t)); - p += sizeof(int32_t); - - while(c>0) { - if (buflen < (p+2*sizeof(int32_t))) { - D(("response buffer is too small")); - return PAM_BUF_ERR; - } - - memcpy(&type, buf+p, sizeof(int32_t)); - p += sizeof(int32_t); - - memcpy(&len, buf+p, sizeof(int32_t)); - p += sizeof(int32_t); - - if (buflen < (p + len)) { - D(("response buffer is too small")); - return PAM_BUF_ERR; - } - - switch(type) { - case SSS_PAM_SYSTEM_INFO: - if (buf[p + (len -1)] != '\0') { - D(("system info does not end with \\0.")); - break; - } - logger(pamh, LOG_INFO, "system info: [%s]", &buf[p]); - break; - case SSS_PAM_DOMAIN_NAME: - if (buf[p + (len -1)] != '\0') { - D(("domain name does not end with \\0.")); - break; - } - D(("domain name: [%s]", &buf[p])); - free(pi->domain_name); - pi->domain_name = strdup((char *) &buf[p]); - if (pi->domain_name == NULL) { - D(("strdup failed")); - } - break; - case SSS_ENV_ITEM: - case SSS_PAM_ENV_ITEM: - case SSS_ALL_ENV_ITEM: - if (buf[p + (len -1)] != '\0') { - D(("env item does not end with \\0.")); - break; - } - - D(("env item: [%s]", &buf[p])); - if (type == SSS_PAM_ENV_ITEM || type == SSS_ALL_ENV_ITEM) { - ret = pam_putenv(pamh, (char *)&buf[p]); - if (ret != PAM_SUCCESS) { - D(("pam_putenv failed.")); - break; - } - } - - if (type == SSS_ENV_ITEM || type == SSS_ALL_ENV_ITEM) { - env_item = strdup((char *)&buf[p]); - if (env_item == NULL) { - D(("strdup failed")); - break; - } - ret = putenv(env_item); - if (ret == -1) { - D(("putenv failed.")); - break; - } - } - break; - case SSS_PAM_USER_INFO: - ret = eval_user_info_response(pamh, len, &buf[p]); - if (ret != PAM_SUCCESS) { - D(("eval_user_info_response failed")); - } - break; - case SSS_PAM_TEXT_MSG: - if (buf[p + (len -1)] != '\0') { - D(("system info does not end with \\0.")); - break; - } - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, (char *) &buf[p], - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - } - break; - case SSS_OTP: - D(("OTP was used, removing authtokens.")); - overwrite_and_free_authtoks(pi); - ret = pam_set_item(pamh, PAM_AUTHTOK, NULL); - if (ret != PAM_SUCCESS) { - D(("Failed to remove PAM_AUTHTOK after using otp [%s]", - pam_strerror(pamh,ret))); - } - break; - case SSS_PAM_OTP_INFO: - if (buf[p + (len - 1)] != '\0') { - D(("otp info does not end with \\0.")); - break; - } - - free(pi->otp_vendor); - pi->otp_vendor = strdup((char *) &buf[p]); - if (pi->otp_vendor == NULL) { - D(("strdup failed")); - break; - } - - offset = strlen(pi->otp_vendor) + 1; - if (offset >= len) { - D(("OTP message size mismatch")); - free(pi->otp_vendor); - pi->otp_vendor = NULL; - break; - } - free(pi->otp_token_id); - pi->otp_token_id = strdup((char *) &buf[p + offset]); - if (pi->otp_token_id == NULL) { - D(("strdup failed")); - break; - } - - offset += strlen(pi->otp_token_id) + 1; - if (offset >= len) { - D(("OTP message size mismatch")); - free(pi->otp_token_id); - pi->otp_token_id = NULL; - break; - } - free(pi->otp_challenge); - pi->otp_challenge = strdup((char *) &buf[p + offset]); - if (pi->otp_challenge == NULL) { - D(("strdup failed")); - break; - } - - break; - case SSS_PAM_CERT_INFO: - case SSS_PAM_CERT_INFO_WITH_HINT: - if (buf[p + (len - 1)] != '\0') { - D(("cert info does not end with \\0.")); - break; - } - - if (type == SSS_PAM_CERT_INFO_WITH_HINT) { - pi->user_name_hint = true; - } else { - pi->user_name_hint = false; - } - - ret = parse_cert_info(pi, buf, len, &p, &cert_user, - &pam_cert_user); - if (ret != 0) { - D(("Failed to parse cert info")); - break; - } - - if ((pi->pam_user == NULL || *(pi->pam_user) == '\0') - && *cert_user != '\0' && *pam_cert_user != '\0') { - ret = pam_set_item(pamh, PAM_USER, pam_cert_user); - if (ret != PAM_SUCCESS) { - D(("Failed to set PAM_USER during " - "Smartcard authentication [%s]", - pam_strerror(pamh, ret))); - break; - } - - pi->pam_user = cert_user; - pi->pam_user_size = strlen(pi->pam_user) + 1; - } - break; - case SSS_PASSWORD_PROMPTING: - 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; - case SSS_CHILD_KEEP_ALIVE: - memcpy(&pi->child_pid, &buf[p], len); - break; - case SSS_PAM_OAUTH2_INFO: - if (buf[p + (len - 1)] != '\0') { - D(("oauth2 info does not end with \\0.")); - break; - } - - free(pi->oauth2_url); - pi->oauth2_url = strdup((char *) &buf[p]); - if (pi->oauth2_url == NULL) { - D(("strdup failed")); - break; - } - - offset = strlen(pi->oauth2_url) + 1; - if (offset >= len) { - D(("OAuth2 message size mismatch")); - free(pi->oauth2_url); - pi->oauth2_url = NULL; - break; - } - - free(pi->oauth2_url_complete); - pi->oauth2_url_complete = strdup((char *) &buf[p + offset]); - if (pi->oauth2_url_complete == NULL) { - D(("strdup failed")); - break; - } - - offset = offset + strlen(pi->oauth2_url_complete) + 1; - if (offset >= len) { - D(("OAuth2 message size mismatch")); - free(pi->oauth2_url_complete); - pi->oauth2_url_complete = NULL; - break; - } - - /* This field is optional. */ - if (pi->oauth2_url_complete[0] == '\0') { - free(pi->oauth2_url_complete); - pi->oauth2_url_complete = NULL; - } - - free(pi->oauth2_pin); - pi->oauth2_pin = strdup((char *) &buf[p + offset]); - if (pi->oauth2_pin == NULL) { - D(("strdup failed")); - break; - } - - break; - case SSS_PAM_PASSKEY_KRB_INFO: - free(pi->passkey_prompt_pin); - pi->passkey_prompt_pin = strdup((char *) &buf[p]); - if (pi->passkey_prompt_pin == NULL) { - D(("strdup failed")); - break; - } - - offset = strlen(pi->passkey_prompt_pin) + 1; - if (offset >= len) { - D(("Passkey message size mismatch")); - free(pi->passkey_prompt_pin); - pi->passkey_prompt_pin = NULL; - break; - } - - free(pi->passkey_key); - pi->passkey_key = strdup((char *) &buf[p + offset]); - if (pi->passkey_key == NULL) { - D(("strdup failed")); - break; - } - break; - case SSS_PAM_PASSKEY_INFO: - if (buf[p + (len - 1)] != '\0') { - D(("passkey info does not end with \\0.")); - break; - } - - free(pi->passkey_prompt_pin); - pi->passkey_prompt_pin = strdup((char *) &buf[p]); - if (pi->passkey_prompt_pin == NULL) { - D(("strdup failed")); - break; - } - break; - default: - D(("Unknown response type [%d]", type)); - } - p += len; - - --c; - } - - return PAM_SUCCESS; -} - -bool is_string_empty_or_whitespace(const char *str) -{ - int i; - - if (str == NULL) { - return true; - } - - for (i = 0; str[i] != '\0'; i++) { - if (!isspace(str[i])) { - return false; - } - } - - return true; -} - -static int get_pam_items(pam_handle_t *pamh, uint32_t flags, - struct pam_items *pi) -{ - int ret; - - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; - pi->pam_authtok = NULL; - pi->pam_authtok_size = 0; - pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_EMPTY; - pi->pam_newauthtok = NULL; - pi->pam_newauthtok_size = 0; - pi->first_factor = NULL; - - ret = pam_get_item(pamh, PAM_SERVICE, (const void **) &(pi->pam_service)); - if (ret != PAM_SUCCESS) return ret; - if (pi->pam_service == NULL) pi->pam_service=""; - pi->pam_service_size=strlen(pi->pam_service)+1; - - ret = pam_get_item(pamh, PAM_USER, (const void **) &(pi->pam_user)); - if (ret == PAM_PERM_DENIED && (flags & PAM_CLI_FLAGS_ALLOW_MISSING_NAME)) { - pi->pam_user = ""; - ret = PAM_SUCCESS; - } - if (ret != PAM_SUCCESS) return ret; - if (flags & PAM_CLI_FLAGS_ALLOW_MISSING_NAME) { - if (is_string_empty_or_whitespace(pi->pam_user)) { - pi->pam_user = ""; - } - } - if (pi->pam_user == NULL) { - D(("No user found, aborting.")); - return PAM_BAD_ITEM; - } - if (strcmp(pi->pam_user, "root") == 0) { - D(("pam_sss will not handle root.")); - return PAM_USER_UNKNOWN; - } - pi->pam_user_size=strlen(pi->pam_user)+1; - - - ret = pam_get_item(pamh, PAM_TTY, (const void **) &(pi->pam_tty)); - if (ret != PAM_SUCCESS) return ret; - if (pi->pam_tty == NULL) pi->pam_tty=""; - pi->pam_tty_size=strlen(pi->pam_tty)+1; - - ret = pam_get_item(pamh, PAM_RUSER, (const void **) &(pi->pam_ruser)); - if (ret != PAM_SUCCESS) return ret; - if (pi->pam_ruser == NULL) pi->pam_ruser=""; - pi->pam_ruser_size=strlen(pi->pam_ruser)+1; - - ret = pam_get_item(pamh, PAM_RHOST, (const void **) &(pi->pam_rhost)); - if (ret != PAM_SUCCESS) return ret; - if (pi->pam_rhost == NULL) pi->pam_rhost=""; - pi->pam_rhost_size=strlen(pi->pam_rhost)+1; - - ret = pam_get_item(pamh, PAM_AUTHTOK, - (const void **) &(pi->pamstack_authtok)); - if (ret != PAM_SUCCESS) return ret; - if (pi->pamstack_authtok == NULL) pi->pamstack_authtok=""; - - ret = pam_get_item(pamh, PAM_OLDAUTHTOK, - (const void **) &(pi->pamstack_oldauthtok)); - if (ret != PAM_SUCCESS) return ret; - if (pi->pamstack_oldauthtok == NULL) pi->pamstack_oldauthtok=""; - - pi->cli_pid = getpid(); - - pi->login_name = pam_modutil_getlogin(pamh); - if (pi->login_name == NULL) pi->login_name=""; - - pi->domain_name = NULL; - - if (pi->requested_domains == NULL) pi->requested_domains = ""; - pi->requested_domains_size = strlen(pi->requested_domains) + 1; - - pi->otp_vendor = NULL; - pi->otp_token_id = NULL; - pi->otp_challenge = NULL; - pi->password_prompting = false; - - pi->cert_list = NULL; - pi->selected_cert = NULL; - - pi->pc = NULL; - - pi->flags = flags; - - return PAM_SUCCESS; -} - -static void print_pam_items(struct pam_items *pi) -{ - if (pi == NULL) return; - - D(("Service: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_service))); - D(("User: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_user))); - D(("Tty: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_tty))); - D(("Ruser: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_ruser))); - D(("Rhost: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_rhost))); - D(("Pamstack_Authtok: %s", - CHECK_AND_RETURN_PI_STRING(pi->pamstack_authtok))); - D(("Pamstack_Oldauthtok: %s", - CHECK_AND_RETURN_PI_STRING(pi->pamstack_oldauthtok))); - D(("Authtok: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_authtok))); - D(("Newauthtok: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_newauthtok))); - D(("Cli_PID: %d", pi->cli_pid)); - D(("Child_PID: %d", pi->child_pid)); - D(("Requested domains: %s", pi->requested_domains)); - D(("Flags: %d", pi->flags)); -} - -static int send_and_receive(pam_handle_t *pamh, struct pam_items *pi, - enum sss_cli_command task, bool quiet_mode) -{ - int ret; - int sret; - int errnop; - struct sss_cli_req_data rd; - uint8_t *buf = NULL; - uint8_t *repbuf = NULL; - size_t replen; - int pam_status = PAM_SYSTEM_ERR; - - print_pam_items(pi); - - ret = pack_message_v3(pi, &rd.len, &buf); - if (ret != 0) { - D(("pack_message failed.")); - pam_status = PAM_SYSTEM_ERR; - goto done; - } - rd.data = buf; - - errnop = 0; - ret = sss_pam_make_request(task, &rd, &repbuf, &replen, &errnop); - - sret = pam_set_data(pamh, FD_DESTRUCTOR, NULL, close_fd); - if (sret != PAM_SUCCESS) { - D(("pam_set_data failed, client might leaks fds")); - } - - if (ret != PAM_SUCCESS) { - /* If there is no PAM responder socket during the access control step - * we assume this is on purpose, i.e. PAM responder is not configured. - * PAM_USER_UNKNOWN is returned to the PAM stack to avoid unexpected - * denials. */ - if (errnop == ESSS_NO_SOCKET && task == SSS_PAM_ACCT_MGMT) { - pam_status = PAM_USER_UNKNOWN; - } else { - if (errnop != 0 && errnop != ESSS_NO_SOCKET) { - logger(pamh, LOG_ERR, "Request to sssd failed. %s", - ssscli_err2string(errnop)); - } - - pam_status = PAM_AUTHINFO_UNAVAIL; - } - goto done; - } - -/* FIXME: add an end signature */ - if (replen < (2*sizeof(int32_t))) { - D(("response not in expected format.")); - pam_status = PAM_SYSTEM_ERR; - goto done; - } - - SAFEALIGN_COPY_UINT32(&pam_status, repbuf, NULL); - ret = eval_response(pamh, replen, repbuf, pi); - if (ret != PAM_SUCCESS) { - D(("eval_response failed.")); - pam_status = ret; - goto done; - } - - switch (task) { - case SSS_PAM_AUTHENTICATE: - logger(pamh, (pam_status == PAM_SUCCESS ? LOG_INFO : LOG_NOTICE), - "authentication %s; logname=%s uid=%lu euid=%d tty=%s " - "ruser=%s rhost=%s user=%s", - pam_status == PAM_SUCCESS ? "success" : "failure", - pi->login_name, getuid(), (unsigned long) geteuid(), - pi->pam_tty, pi->pam_ruser, pi->pam_rhost, pi->pam_user); - if (pam_status != PAM_SUCCESS) { - /* don't log if quiet_mode is on and pam_status is - * User not known to the underlying authentication module - */ - if (!quiet_mode || pam_status != 10) { - logger(pamh, LOG_NOTICE, "received for user %s: %d (%s)", - pi->pam_user, pam_status, - pam_strerror(pamh,pam_status)); - } - } - break; - case SSS_PAM_CHAUTHTOK_PRELIM: - if (pam_status != PAM_SUCCESS) { - /* don't log if quiet_mode is on and pam_status is - * User not known to the underlying authentication module - */ - if (!quiet_mode || pam_status != 10) { - logger(pamh, LOG_NOTICE, - "Authentication failed for user %s: %d (%s)", - pi->pam_user, pam_status, - pam_strerror(pamh,pam_status)); - } - } - break; - case SSS_PAM_CHAUTHTOK: - if (pam_status != PAM_SUCCESS) { - logger(pamh, LOG_NOTICE, - "Password change failed for user %s: %d (%s)", - pi->pam_user, pam_status, - pam_strerror(pamh,pam_status)); - } - break; - case SSS_PAM_ACCT_MGMT: - if (pam_status != PAM_SUCCESS) { - /* don't log if quiet_mode is on and pam_status is - * User not known to the underlying authentication module - */ - if (!quiet_mode || pam_status != 10) { - logger(pamh, LOG_NOTICE, - "Access denied for user %s: %d (%s)", - pi->pam_user, pam_status, - pam_strerror(pamh,pam_status)); - } - } - break; - case SSS_PAM_OPEN_SESSION: - case SSS_PAM_SETCRED: - case SSS_PAM_CLOSE_SESSION: - case SSS_PAM_PREAUTH: - break; - default: - D(("Illegal task [%#x]", task)); - pam_status = PAM_SYSTEM_ERR; - } - -done: - if (buf != NULL ) { - sss_erase_mem_securely((void *)buf, rd.len); - free(buf); - } - free(repbuf); - - return pam_status; -} - -static int prompt_password(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); - sss_erase_mem_securely((void *)answer, strlen(answer)); - free(answer); - answer=NULL; - if (pi->pam_authtok == NULL) { - return PAM_BUF_ERR; - } - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD; - pi->pam_authtok_size=strlen(pi->pam_authtok); - } - - return PAM_SUCCESS; -} - -static int prompt_2fa(pam_handle_t *pamh, struct pam_items *pi, - bool second_factor_optional, - const char *prompt_fa1, const char *prompt_fa2) -{ - int ret; - const struct pam_conv *conv; - const struct pam_message *mesg[2] = { NULL, NULL }; - struct pam_message m[2] = { {0}, {0} }; - struct pam_response *resp = NULL; - size_t needed_size; - - ret = pam_get_item(pamh, PAM_CONV, (const void **) &conv); - if (ret != PAM_SUCCESS) { - return ret; - } - if (conv == NULL || conv->conv == NULL) { - logger(pamh, LOG_ERR, "No conversation function"); - return PAM_SYSTEM_ERR; - } - - m[0].msg_style = PAM_PROMPT_ECHO_OFF; - m[0].msg = prompt_fa1; - m[1].msg_style = PAM_PROMPT_ECHO_OFF; - m[1].msg = prompt_fa2; - - mesg[0] = (const struct pam_message *) m; - /* The following assignment might look a bit odd but is recommended in the - * pam_conv man page to make sure that the second argument of the PAM - * conversation function can be interpreted in two different ways. - * Basically it is important that both the actual struct pam_message and - * the pointers to the struct pam_message are arrays. Since the assignment - * makes clear that mesg[] and (*mesg)[] are arrays it should be kept this - * way and not be replaced by other equivalent assignments. */ - mesg[1] = & (( *mesg )[1]); - - ret = conv->conv(2, mesg, &resp, conv->appdata_ptr); - if (ret != PAM_SUCCESS) { - D(("Conversation failure: %s.", pam_strerror(pamh, ret))); - return ret; - } - - if (resp == NULL) { - D(("response expected, but resp==NULL")); - return PAM_SYSTEM_ERR; - } - - if (resp[0].resp == NULL || *(resp[0].resp) == '\0') { - D(("Missing factor.")); - ret = PAM_CRED_INSUFFICIENT; - goto done; - } - - if (resp[1].resp == NULL || *(resp[1].resp) == '\0') { - /* Missing second factor, assume first factor contains combined 2FA - * credentials if the second factor is not optional. If it is optional - * then it is assumed that the first factor contain the password. */ - pi->pam_authtok = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); - if (pi->pam_authtok == NULL) { - D(("strndup failed.")); - ret = PAM_BUF_ERR; - goto done; - } - pi->pam_authtok_size = strlen(pi->pam_authtok) + 1; - pi->pam_authtok_type = second_factor_optional - ? SSS_AUTHTOK_TYPE_PASSWORD - : SSS_AUTHTOK_TYPE_2FA_SINGLE; - } else if (pi->pam_service != NULL && strcmp(pi->pam_service, "sshd") == 0 - && strcmp(resp[0].resp, resp[1].resp) == 0) { - /* Special handling for SSH with password authentication (ssh's - * 'PasswordAuthentication' option. In this mode the ssh client - * directly prompts the user for a password and the prompts we are - * sending are ignored. Since we send two prompts ssh * will create two - * response as well with the same content. We assume that the combined - * 2FA credentials are used even if the second factor is optional - * because there is no indication about the intention of the user. As a - * result we prefer the more secure variant. */ - - pi->pam_authtok = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); - if (pi->pam_authtok == NULL) { - D(("strndup failed.")); - ret = PAM_BUF_ERR; - goto done; - } - pi->pam_authtok_size = strlen(pi->pam_authtok) + 1; - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_2FA_SINGLE; - } else { - - ret = sss_auth_pack_2fa_blob(resp[0].resp, 0, resp[1].resp, 0, NULL, 0, - &needed_size); - if (ret != EAGAIN) { - D(("sss_auth_pack_2fa_blob failed.")); - ret = PAM_BUF_ERR; - goto done; - } - - pi->pam_authtok = malloc(needed_size); - if (pi->pam_authtok == NULL) { - D(("malloc failed.")); - ret = PAM_BUF_ERR; - goto done; - } - - ret = sss_auth_pack_2fa_blob(resp[0].resp, 0, resp[1].resp, 0, - (uint8_t *) pi->pam_authtok, needed_size, - &needed_size); - if (ret != EOK) { - D(("sss_auth_pack_2fa_blob failed.")); - ret = PAM_BUF_ERR; - goto done; - } - - pi->pam_authtok_size = needed_size; - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_2FA; - pi->first_factor = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); - if (pi->first_factor == NULL) { - D(("strndup failed.")); - ret = PAM_BUF_ERR; - goto done; - } - } - - ret = PAM_SUCCESS; - -done: - if (resp != NULL) { - if (resp[0].resp != NULL) { - sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); - free(resp[0].resp); - } - if (resp[1].resp != NULL) { - sss_erase_mem_securely((void *)resp[1].resp, strlen(resp[1].resp)); - free(resp[1].resp); - } - - free(resp); - resp = NULL; - } - - 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); - sss_erase_mem_securely((void *)answer, strlen(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; -} - -static int prompt_oauth2(pam_handle_t *pamh, struct pam_items *pi) -{ - char *answer = NULL; - char *msg; - int ret; - - if (pi->oauth2_url_complete != NULL) { - ret = asprintf(&msg, _("Authenticate at %1$s and press ENTER."), - pi->oauth2_url_complete); - } else { - ret = asprintf(&msg, _("Authenticate with PIN %1$s at %2$s and press " - "ENTER."), pi->oauth2_pin, pi->oauth2_url); - } - if (ret == -1) { - return PAM_SYSTEM_ERR; - } - - ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, msg, NULL, &answer); - free(msg); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return ret; - } - - /* We don't care about answer here. We just need to notify that the - * authentication has finished. */ - free(answer); - - pi->pam_authtok = strdup(pi->oauth2_pin); - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_OAUTH2; - pi->pam_authtok_size=strlen(pi->oauth2_pin); - - return PAM_SUCCESS; -} - -static int prompt_passkey(pam_handle_t *pamh, struct pam_items *pi, - const char *prompt_interactive, const char *prompt_touch) -{ - int ret; - const struct pam_conv *conv; - const struct pam_message *mesg[4] = { NULL, NULL, NULL, NULL }; - struct pam_message m[4] = { {0}, {0}, {0}, {0} }; - struct pam_response *resp = NULL; - bool kerberos_preauth; - bool prompt_pin; - int pin_idx = 0; - int msg_idx = 0; - size_t needed_size; - - ret = pam_get_item(pamh, PAM_CONV, (const void **) &conv); - if (ret != PAM_SUCCESS) { - return ret; - } - if (conv == NULL || conv->conv == NULL) { - logger(pamh, LOG_ERR, "No conversation function"); - return PAM_SYSTEM_ERR; - } - - kerberos_preauth = pi->passkey_key != NULL ? true : false; - if (!kerberos_preauth) { - m[msg_idx].msg_style = PAM_TEXT_INFO; - m[msg_idx].msg = PASSKEY_LOCAL_AUTH_MSG; - msg_idx++; - } - - if ((strcasecmp(pi->passkey_prompt_pin, "false")) == 0) { - prompt_pin = false; - } else { - prompt_pin = true; - } - - /* Interactive, prompt a message and wait before continuing */ - if (prompt_interactive != NULL && prompt_interactive[0] != '\0') { - m[msg_idx].msg_style = PAM_PROMPT_ECHO_OFF; - m[msg_idx].msg = prompt_interactive; - msg_idx++; - } - - /* Prompt for PIN - * - * If prompt_pin is false but a PIN is set on the device - * we still prompt for PIN */ - if (prompt_pin) { - m[msg_idx].msg_style = PAM_PROMPT_ECHO_OFF; - m[msg_idx].msg = PASSKEY_DEFAULT_PIN_MSG; - pin_idx = msg_idx; - msg_idx++; - } - - /* Prompt to remind the user to touch the device */ - if (prompt_touch != NULL && prompt_touch[0] != '\0') { - m[msg_idx].msg_style = PAM_PROMPT_ECHO_OFF; - m[msg_idx].msg = prompt_touch; - msg_idx++; - } - - mesg[0] = (const struct pam_message *) m; - /* The following assignment might look a bit odd but is recommended in the - * pam_conv man page to make sure that the second argument of the PAM - * conversation function can be interpreted in two different ways. - * Basically it is important that both the actual struct pam_message and - * the pointers to the struct pam_message are arrays. Since the assignment - * makes clear that mesg[] and (*mesg)[] are arrays it should be kept this - * way and not be replaced by other equivalent assignments. */ - for (int i = 1; i < msg_idx; i++) { - mesg[i] = & (( *mesg )[i]); - } - - ret = conv->conv(msg_idx, mesg, &resp, conv->appdata_ptr); - if (ret != PAM_SUCCESS) { - D(("Conversation failure: %s.", pam_strerror(pamh, ret))); - return ret; - } - - if (kerberos_preauth) { - if (!prompt_pin) { - resp[pin_idx].resp = NULL; - } - - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY_KRB; - sss_auth_passkey_calc_size(pi->passkey_prompt_pin, - pi->passkey_key, - resp[pin_idx].resp, - &needed_size); - - pi->pam_authtok = malloc(needed_size); - if (pi->pam_authtok == NULL) { - D(("malloc failed.")); - ret = PAM_BUF_ERR; - goto done; - } - - sss_auth_pack_passkey_blob((uint8_t *)pi->pam_authtok, - pi->passkey_prompt_pin, pi->passkey_key, - resp[pin_idx].resp); - - } else { - if (!prompt_pin) { - /* user verification = false, SSS_AUTHTOK_TYPE_PASSKEY will be reset to - * SSS_AUTHTOK_TYPE_NULL in PAM responder - */ - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY; - pi->pam_authtok = NULL; - pi->pam_authtok_size = 0; - ret = PAM_SUCCESS; - goto done; - } else { - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY; - pi->pam_authtok = strdup(resp[pin_idx].resp); - needed_size = strlen(pi->pam_authtok); - } - } - - pi->pam_authtok_size = needed_size; - - /* Fallback to password auth if no PIN was entered */ - if (prompt_pin) { - if (resp[pin_idx].resp == NULL || resp[pin_idx].resp[0] == '\0') { - ret = EIO; - goto done; - } - } - - ret = PAM_SUCCESS; - -done: - if (resp != NULL) { - if (resp[pin_idx].resp != NULL) { - sss_erase_mem_securely((void *)resp[pin_idx].resp, - strlen(resp[pin_idx].resp)); - free(resp[pin_idx].resp); - } - - free(resp); - resp = NULL; - } - - return ret; -} - -#define SC_PROMPT_FMT "PIN for %s: " - -#ifndef discard_const -#define discard_const(ptr) ((void *)((uintptr_t)(ptr))) -#endif - -#define CERT_SEL_PROMPT_FMT "%s" -#define SEL_TITLE discard_const("Please select a certificate") - -static int prompt_multi_cert_gdm(pam_handle_t *pamh, struct pam_items *pi) -{ -#ifdef HAVE_GDM_PAM_EXTENSIONS - int ret; - size_t cert_count = 0; - size_t c; - const struct pam_conv *conv; - struct cert_auth_info *cai; - GdmPamExtensionChoiceListRequest *request = NULL; - GdmPamExtensionChoiceListResponse *response = NULL; - struct pam_message prompt_message; - const struct pam_message *prompt_messages[1]; - struct pam_response *reply = NULL; - char *prompt; - - if (!GDM_PAM_EXTENSION_SUPPORTED(GDM_PAM_EXTENSION_CHOICE_LIST)) { - return ENOTSUP; - } - - if (pi->cert_list == NULL) { - return EINVAL; - } - - DLIST_FOR_EACH(cai, pi->cert_list) { - cert_count++; - } - - ret = pam_get_item(pamh, PAM_CONV, (const void **)&conv); - if (ret != PAM_SUCCESS) { - ret = EIO; - return ret; - } - - request = calloc(1, GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_SIZE(cert_count)); - if (request == NULL) { - ret = ENOMEM; - goto done; - } - GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_INIT(request, SEL_TITLE, cert_count); - - c = 0; - DLIST_FOR_EACH(cai, pi->cert_list) { - ret = asprintf(&prompt, CERT_SEL_PROMPT_FMT, cai->prompt_str); - if (ret == -1) { - ret = ENOMEM; - goto done; - } - free(cai->choice_list_id); - ret = asprintf(&cai->choice_list_id, "%zu", c); - if (ret == -1) { - cai->choice_list_id = NULL; - free(prompt); - ret = ENOMEM; - goto done; - } - - request->list.items[c].key = cai->choice_list_id; - request->list.items[c++].text = prompt; - } - - GDM_PAM_EXTENSION_MESSAGE_TO_BINARY_PROMPT_MESSAGE(request, - &prompt_message); - prompt_messages[0] = &prompt_message; - - ret = conv->conv(1, prompt_messages, &reply, conv->appdata_ptr); - if (ret != PAM_SUCCESS) { - ret = EIO; - goto done; - } - - ret = EIO; - response = GDM_PAM_EXTENSION_REPLY_TO_CHOICE_LIST_RESPONSE(reply); - if (response->key == NULL) { - goto done; - } - - DLIST_FOR_EACH(cai, pi->cert_list) { - if (strcmp(response->key, cai->choice_list_id) == 0) { - pam_info(pamh, "Certificate ‘%s’ selected", cai->key_id); - pi->selected_cert = cai; - ret = 0; - break; - } - } - -done: - if (request != NULL) { - for (c = 0; c < cert_count; c++) { - free(discard_const(request->list.items[c++].text)); - } - free(request); - } - free(response); - - return ret; -#else - return ENOTSUP; -#endif -} - -#define TEXT_CERT_SEL_PROMPT_FMT "%s\n[%zu]:\n%s\n" -#define TEXT_SEL_TITLE discard_const("Please select a certificate by typing " \ - "the corresponding number\n") - -static int prompt_multi_cert(pam_handle_t *pamh, struct pam_items *pi) -{ - int ret; - size_t cert_count = 0; - size_t tries = 0; - long int resp = -1; - struct cert_auth_info *cai; - char *prompt; - char *tmp; - char *answer; - char *ep; - - /* First check if gdm extension is supported */ - ret = prompt_multi_cert_gdm(pamh, pi); - if (ret != ENOTSUP) { - return ret; - } - - if (pi->cert_list == NULL) { - return EINVAL; - } - - prompt = strdup(TEXT_SEL_TITLE); - if (prompt == NULL) { - return ENOMEM; - } - - DLIST_FOR_EACH(cai, pi->cert_list) { - cert_count++; - ret = asprintf(&tmp, TEXT_CERT_SEL_PROMPT_FMT, prompt, cert_count, - cai->prompt_str); - free(prompt); - if (ret == -1) { - return ENOMEM; - } - - prompt = tmp; - } - - do { - ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_ON, prompt, NULL, - &answer); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - break; - } - - errno = 0; - resp = strtol(answer, &ep, 10); - if (errno == 0 && *ep == '\0' && resp > 0 && resp <= cert_count) { - /* do not free answer ealier because ep is pointing to it */ - free(answer); - break; - } - free(answer); - resp = -1; - } while (++tries < 5); - free(prompt); - - pi->selected_cert = NULL; - ret = ENOENT; - if (resp > 0 && resp <= cert_count) { - cert_count = 0; - DLIST_FOR_EACH(cai, pi->cert_list) { - cert_count++; - if (resp == cert_count) { - pam_info(pamh, "Certificate ‘%s’ selected", cai->key_id); - pi->selected_cert = cai; - ret = 0; - break; - } - } - } - - return ret; -} - -#define SC_INSERT_PROMPT _("Please (re)insert (different) Smartcard") - -static int prompt_sc_pin(pam_handle_t *pamh, struct pam_items *pi) -{ - int ret; - char *answer = NULL; - char *prompt = NULL; - size_t needed_size; - const struct pam_conv *conv; - const struct pam_message *mesg[2] = { NULL, NULL }; - struct pam_message m[2] = { { 0 }, { 0 } }; - struct pam_response *resp = NULL; - struct cert_auth_info *cai = pi->selected_cert; - - if (cai == NULL && (SERVICE_IS_GDM_SMARTCARD(pi) - || (pi->flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH))) { - ret = asprintf(&prompt, SC_INSERT_PROMPT); - } else if (cai == NULL || cai->token_name == NULL - || *cai->token_name == '\0') { - return PAM_SYSTEM_ERR; - } else { - ret = asprintf(&prompt, SC_PROMPT_FMT, cai->token_name); - } - - if (ret == -1) { - D(("asprintf failed.")); - return PAM_SYSTEM_ERR; - } - - if (cai == NULL) { - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, prompt, NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("Conversation failure: %s, ignored", pam_strerror(pamh, ret))); - } - } - - if (pi->user_name_hint) { - ret = pam_get_item(pamh, PAM_CONV, (const void **)&conv); - if (ret != PAM_SUCCESS) { - free(prompt); - return ret; - } - if (conv == NULL || conv->conv == NULL) { - logger(pamh, LOG_ERR, "No conversation function"); - free(prompt); - return PAM_SYSTEM_ERR; - } - - m[0].msg_style = PAM_PROMPT_ECHO_OFF; - m[0].msg = prompt; - m[1].msg_style = PAM_PROMPT_ECHO_ON; - m[1].msg = "User name hint: "; - - mesg[0] = (const struct pam_message *)m; - /* The following assignment might look a bit odd but is recommended in the - * pam_conv man page to make sure that the second argument of the PAM - * conversation function can be interpreted in two different ways. - * Basically it is important that both the actual struct pam_message and - * the pointers to the struct pam_message are arrays. Since the assignment - * makes clear that mesg[] and (*mesg)[] are arrays it should be kept this - * way and not be replaced by other equivalent assignments. */ - mesg[1] = &((*mesg)[1]); - - ret = conv->conv(2, mesg, &resp, conv->appdata_ptr); - free(prompt); - if (ret != PAM_SUCCESS) { - D(("Conversation failure: %s.", pam_strerror(pamh, ret))); - return ret; - } - - if (resp == NULL) { - D(("response expected, but resp==NULL")); - return PAM_SYSTEM_ERR; - } - - if (resp[0].resp == NULL || *(resp[0].resp) == '\0') { - D(("Missing PIN.")); - ret = PAM_CRED_INSUFFICIENT; - goto done; - } - - answer = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); - sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); - free(resp[0].resp); - resp[0].resp = NULL; - if (answer == NULL) { - D(("strndup failed")); - ret = PAM_BUF_ERR; - goto done; - } - - if (resp[1].resp != NULL && *(resp[1].resp) != '\0') { - ret = pam_set_item(pamh, PAM_USER, resp[1].resp); - free(resp[1].resp); - resp[1].resp = NULL; - if (ret != PAM_SUCCESS) { - D(("Failed to set PAM_USER with user name hint [%s]", - pam_strerror(pamh, ret))); - goto done; - } - - ret = pam_get_item(pamh, PAM_USER, (const void **)&(pi->pam_user)); - if (ret != PAM_SUCCESS) { - D(("Failed to get PAM_USER with user name hint [%s]", - pam_strerror(pamh, ret))); - goto done; - } - - pi->pam_user_size = strlen(pi->pam_user) + 1; - } - } else { - ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, prompt, NULL, - &answer); - free(prompt); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return ret; - } - } - - if (cai == NULL) { - /* it is expected that the user just replaces the Smartcard which - * would trigger gdm to restart the PAM module, so it is not - * expected that this part of the code is reached. */ - ret = PAM_AUTHINFO_UNAVAIL; - goto done; - } - - if (answer == NULL || *answer == '\0') { - D(("Missing PIN.")); - ret = PAM_CRED_INSUFFICIENT; - goto done; - } else { - - ret = sss_auth_pack_sc_blob(answer, 0, cai->token_name, 0, - cai->module_name, 0, - cai->key_id, 0, - cai->label, 0, - NULL, 0, &needed_size); - if (ret != EAGAIN) { - D(("sss_auth_pack_sc_blob failed.")); - ret = PAM_BUF_ERR; - goto done; - } - - pi->pam_authtok = malloc(needed_size); - if (pi->pam_authtok == NULL) { - D(("malloc failed.")); - ret = PAM_BUF_ERR; - goto done; - } - - ret = sss_auth_pack_sc_blob(answer, 0, cai->token_name, 0, - cai->module_name, 0, - cai->key_id, 0, - cai->label, 0, - (uint8_t *) pi->pam_authtok, needed_size, - &needed_size); - if (ret != EOK) { - D(("sss_auth_pack_sc_blob failed.")); - free((void *)pi->pam_authtok); - ret = PAM_BUF_ERR; - goto done; - } - - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_SC_PIN; - pi->pam_authtok_size = needed_size; - } - - ret = PAM_SUCCESS; - -done: - if (answer != NULL) { - sss_erase_mem_securely((void *)answer, strlen(answer)); - free(answer); - answer=NULL; - } - - if (resp != NULL) { - if (resp[0].resp != NULL) { - sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); - free(resp[0].resp); - } - if (resp[1].resp != NULL) { - sss_erase_mem_securely((void *)resp[1].resp, strlen(resp[1].resp)); - free(resp[1].resp); - } - - free(resp); - resp = NULL; - } - - return ret; -} - -static int prompt_new_password(pam_handle_t *pamh, struct pam_items *pi) -{ - int ret; - char *answer = NULL; - - ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, - _("New Password: "), - _("Reenter new Password: "), - &answer); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return ret; - } - if (answer == NULL) { - pi->pam_newauthtok = NULL; - pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_EMPTY; - pi->pam_newauthtok_size=0; - } else { - pi->pam_newauthtok = strdup(answer); - sss_erase_mem_securely((void *)answer, strlen(answer)); - free(answer); - answer=NULL; - if (pi->pam_newauthtok == NULL) { - return PAM_BUF_ERR; - } - pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_PASSWORD; - pi->pam_newauthtok_size=strlen(pi->pam_newauthtok); - } - - return PAM_SUCCESS; -} - -static void eval_argv(pam_handle_t *pamh, int argc, const char **argv, - uint32_t *flags, int *retries, bool *quiet_mode, - const char **domains) -{ - char *ep; - - *quiet_mode = false; - - for (; argc-- > 0; ++argv) { - if (strcmp(*argv, "forward_pass") == 0) { - *flags |= PAM_CLI_FLAGS_FORWARD_PASS; - } else if (strcmp(*argv, "use_first_pass") == 0) { - *flags |= PAM_CLI_FLAGS_USE_FIRST_PASS; - } else if (strcmp(*argv, "use_authtok") == 0) { - *flags |= PAM_CLI_FLAGS_USE_AUTHTOK; - } else if (strncmp(*argv, OPT_DOMAINS_KEY, strlen(OPT_DOMAINS_KEY)) == 0) { - if (*(*argv+strlen(OPT_DOMAINS_KEY)) == '\0') { - logger(pamh, LOG_ERR, "Missing argument to option domains."); - *domains = ""; - } else { - *domains = *argv+strlen(OPT_DOMAINS_KEY); - } - - } else if (strncmp(*argv, OPT_RETRY_KEY, strlen(OPT_RETRY_KEY)) == 0) { - if (*(*argv+6) == '\0') { - logger(pamh, LOG_ERR, "Missing argument to option retry."); - *retries = 0; - } else { - errno = 0; - *retries = strtol(*argv+6, &ep, 10); - if (errno != 0) { - D(("strtol failed [%d][%s]", errno, strerror(errno))); - *retries = 0; - } - if (*ep != '\0') { - logger(pamh, LOG_ERR, "Argument to option retry contains " - "extra characters."); - *retries = 0; - } - if (*retries < 0) { - logger(pamh, LOG_ERR, "Argument to option retry must not " - "be negative."); - *retries = 0; - } - } - } else if (strcmp(*argv, "quiet") == 0) { - *quiet_mode = true; - } else if (strcmp(*argv, "allow_chauthtok_by_root") == 0) { - *flags |= PAM_CLI_FLAGS_ALLOW_CHAUTHTOK_BY_ROOT; - } else if (strcmp(*argv, "ignore_unknown_user") == 0) { - *flags |= PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER; - } else if (strcmp(*argv, "ignore_authinfo_unavail") == 0) { - *flags |= PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL; - } else if (strcmp(*argv, "use_2fa") == 0) { - *flags |= PAM_CLI_FLAGS_USE_2FA; - } else if (strcmp(*argv, "allow_missing_name") == 0) { - *flags |= PAM_CLI_FLAGS_ALLOW_MISSING_NAME; - } else if (strcmp(*argv, "prompt_always") == 0) { - *flags |= PAM_CLI_FLAGS_PROMPT_ALWAYS; - } else if (strcmp(*argv, "try_cert_auth") == 0) { - *flags |= PAM_CLI_FLAGS_TRY_CERT_AUTH; - } else if (strcmp(*argv, "require_cert_auth") == 0) { - *flags |= PAM_CLI_FLAGS_REQUIRE_CERT_AUTH; - } else { - logger(pamh, LOG_WARNING, "unknown option: %s", *argv); - } - } - - return; -} - -static int prompt_by_config(pam_handle_t *pamh, struct pam_items *pi) -{ - size_t c; - int ret = PAM_SUCCESS; - - if (pi->pc == NULL || *pi->pc == NULL) { - return PAM_SYSTEM_ERR; - } - - 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: - if (pi->password_prompting) { - ret = prompt_2fa(pamh, pi, true, pc_get_2fa_1st_prompt(pi->pc[c]), - pc_get_2fa_2nd_prompt(pi->pc[c])); - } else { - ret = prompt_2fa(pamh, pi, false, 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_PASSKEY: - ret = prompt_passkey(pamh, pi, - pc_get_passkey_inter_prompt(pi->pc[c]), - pc_get_passkey_touch_prompt(pi->pc[c])); - break; - case PC_TYPE_SC_PIN: - ret = prompt_sc_pin(pamh, pi); - /* Todo: add extra string option */ - break; - default: - ret = PAM_SYSTEM_ERR; - } - - /* 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) -{ - int ret; - const char *pin = NULL; - - if ((flags & PAM_CLI_FLAGS_USE_FIRST_PASS) - || ( pi->pamstack_authtok != NULL - && *(pi->pamstack_authtok) != '\0' - && !(flags & PAM_CLI_FLAGS_PROMPT_ALWAYS))) { - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PAM_STACKED; - pi->pam_authtok = strdup(pi->pamstack_authtok); - if (pi->pam_authtok == NULL) { - D(("option use_first_pass set, but no password found")); - return PAM_BUF_ERR; - } - pi->pam_authtok_size = strlen(pi->pam_authtok); - } else { - if (pi->oauth2_url != NULL) { - /* Prompt config is not supported for OAuth2. */ - ret = prompt_oauth2(pamh, pi); - } else if (pi->pc != NULL) { - ret = prompt_by_config(pamh, pi); - } 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 if (SERVICE_IS_GDM_SMARTCARD(pi) - || (pi->flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH)) { - /* Use pin prompt as fallback for gdm-smartcard */ - ret = prompt_sc_pin(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, true, _("First Factor: "), - _("Second Factor (optional): ")); - } else { - ret = prompt_2fa(pamh, pi, false, _("First Factor: "), - _("Second Factor: ")); - } - } else if (pi->passkey_prompt_pin) { - ret = prompt_passkey(pamh, pi, - _("Insert your passkey device, then press ENTER."), - ""); - /* Fallback to password auth if no PIN was entered */ - if (ret == EIO) { - ret = prompt_password(pamh, pi, _("Password: ")); - if (pi->pam_authtok_size == 0) { - D(("Empty password failure")); - pi->passkey_prompt_pin = NULL; - return PAM_AUTHTOK_ERR; - } - } - } else { - ret = prompt_password(pamh, pi, _("Password: ")); - } - } - if (ret != PAM_SUCCESS) { - D(("failed to get password from user")); - return ret; - } - - if (flags & PAM_CLI_FLAGS_FORWARD_PASS) { - if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_PASSWORD - || pi->pam_authtok_type == SSS_AUTHTOK_TYPE_PAM_STACKED) { - ret = pam_set_item(pamh, PAM_AUTHTOK, pi->pam_authtok); - } else if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_SC_PIN) { - pin = sss_auth_get_pin_from_sc_blob((uint8_t *) pi->pam_authtok, - pi->pam_authtok_size); - if (pin != NULL) { - ret = pam_set_item(pamh, PAM_AUTHTOK, pin); - } else { - ret = PAM_SYSTEM_ERR; - } - } else if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_2FA - && pi->first_factor != NULL) { - ret = pam_set_item(pamh, PAM_AUTHTOK, pi->first_factor); - } else { - ret = PAM_SYSTEM_ERR; - } - if (ret != PAM_SUCCESS) { - D(("Failed to set PAM_AUTHTOK [%s], " - "authtok may not be available for other modules", - pam_strerror(pamh,ret))); - } - } - } - - return PAM_SUCCESS; -} - -static int check_authtok_data(pam_handle_t *pamh, struct pam_items *pi) -{ - int pam_status; - int *authtok_type; - size_t *authtok_size; - char *authtok_data; - - pam_status = pam_get_data(pamh, PAM_SSS_AUTHOK_TYPE, - (const void **) &authtok_type); - if (pam_status != PAM_SUCCESS) { - D(("pam_get_data failed.")); - return EIO; - } - - pam_status = pam_get_data(pamh, PAM_SSS_AUTHOK_SIZE, - (const void **) &authtok_size); - if (pam_status != PAM_SUCCESS) { - D(("pam_get_data failed.")); - return EIO; - } - - pam_status = pam_get_data(pamh, PAM_SSS_AUTHOK_DATA, - (const void **) &authtok_data); - if (pam_status != PAM_SUCCESS) { - D(("pam_get_data failed.")); - return EIO; - } - - pi->pam_authtok = malloc(*authtok_size); - if (pi->pam_authtok == NULL) { - D(("malloc failed.")); - return ENOMEM; - } - memcpy(pi->pam_authtok, authtok_data, *authtok_size); - - pi->pam_authtok_type = *authtok_type; - pi->pam_authtok_size = *authtok_size; - - return 0; -} - -static int keep_authtok_data(pam_handle_t *pamh, struct pam_items *pi) -{ - int pam_status; - int *authtok_type; - size_t *authtok_size; - char *authtok_data; - - authtok_type = malloc(sizeof(int)); - if (authtok_type == NULL) { - D(("malloc failed.")); - return ENOMEM; - } - *authtok_type = pi->pam_authtok_type; - - pam_status = pam_set_data(pamh, PAM_SSS_AUTHOK_TYPE, authtok_type, - free_exp_data); - if (pam_status != PAM_SUCCESS) { - free(authtok_type); - D(("pam_set_data failed.")); - return EIO; - } - - authtok_size = malloc(sizeof(size_t)); - if (authtok_size == NULL) { - D(("malloc failed.")); - return ENOMEM; - } - *authtok_size = pi->pam_authtok_size; - - pam_status = pam_set_data(pamh, PAM_SSS_AUTHOK_SIZE, authtok_size, - free_exp_data); - if (pam_status != PAM_SUCCESS) { - free(authtok_size); - D(("pam_set_data failed.")); - return EIO; - } - - authtok_data = malloc(pi->pam_authtok_size); - if (authtok_data == NULL) { - D(("malloc failed.")); - return ENOMEM; - } - memcpy(authtok_data, pi->pam_authtok, pi->pam_authtok_size); - - pam_status = pam_set_data(pamh, PAM_SSS_AUTHOK_DATA, authtok_data, - free_exp_data); - if (pam_status != PAM_SUCCESS) { - free(authtok_data); - D(("pam_set_data failed.")); - return EIO; - } - - return 0; -} - -static int get_authtok_for_password_change(pam_handle_t *pamh, - struct pam_items *pi, - uint32_t flags, - int pam_flags) -{ - int ret; - const int *exp_data = NULL; - ret = pam_get_data(pamh, PWEXP_FLAG, (const void **) &exp_data); - if (ret != PAM_SUCCESS) { - exp_data = NULL; - } - - if (pam_flags & PAM_PRELIM_CHECK) { - if (!(flags & PAM_CLI_FLAGS_ALLOW_CHAUTHTOK_BY_ROOT) && getuid() == 0 && !exp_data ) - return PAM_SUCCESS; - - 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, true, - _("First Factor (Current Password): "), - _("Second Factor (optional): ")); - } else { - ret = prompt_2fa(pamh, pi, false, - _("First Factor (Current Password): "), - _("Second Factor: ")); - } - } else if ((flags & PAM_CLI_FLAGS_USE_FIRST_PASS) - && check_authtok_data(pamh, pi) != 0) { - if (pi->pamstack_oldauthtok == NULL) { - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; - pi->pam_authtok = NULL; - pi->pam_authtok_size = 0; - } else { - pi->pam_authtok = strdup(pi->pamstack_oldauthtok); - if (pi->pam_authtok == NULL) { - D(("strdup failed")); - return PAM_BUF_ERR; - } - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD; - pi->pam_authtok_size = strlen(pi->pam_authtok); - } - ret = PAM_SUCCESS; - } else { - ret = prompt_password(pamh, pi, _("Current Password: ")); - } - if (ret != PAM_SUCCESS) { - D(("failed to get credentials from user")); - return ret; - } - - ret = pam_set_item(pamh, PAM_OLDAUTHTOK, pi->pam_authtok); - if (ret != PAM_SUCCESS) { - D(("Failed to set PAM_OLDAUTHTOK [%s], " - "oldauthtok may not be available", - pam_strerror(pamh,ret))); - return ret; - } - - if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_2FA) { - ret = keep_authtok_data(pamh, pi); - if (ret != 0) { - D(("Failed to store authtok data to pam handle. Password " - "change might fail.")); - } - } - - return PAM_SUCCESS; - } - - if (check_authtok_data(pamh, pi) != 0) { - if (pi->pamstack_oldauthtok == NULL) { - if (getuid() != 0) { - D(("no password found for chauthtok")); - return PAM_BUF_ERR; - } else { - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; - pi->pam_authtok = NULL; - pi->pam_authtok_size = 0; - } - } else { - pi->pam_authtok = strdup(pi->pamstack_oldauthtok); - if (pi->pam_authtok == NULL) { - D(("strdup failed")); - return PAM_BUF_ERR; - } - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD; - pi->pam_authtok_size = strlen(pi->pam_authtok); - } - } - - if (flags & PAM_CLI_FLAGS_USE_AUTHTOK) { - pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_PASSWORD; - pi->pam_newauthtok = strdup(pi->pamstack_authtok); - if (pi->pam_newauthtok == NULL) { - D(("option use_authtok set, but no new password found")); - return PAM_BUF_ERR; - } - pi->pam_newauthtok_size = strlen(pi->pam_newauthtok); - } else { - ret = prompt_new_password(pamh, pi); - if (ret != PAM_SUCCESS) { - D(("failed to get new password from user")); - return ret; - } - - if (flags & PAM_CLI_FLAGS_FORWARD_PASS) { - ret = pam_set_item(pamh, PAM_AUTHTOK, pi->pam_newauthtok); - if (ret != PAM_SUCCESS) { - D(("Failed to set PAM_AUTHTOK [%s], " - "oldauthtok may not be available", - pam_strerror(pamh,ret))); - } - } - } - - return PAM_SUCCESS; -} - -#define SC_ENTER_LABEL_FMT "Please insert smart card labeled\n %s" -#define SC_ENTER_FMT "Please insert smart card" - -static int check_login_token_name(pam_handle_t *pamh, struct pam_items *pi, - int retries, bool quiet_mode) -{ - int ret; - int pam_status; - char *login_token_name; - char *prompt = NULL; - uint32_t orig_flags = pi->flags; - - login_token_name = getenv("PKCS11_LOGIN_TOKEN_NAME"); - if (login_token_name == NULL - && !(pi->flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH)) { - return PAM_SUCCESS; - } - - if (login_token_name == NULL) { - ret = asprintf(&prompt, SC_ENTER_FMT); - } else { - ret = asprintf(&prompt, SC_ENTER_LABEL_FMT, login_token_name); - } - if (ret == -1) { - return ENOMEM; - } - - pi->flags |= PAM_CLI_FLAGS_REQUIRE_CERT_AUTH; - - /* TODO: check multiple cert case */ - while (pi->cert_list == NULL || pi->cert_list->token_name == NULL - || (login_token_name != NULL - && strcmp(login_token_name, - pi->cert_list->token_name) != 0)) { - - free_cert_list(pi->cert_list); - pi->cert_list = NULL; - if (retries < 0) { - ret = PAM_AUTHINFO_UNAVAIL; - goto done; - } - retries--; - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, prompt, NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - goto done; - } - - pam_status = send_and_receive(pamh, pi, SSS_PAM_PREAUTH, quiet_mode); - if (pam_status != PAM_SUCCESS) { - D(("send_and_receive returned [%d] during pre-auth", pam_status)); - /* - * Since we are waiting for the right Smartcard to be inserted errors - * can be ignored here. - */ - } - } - - ret = PAM_SUCCESS; - -done: - - pi->flags = orig_flags; - free(prompt); - - return ret; -} - -static int pam_sss(enum sss_cli_command task, pam_handle_t *pamh, - int pam_flags, int argc, const char **argv) -{ - int ret; - int pam_status; - struct pam_items pi = { 0 }; - uint32_t flags = 0; - const int *exp_data; - int *pw_exp_data; - bool retry = false; - bool quiet_mode = false; - int retries = 0; - const char *domains = NULL; - - bindtextdomain(PACKAGE, LOCALEDIR); - - D(("Hello pam_sssd: %#x", task)); - - eval_argv(pamh, argc, argv, &flags, &retries, &quiet_mode, &domains); - - /* Fail all authentication on misconfigured domains= parameter. The admin - * probably wanted to restrict authentication, so it's safer to fail */ - if (domains && strcmp(domains, "") == 0) { - return PAM_SYSTEM_ERR; - } - - pi.requested_domains = domains; - - ret = get_pam_items(pamh, flags, &pi); - if (ret != PAM_SUCCESS) { - D(("get items returned error: %s", pam_strerror(pamh,ret))); - if ((flags & PAM_CLI_FLAGS_TRY_CERT_AUTH) - || (flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) ) { - return PAM_AUTHINFO_UNAVAIL; - } - if (flags & PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER && ret == PAM_USER_UNKNOWN) { - ret = PAM_IGNORE; - } - if (flags & PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL - && ret == PAM_AUTHINFO_UNAVAIL) { - ret = PAM_IGNORE; - } - return ret; - } - - do { - retry = false; - - switch(task) { - case SSS_PAM_AUTHENTICATE: - /* - * Only do preauth if - * - PAM_CLI_FLAGS_USE_FIRST_PASS is not set - * - no password is on the stack or PAM_CLI_FLAGS_PROMPT_ALWAYS is set - * - preauth indicator file exists. - */ - if ( !(flags & PAM_CLI_FLAGS_USE_FIRST_PASS) - && (pi.pam_authtok == NULL - || (flags & PAM_CLI_FLAGS_PROMPT_ALWAYS)) - && access(PAM_PREAUTH_INDICATOR, F_OK) == 0) { - - if (flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) { - /* Do not use PAM_CLI_FLAGS_REQUIRE_CERT_AUTH in the first - * SSS_PAM_PREAUTH run. In case a card is already inserted - * we do not have to prompt to insert a card. */ - pi.flags &= ~PAM_CLI_FLAGS_REQUIRE_CERT_AUTH; - pi.flags |= PAM_CLI_FLAGS_TRY_CERT_AUTH; - } - - pam_status = send_and_receive(pamh, &pi, SSS_PAM_PREAUTH, - quiet_mode); - - pi.flags = flags; - if (pam_status != PAM_SUCCESS) { - D(("send_and_receive returned [%d] during pre-auth", - pam_status)); - /* - * Since we are only interested in the result message - * and will always use password authentication - * as a fallback (except for gdm-smartcard), - * errors can be ignored here. - */ - } - } - - if (flags & PAM_CLI_FLAGS_TRY_CERT_AUTH - && pi.cert_list == NULL) { - D(("No certificates for authentication available.")); - overwrite_and_free_pam_items(&pi); - return PAM_AUTHINFO_UNAVAIL; - } - - if (SERVICE_IS_GDM_SMARTCARD(&pi) - || (flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH)) { - ret = check_login_token_name(pamh, &pi, retries, - quiet_mode); - if (ret != PAM_SUCCESS) { - D(("check_login_token_name failed.\n")); - } - } - - ret = get_authtok_for_authentication(pamh, &pi, flags); - if (ret != PAM_SUCCESS) { - D(("failed to get authentication token: %s", - pam_strerror(pamh, ret))); - overwrite_and_free_pam_items(&pi); - return ret; - } - break; - case SSS_PAM_CHAUTHTOK: - /* - * Even if we only want to change the (long term) password - * there are cases where more than the password is needed to - * get the needed privileges in a backend to change the - * password. - * - * E.g. with mandatory 2-factor authentication we have to ask - * not only for the current password but for the second - * factor, e.g. the one-time token value, as well. - * - * The means the preauth step has to be done here as well but - * only if - * - PAM_PRELIM_CHECK is set - * - PAM_CLI_FLAGS_USE_FIRST_PASS is not set - * - no password is on the stack or PAM_CLI_FLAGS_PROMPT_ALWAYS is set - * - preauth indicator file exists. - */ - if ( (pam_flags & PAM_PRELIM_CHECK) - && !(flags & PAM_CLI_FLAGS_USE_FIRST_PASS) - && (pi.pam_authtok == NULL - || (flags & PAM_CLI_FLAGS_PROMPT_ALWAYS)) - && access(PAM_PREAUTH_INDICATOR, F_OK) == 0) { - pam_status = send_and_receive(pamh, &pi, SSS_PAM_PREAUTH, - quiet_mode); - if (pam_status != PAM_SUCCESS) { - D(("send_and_receive returned [%d] during pre-auth", - pam_status)); - /* - * Since we are only interested in the result message - * and will always use password authentication - * as a fallback, errors can be ignored here. - */ - } - } - - ret = get_authtok_for_password_change(pamh, &pi, flags, pam_flags); - if (ret != PAM_SUCCESS) { - D(("failed to get tokens for password change: %s", - pam_strerror(pamh, ret))); - overwrite_and_free_pam_items(&pi); - return ret; - } - - if (pam_flags & PAM_PRELIM_CHECK) { - if (pi.pam_authtok_type == SSS_AUTHTOK_TYPE_2FA) { - /* We cannot validate the credentials with an OTP - * token value during PAM_PRELIM_CHECK because it - * would be invalid for the actual password change. So - * we are done. */ - - overwrite_and_free_pam_items(&pi); - return PAM_SUCCESS; - } - task = SSS_PAM_CHAUTHTOK_PRELIM; - } - break; - case SSS_PAM_ACCT_MGMT: - case SSS_PAM_SETCRED: - case SSS_PAM_OPEN_SESSION: - case SSS_PAM_CLOSE_SESSION: - break; - default: - D(("Illegal task [%#x]", task)); - overwrite_and_free_pam_items(&pi); - return PAM_SYSTEM_ERR; - } - - pam_status = send_and_receive(pamh, &pi, task, quiet_mode); - - if (flags & PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER - && pam_status == PAM_USER_UNKNOWN) { - pam_status = PAM_IGNORE; - } - if (flags & PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL - && pam_status == PAM_AUTHINFO_UNAVAIL) { - pam_status = PAM_IGNORE; - } - - switch (task) { - case SSS_PAM_AUTHENTICATE: - /* We allow sssd to send the return code PAM_NEW_AUTHTOK_REQD during - * authentication, see sss_cli.h for details */ - if (pam_status == PAM_NEW_AUTHTOK_REQD) { - D(("Authtoken expired, trying to change it")); - - pw_exp_data = malloc(sizeof(int)); - if (pw_exp_data == NULL) { - D(("malloc failed.")); - pam_status = PAM_BUF_ERR; - break; - } - *pw_exp_data = 1; - - pam_status = pam_set_data(pamh, PWEXP_FLAG, pw_exp_data, - free_exp_data); - if (pam_status != PAM_SUCCESS) { - D(("pam_set_data failed.")); - } - } - break; - case SSS_PAM_ACCT_MGMT: - if (pam_status == PAM_SUCCESS && - pam_get_data(pamh, PWEXP_FLAG, (const void **) &exp_data) == - PAM_SUCCESS) { - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, - _("Password expired. Change your password now."), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - } - pam_status = PAM_NEW_AUTHTOK_REQD; - } - break; - case SSS_PAM_CHAUTHTOK: - if (pam_status != PAM_SUCCESS && pam_status != PAM_USER_UNKNOWN) { - ret = pam_set_item(pamh, PAM_AUTHTOK, NULL); - if (ret != PAM_SUCCESS) { - D(("Failed to unset PAM_AUTHTOK [%s]", - pam_strerror(pamh,ret))); - } - ret = pam_set_item(pamh, PAM_OLDAUTHTOK, NULL); - if (ret != PAM_SUCCESS) { - D(("Failed to unset PAM_OLDAUTHTOK [%s]", - pam_strerror(pamh,ret))); - } - } - break; - case SSS_PAM_CHAUTHTOK_PRELIM: - if (pam_status == PAM_PERM_DENIED && pi.pam_authtok_size == 0 && - getuid() == 0 && - pam_get_data(pamh, PWEXP_FLAG, (const void **) &exp_data) != - PAM_SUCCESS) { - - ret = select_pw_reset_message(pamh, &pi); - if (ret != 0) { - D(("select_pw_reset_message failed.\n")); - } - } - default: - /* nothing to do */ - break; - } - - overwrite_and_free_pam_items(&pi); - - D(("retries [%d].", retries)); - - if (pam_status != PAM_SUCCESS && - (task == SSS_PAM_AUTHENTICATE || task == SSS_PAM_CHAUTHTOK_PRELIM) && - retries > 0) { - retry = true; - retries--; - - flags &= ~PAM_CLI_FLAGS_USE_FIRST_PASS; - ret = pam_set_item(pamh, PAM_AUTHTOK, NULL); - if (ret != PAM_SUCCESS) { - D(("Failed to unset PAM_AUTHTOK [%s]", - pam_strerror(pamh,ret))); - } - ret = pam_set_item(pamh, PAM_OLDAUTHTOK, NULL); - if (ret != PAM_SUCCESS) { - D(("Failed to unset PAM_OLDAUTHTOK [%s]", - pam_strerror(pamh,ret))); - } - } - } while(retry); - - return pam_status; -} - -PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, - const char **argv ) -{ - return pam_sss(SSS_PAM_AUTHENTICATE, pamh, flags, argc, argv); -} - - -PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, - const char **argv ) -{ - return pam_sss(SSS_PAM_SETCRED, pamh, flags, argc, argv); -} - -PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, - const char **argv ) -{ - return pam_sss(SSS_PAM_ACCT_MGMT, pamh, flags, argc, argv); -} - -PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, - const char **argv ) -{ - return pam_sss(SSS_PAM_CHAUTHTOK, pamh, flags, argc, argv); -} - -PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, - const char **argv ) -{ - return pam_sss(SSS_PAM_OPEN_SESSION, pamh, flags, argc, argv); -} - -PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, - const char **argv ) -{ - return pam_sss(SSS_PAM_CLOSE_SESSION, pamh, flags, argc, argv); -} - - -#ifdef PAM_STATIC - -/* static module data */ - -struct pam_module _pam_sssd_modstruct ={ - "pam_sssd", - pam_sm_authenticate, - pam_sm_setcred, - pam_sm_acct_mgmt, - pam_sm_open_session, - pam_sm_close_session, - pam_sm_chauthtok -}; - -#endif +/* + Authors: + Sumit Bose + + Copyright (C) 2009 Red Hat + Copyright (C) 2010, rhafer@suse.de, Novell Inc. + + 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 . +*/ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef HAVE_GDM_PAM_EXTENSIONS +#include +#endif + +#include "sss_pam_compat.h" +#include "sss_pam_macros.h" + +#include "sss_cli.h" +#include "pam_message.h" +#include "util/atomic_io.h" +#include "util/authtok-utils.h" +#include "util/dlinklist.h" +#include "util/memory_erase.h" + +#include +#define _(STRING) dgettext (PACKAGE, STRING) +#define _n(SINGULAR, PLURAL, VALUE) dngettext(PACKAGE, SINGULAR, PLURAL, VALUE) + +#define PWEXP_FLAG "pam_sss:password_expired_flag" +#define FD_DESTRUCTOR "pam_sss:fd_destructor" +#define PAM_SSS_AUTHOK_TYPE "pam_sss:authtok_type" +#define PAM_SSS_AUTHOK_SIZE "pam_sss:authtok_size" +#define PAM_SSS_AUTHOK_DATA "pam_sss:authtok_data" + +#define PW_RESET_MSG_FILENAME_TEMPLATE SSSD_CONF_DIR"/customize/%s/pam_sss_pw_reset_message.%s" +#define PW_RESET_MSG_MAX_SIZE 4096 + +#define OPT_RETRY_KEY "retry=" +#define OPT_DOMAINS_KEY "domains=" + +#define EXP_ACC_MSG _("Permission denied. ") +#define SRV_MSG _("Server message: ") +#define PASSKEY_LOCAL_AUTH_MSG _("Kerberos TGT will not be granted upon login, user experience will be affected.") +#define PASSKEY_DEFAULT_PIN_MSG _("Enter PIN:") + +#define DEBUG_MGS_LEN 1024 +#define MAX_AUTHTOK_SIZE (1024*1024) +#define CHECK_AND_RETURN_PI_STRING(s) ((s != NULL && *s != '\0')? s : "(not available)") +#define SERVICE_IS_GDM_SMARTCARD(pitem) (strcmp((pitem)->pam_service, \ + "gdm-smartcard") == 0) + +static void logger(pam_handle_t *pamh, int level, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + +#ifdef DEBUG + va_list apd; + char debug_msg[DEBUG_MGS_LEN]; + int ret; + va_copy(apd, ap); + + ret = vsnprintf(debug_msg, DEBUG_MGS_LEN, fmt, apd); + if (ret >= DEBUG_MGS_LEN) { + D(("the following message is truncated: %s", debug_msg)); + } else if (ret < 0) { + D(("vsnprintf failed to format debug message!")); + } else { + D((debug_msg)); + } + + va_end(apd); +#endif + + pam_vsyslog(pamh, LOG_AUTHPRIV|level, fmt, ap); + + va_end(ap); +} + +static void free_exp_data(pam_handle_t *pamh, void *ptr, int err) +{ + free(ptr); +} + +static void close_fd(pam_handle_t *pamh, void *ptr, int err) +{ +#ifdef PAM_DATA_REPLACE + if (err & PAM_DATA_REPLACE) { + /* Nothing to do */ + return; + } +#endif /* PAM_DATA_REPLACE */ + + D(("Closing the fd")); + + sss_pam_lock(); + sss_cli_close_socket(); + sss_pam_unlock(); +} + +struct cert_auth_info { + char *cert_user; + char *cert; + char *token_name; + char *module_name; + char *key_id; + char *label; + char *prompt_str; + char *pam_cert_user; + char *choice_list_id; + struct cert_auth_info *prev; + struct cert_auth_info *next; +}; + +static void free_cai(struct cert_auth_info *cai) +{ + if (cai != NULL) { + free(cai->cert_user); + free(cai->cert); + free(cai->token_name); + free(cai->module_name); + free(cai->key_id); + free(cai->label); + free(cai->prompt_str); + free(cai->choice_list_id); + free(cai); + } +} + +static void free_cert_list(struct cert_auth_info *list) +{ + struct cert_auth_info *cai; + struct cert_auth_info *cai_next; + + if (list != NULL) { + DLIST_FOR_EACH_SAFE(cai, cai_next, list) { + DLIST_REMOVE(list, cai); + free_cai(cai); + } + } +} + +static void overwrite_and_free_authtoks(struct pam_items *pi) +{ + if (pi->pam_authtok != NULL) { + sss_erase_mem_securely((void *)pi->pam_authtok, pi->pam_authtok_size); + free((void *)pi->pam_authtok); + pi->pam_authtok = NULL; + } + + if (pi->pam_newauthtok != NULL) { + sss_erase_mem_securely((void *)pi->pam_newauthtok, pi->pam_newauthtok_size); + free((void *)pi->pam_newauthtok); + pi->pam_newauthtok = NULL; + } + + if (pi->first_factor != NULL) { + sss_erase_mem_securely((void *)pi->first_factor, strlen(pi->first_factor)); + free((void *)pi->first_factor); + pi->first_factor = NULL; + } + + pi->pamstack_authtok = NULL; + pi->pamstack_oldauthtok = NULL; +} + +static void overwrite_and_free_pam_items(struct pam_items *pi) +{ + overwrite_and_free_authtoks(pi); + + free(pi->domain_name); + pi->domain_name = NULL; + + free(pi->otp_vendor); + pi->otp_vendor = NULL; + + free(pi->otp_token_id); + pi->otp_token_id = NULL; + + free(pi->otp_challenge); + pi->otp_challenge = NULL; + + free(pi->passkey_key); + pi->passkey_key = NULL; + + free(pi->passkey_prompt_pin); + pi->passkey_prompt_pin = NULL; + + free(pi->passkey_devinfo); + pi->passkey_devinfo = NULL; + + + 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) { + if (s1 == NULL && s2 == NULL) return 0; + if (s1 == NULL && s2 != NULL) return -1; + if (s1 != NULL && s2 == NULL) return 1; + return strcmp(s1, s2); +} + +enum { + SSS_PAM_CONV_DONE = 0, + SSS_PAM_CONV_STD, + SSS_PAM_CONV_REENTER, +}; + +static int do_pam_conversation(pam_handle_t *pamh, const int msg_style, + const char *msg, + const char *reenter_msg, + char **_answer) +{ + int ret; + int state = SSS_PAM_CONV_STD; + const struct pam_conv *conv; + const struct pam_message *mesg[1]; + struct pam_message *pam_msg; + struct pam_response *resp=NULL; + char *answer = NULL; + + if ((msg_style == PAM_TEXT_INFO || msg_style == PAM_ERROR_MSG) && + msg == NULL) return PAM_SYSTEM_ERR; + + if ((msg_style == PAM_PROMPT_ECHO_OFF || + msg_style == PAM_PROMPT_ECHO_ON) && + (msg == NULL || _answer == NULL)) return PAM_SYSTEM_ERR; + + if (msg_style == PAM_TEXT_INFO || msg_style == PAM_ERROR_MSG) { + logger(pamh, LOG_INFO, "User %s message: %s", + msg_style == PAM_TEXT_INFO ? "info" : "error", + msg); + } + + ret=pam_get_item(pamh, PAM_CONV, (const void **) &conv); + if (ret != PAM_SUCCESS) return ret; + if (conv == NULL || conv->conv == NULL) { + logger(pamh, LOG_ERR, "No conversation function"); + return PAM_SYSTEM_ERR; + } + + do { + pam_msg = malloc(sizeof(struct pam_message)); + if (pam_msg == NULL) { + D(("Malloc failed.")); + ret = PAM_SYSTEM_ERR; + goto failed; + } + + pam_msg->msg_style = msg_style; + if (state == SSS_PAM_CONV_REENTER) { + pam_msg->msg = reenter_msg; + } else { + pam_msg->msg = msg; + } + + mesg[0] = (const struct pam_message *) pam_msg; + + ret=conv->conv(1, mesg, &resp, + conv->appdata_ptr); + free(pam_msg); + if (ret != PAM_SUCCESS) { + D(("Conversation failure: %s.", pam_strerror(pamh,ret))); + goto failed; + } + + if (msg_style == PAM_PROMPT_ECHO_OFF || + msg_style == PAM_PROMPT_ECHO_ON) { + if (resp == NULL) { + D(("response expected, but resp==NULL")); + ret = PAM_SYSTEM_ERR; + goto failed; + } + + if (state == SSS_PAM_CONV_REENTER) { + if (null_strcmp(answer, resp[0].resp) != 0) { + logger(pamh, LOG_NOTICE, "Passwords do not match."); + sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); + free(resp[0].resp); + if (answer != NULL) { + sss_erase_mem_securely((void *) answer, strlen(answer)); + free(answer); + answer = NULL; + } + ret = do_pam_conversation(pamh, PAM_ERROR_MSG, + _("Passwords do not match"), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + ret = PAM_SYSTEM_ERR; + goto failed; + } + ret = PAM_CRED_ERR; + goto failed; + } + sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); + free(resp[0].resp); + } else { + if (resp[0].resp == NULL) { + D(("Empty password")); + answer = NULL; + } else { + answer = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); + sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); + free(resp[0].resp); + if(answer == NULL) { + D(("strndup failed")); + ret = PAM_BUF_ERR; + goto failed; + } + } + } + free(resp); + resp = NULL; + } + + if (reenter_msg != NULL && state == SSS_PAM_CONV_STD) { + state = SSS_PAM_CONV_REENTER; + } else { + state = SSS_PAM_CONV_DONE; + } + } while (state != SSS_PAM_CONV_DONE); + + if (_answer) *_answer = answer; + return PAM_SUCCESS; + +failed: + free(answer); + return ret; + +} + +static errno_t display_pw_reset_message(pam_handle_t *pamh, + const char *domain_name, + const char *suffix) +{ + int ret; + struct stat stat_buf; + char *msg_buf = NULL; + int fd = -1; + size_t size; + size_t total_len; + char *filename = NULL; + + if (strchr(suffix, '/') != NULL || strchr(domain_name, '/') != NULL) { + D(("Suffix [%s] or domain name [%s] contain illegal character.", suffix, + domain_name)); + return EINVAL; + } + + size = sizeof(PW_RESET_MSG_FILENAME_TEMPLATE) + strlen(domain_name) + + strlen(suffix); + filename = malloc(size); + if (filename == NULL) { + D(("malloc failed.")); + ret = ENOMEM; + goto done; + } + ret = snprintf(filename, size, PW_RESET_MSG_FILENAME_TEMPLATE, domain_name, + suffix); + if (ret < 0 || ret >= size) { + D(("snprintf failed.")); + ret = EFAULT; + goto done; + } + + fd = open(filename, O_RDONLY); + if (fd == -1) { + ret = errno; + D(("open failed [%d][%s].\n", ret, strerror(ret))); + goto done; + } + + ret = fstat(fd, &stat_buf); + if (ret == -1) { + ret = errno; + D(("fstat failed [%d][%s].", ret, strerror(ret))); + goto done; + } + + if (!S_ISREG(stat_buf.st_mode)) { + logger(pamh, LOG_ERR, + "Password reset message file is not a regular file."); + ret = EINVAL; + goto done; + } + + if (stat_buf.st_uid != 0 || stat_buf.st_gid != 0 || + (stat_buf.st_mode & ~S_IFMT) != 0644) { + logger(pamh, LOG_ERR,"Permission error, " + "file [%s] must be owned by root with permissions 0644.", + filename); + ret = EPERM; + goto done; + } + + if (stat_buf.st_size > PW_RESET_MSG_MAX_SIZE) { + logger(pamh, LOG_ERR, "Password reset message file is too large."); + ret = EFBIG; + goto done; + } + + msg_buf = malloc(stat_buf.st_size + 1); + if (msg_buf == NULL) { + D(("malloc failed.")); + ret = ENOMEM; + goto done; + } + + errno = 0; + total_len = sss_atomic_read_s(fd, msg_buf, stat_buf.st_size); + if (total_len == -1) { + ret = errno; + D(("read failed [%d][%s].", ret, strerror(ret))); + goto done; + } + + ret = close(fd); + fd = -1; + if (ret == -1) { + ret = errno; + D(("close failed [%d][%s].", ret, strerror(ret))); + } + + if (total_len != stat_buf.st_size) { + D(("read fewer bytes [%d] than expected [%d].", total_len, + stat_buf.st_size)); + ret = EIO; + goto done; + } + + msg_buf[stat_buf.st_size] = '\0'; + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, msg_buf, NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + } + +done: + if (fd != -1) { + close(fd); + } + free(msg_buf); + free(filename); + + return ret; +} + +static errno_t select_pw_reset_message(pam_handle_t *pamh, struct pam_items *pi) +{ + int ret; + char *locale; + const char *domain_name; + + domain_name = pi->domain_name; + if (domain_name == NULL || *domain_name == '\0') { + D(("Domain name is unknown.")); + return EINVAL; + } + + locale = setlocale(LC_MESSAGES, NULL); + + ret = -1; + if (locale != NULL) { + ret = display_pw_reset_message(pamh, domain_name, locale); + } + + if (ret != 0) { + ret = display_pw_reset_message(pamh, domain_name, "txt"); + } + + if (ret != 0) { + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, + _("Password reset by root is not supported."), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + } + } + + return ret; +} + +static int user_info_offline_auth(pam_handle_t *pamh, size_t buflen, + uint8_t *buf) +{ + int ret; + int64_t expire_date; + struct tm tm; + char expire_str[128]; + char user_msg[256]; + + expire_str[0] = '\0'; + + if (buflen != sizeof(uint32_t) + sizeof(int64_t)) { + D(("User info response data has the wrong size")); + return PAM_BUF_ERR; + } + + memcpy(&expire_date, buf + sizeof(uint32_t), sizeof(int64_t)); + + if (expire_date > 0) { + if (localtime_r((time_t *) &expire_date, &tm) != NULL) { + ret = strftime(expire_str, sizeof(expire_str), "%c", &tm); + if (ret == 0) { + D(("strftime failed.")); + expire_str[0] = '\0'; + } + } else { + D(("localtime_r failed")); + } + } + + ret = snprintf(user_msg, sizeof(user_msg), "%s%s%s.", + _("Authenticated with cached credentials"), + expire_str[0] ? _(", your cached password will expire at: ") : "", + expire_str[0] ? expire_str : ""); + if (ret < 0 || ret >= sizeof(user_msg)) { + D(("snprintf failed.")); + return PAM_SYSTEM_ERR; + } + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_grace_login(pam_handle_t *pamh, + size_t buflen, + uint8_t *buf) +{ + int ret; + uint32_t grace; + char user_msg[256]; + + if (buflen != 2* sizeof(uint32_t)) { + D(("User info response data has the wrong size")); + return PAM_BUF_ERR; + } + memcpy(&grace, buf + sizeof(uint32_t), sizeof(uint32_t)); + ret = snprintf(user_msg, sizeof(user_msg), + _("Your password has expired. " + "You have %1$d grace login(s) remaining."), + grace); + if (ret < 0 || ret >= sizeof(user_msg)) { + D(("snprintf failed.")); + return PAM_SYSTEM_ERR; + } + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); + + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +#define MINSEC 60 +#define HOURSEC (60*MINSEC) +#define DAYSEC (24*HOURSEC) +static int user_info_expire_warn(pam_handle_t *pamh, + size_t buflen, + uint8_t *buf) +{ + int ret; + uint32_t expire; + char user_msg[256]; + const char* unit; + + if (buflen != 2* sizeof(uint32_t)) { + D(("User info response data has the wrong size")); + return PAM_BUF_ERR; + } + memcpy(&expire, buf + sizeof(uint32_t), sizeof(uint32_t)); + /* expire == 0 indicates the password expired */ + if (expire != 0) { + if (expire >= DAYSEC) { + expire /= DAYSEC; + unit = _n("day", "days", expire); + } else if (expire >= HOURSEC) { + expire /= HOURSEC; + unit = _n("hour", "hours", expire); + } else if (expire >= MINSEC) { + expire /= MINSEC; + unit = _n("minute", "minutes", expire); + } else { + unit = _n("second", "seconds", expire); + } + + ret = snprintf(user_msg, sizeof(user_msg), + _("Your password will expire in %1$d %2$s."), expire, unit); + } else { + ret = snprintf(user_msg, sizeof(user_msg), + _("Your password has expired.")); + } + + if (ret < 0 || ret >= sizeof(user_msg)) { + D(("snprintf failed.")); + return PAM_SYSTEM_ERR; + } + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); + + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_offline_auth_delayed(pam_handle_t *pamh, size_t buflen, + uint8_t *buf) +{ + int ret; + int64_t delayed_until; + struct tm tm; + char delay_str[128]; + char user_msg[256]; + + delay_str[0] = '\0'; + + if (buflen != sizeof(uint32_t) + sizeof(int64_t)) { + D(("User info response data has the wrong size")); + return PAM_BUF_ERR; + } + + memcpy(&delayed_until, buf + sizeof(uint32_t), sizeof(int64_t)); + + if (delayed_until <= 0) { + D(("User info response data has an invalid value")); + return PAM_BUF_ERR; + } + + if (localtime_r((time_t *) &delayed_until, &tm) != NULL) { + ret = strftime(delay_str, sizeof(delay_str), "%c", &tm); + if (ret == 0) { + D(("strftime failed.")); + delay_str[0] = '\0'; + } + } else { + D(("localtime_r failed")); + } + + ret = snprintf(user_msg, sizeof(user_msg), "%s%s.", + _("Authentication is denied until: "), + delay_str); + if (ret < 0 || ret >= sizeof(user_msg)) { + D(("snprintf failed.")); + return PAM_SYSTEM_ERR; + } + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_offline_chpass(pam_handle_t *pamh) +{ + int ret; + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, + _("System is offline, password change not possible"), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_otp_chpass(pam_handle_t *pamh) +{ + int ret; + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, + _("After changing the OTP password, you need to " + "log out and back in order to acquire a ticket"), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_pin_locked(pam_handle_t *pamh) +{ + int ret; + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, _("PIN locked"), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_no_krb_tgt(pam_handle_t *pamh) +{ + int ret; + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, + _("No Kerberos TGT granted as " + "the server does not support this method. " + "Your single-sign on(SSO) experience will " + "be affected."), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_account_expired(pam_handle_t *pamh, size_t buflen, + uint8_t *buf) +{ + int ret; + uint32_t msg_len; + char *user_msg; + size_t bufsize = 0; + + /* resp_type and length of message are expected to be in buf */ + if (buflen < 2* sizeof(uint32_t)) { + D(("User info response data is too short")); + return PAM_BUF_ERR; + } + + /* msg_len = legth of message */ + memcpy(&msg_len, buf + sizeof(uint32_t), sizeof(uint32_t)); + + if (buflen != 2* sizeof(uint32_t) + msg_len) { + D(("User info response data has the wrong size")); + return PAM_BUF_ERR; + } + + bufsize = strlen(EXP_ACC_MSG) + 1; + + if (msg_len > 0) { + bufsize += strlen(SRV_MSG) + msg_len; + } + + user_msg = (char *)malloc(sizeof(char) * bufsize); + if (!user_msg) { + D(("Out of memory.")); + return PAM_SYSTEM_ERR; + } + + ret = snprintf(user_msg, bufsize, "%s%s%.*s", + EXP_ACC_MSG, + msg_len > 0 ? SRV_MSG : "", + (int)msg_len, + msg_len > 0 ? (char *)(buf + 2 * sizeof(uint32_t)) : "" ); + if (ret < 0 || ret > bufsize) { + D(("snprintf failed.")); + + free(user_msg); + return PAM_SYSTEM_ERR; + } + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); + free(user_msg); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_chpass_error(pam_handle_t *pamh, size_t buflen, + uint8_t *buf) +{ + int ret; + uint32_t msg_len; + char *user_msg; + size_t bufsize = 0; + + if (buflen < 2* sizeof(uint32_t)) { + D(("User info response data is too short")); + return PAM_BUF_ERR; + } + + memcpy(&msg_len, buf + sizeof(uint32_t), sizeof(uint32_t)); + + if (buflen != 2* sizeof(uint32_t) + msg_len) { + D(("User info response data has the wrong size")); + return PAM_BUF_ERR; + } + + bufsize = strlen(_("Password change failed. ")) + 1; + + if (msg_len > 0) { + bufsize += strlen(_("Server message: ")) + msg_len; + } + + user_msg = (char *)malloc(sizeof(char) * bufsize); + if (!user_msg) { + D(("Out of memory.")); + return PAM_SYSTEM_ERR; + } + + ret = snprintf(user_msg, bufsize, "%s%s%.*s", + _("Password change failed. "), + msg_len > 0 ? _("Server message: ") : "", + (int)msg_len, + msg_len > 0 ? (char *)(buf + 2 * sizeof(uint32_t)) : "" ); + if (ret < 0 || ret > bufsize) { + D(("snprintf failed.")); + + free(user_msg); + return PAM_SYSTEM_ERR; + } + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); + free(user_msg); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int eval_user_info_response(pam_handle_t *pamh, size_t buflen, + uint8_t *buf) +{ + int ret; + uint32_t type; + + if (buflen < sizeof(uint32_t)) { + D(("User info response data is too short")); + return PAM_BUF_ERR; + } + + memcpy(&type, buf, sizeof(uint32_t)); + + switch(type) { + case SSS_PAM_USER_INFO_OFFLINE_AUTH: + ret = user_info_offline_auth(pamh, buflen, buf); + break; + case SSS_PAM_USER_INFO_GRACE_LOGIN: + ret = user_info_grace_login(pamh, buflen, buf); + break; + case SSS_PAM_USER_INFO_EXPIRE_WARN: + ret = user_info_expire_warn(pamh, buflen, buf); + break; + case SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED: + ret = user_info_offline_auth_delayed(pamh, buflen, buf); + break; + case SSS_PAM_USER_INFO_OFFLINE_CHPASS: + ret = user_info_offline_chpass(pamh); + break; + case SSS_PAM_USER_INFO_OTP_CHPASS: + ret = user_info_otp_chpass(pamh); + break; + case SSS_PAM_USER_INFO_CHPASS_ERROR: + ret = user_info_chpass_error(pamh, buflen, buf); + break; + case SSS_PAM_USER_INFO_PIN_LOCKED: + ret = user_info_pin_locked(pamh); + break; + case SSS_PAM_USER_INFO_ACCOUNT_EXPIRED: + ret = user_info_account_expired(pamh, buflen, buf); + break; + case SSS_PAM_USER_INFO_NO_KRB_TGT: + ret = user_info_no_krb_tgt(pamh); + break; + default: + D(("Unknown user info type [%d]", type)); + ret = PAM_SYSTEM_ERR; + } + + return ret; +} + +static int parse_cert_info(struct pam_items *pi, uint8_t *buf, size_t len, + size_t *p, const char **cert_user, + const char **pam_cert_user) +{ + struct cert_auth_info *cai = NULL; + size_t offset; + int ret; + + if (buf[*p + (len - 1)] != '\0') { + D(("cert info does not end with \\0.")); + return EINVAL; + } + + cai = calloc(1, sizeof(struct cert_auth_info)); + if (cai == NULL) { + return ENOMEM; + } + + cai->cert_user = strdup((char *) &buf[*p]); + if (cai->cert_user == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + if (cert_user != NULL) { + *cert_user = cai->cert_user; + } + + offset = strlen(cai->cert_user) + 1; + if (offset >= len) { + D(("Cert message size mismatch")); + ret = EINVAL; + goto done; + } + + cai->token_name = strdup((char *) &buf[*p + offset]); + if (cai->token_name == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + + offset += strlen(cai->token_name) + 1; + if (offset >= len) { + D(("Cert message size mismatch")); + ret = EINVAL; + goto done; + } + + cai->module_name = strdup((char *) &buf[*p + offset]); + if (cai->module_name == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + + offset += strlen(cai->module_name) + 1; + if (offset >= len) { + D(("Cert message size mismatch")); + ret = EINVAL; + goto done; + } + + cai->key_id = strdup((char *) &buf[*p + offset]); + if (cai->key_id == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + + offset += strlen(cai->key_id) + 1; + if (offset >= len) { + D(("Cert message size mismatch")); + ret = EINVAL; + goto done; + } + + cai->label = strdup((char *) &buf[*p + offset]); + if (cai->label == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + + offset += strlen(cai->label) + 1; + if (offset >= len) { + D(("Cert message size mismatch")); + ret = EINVAL; + goto done; + } + + cai->prompt_str = strdup((char *) &buf[*p + offset]); + if (cai->prompt_str == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + + offset += strlen(cai->prompt_str) + 1; + if (offset >= len) { + D(("Cert message size mismatch")); + ret = EINVAL; + goto done; + } + + cai->pam_cert_user = strdup((char *) &buf[*p + offset]); + if (cai->pam_cert_user == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + if (pam_cert_user != NULL) { + *pam_cert_user = cai->pam_cert_user; + } + + D(("cert user: [%s] token name: [%s] module: [%s] key id: [%s] " + "prompt: [%s] pam cert user: [%s]", + cai->cert_user, cai->token_name, cai->module_name, + cai->key_id, cai->prompt_str, cai->pam_cert_user)); + + DLIST_ADD(pi->cert_list, cai); + ret = 0; + +done: + if (ret != 0) { + free_cai(cai); + } + + return ret; +} + +static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf, + struct pam_items *pi) +{ + int ret; + size_t p=0; + char *env_item; + int32_t c; + int32_t type; + int32_t len; + int32_t pam_status; + size_t offset; + const char *cert_user; + const char *pam_cert_user; + + if (buflen < (2*sizeof(int32_t))) { + D(("response buffer is too small")); + return PAM_BUF_ERR; + } + + memcpy(&pam_status, buf+p, sizeof(int32_t)); + p += sizeof(int32_t); + + + memcpy(&c, buf+p, sizeof(int32_t)); + p += sizeof(int32_t); + + while(c>0) { + if (buflen < (p+2*sizeof(int32_t))) { + D(("response buffer is too small")); + return PAM_BUF_ERR; + } + + memcpy(&type, buf+p, sizeof(int32_t)); + p += sizeof(int32_t); + + memcpy(&len, buf+p, sizeof(int32_t)); + p += sizeof(int32_t); + + if (buflen < (p + len)) { + D(("response buffer is too small")); + return PAM_BUF_ERR; + } + + switch(type) { + case SSS_PAM_SYSTEM_INFO: + if (buf[p + (len -1)] != '\0') { + D(("system info does not end with \\0.")); + break; + } + logger(pamh, LOG_INFO, "system info: [%s]", &buf[p]); + break; + case SSS_PAM_DOMAIN_NAME: + if (buf[p + (len -1)] != '\0') { + D(("domain name does not end with \\0.")); + break; + } + D(("domain name: [%s]", &buf[p])); + free(pi->domain_name); + pi->domain_name = strdup((char *) &buf[p]); + if (pi->domain_name == NULL) { + D(("strdup failed")); + } + break; + case SSS_ENV_ITEM: + case SSS_PAM_ENV_ITEM: + case SSS_ALL_ENV_ITEM: + if (buf[p + (len -1)] != '\0') { + D(("env item does not end with \\0.")); + break; + } + + D(("env item: [%s]", &buf[p])); + if (type == SSS_PAM_ENV_ITEM || type == SSS_ALL_ENV_ITEM) { + ret = pam_putenv(pamh, (char *)&buf[p]); + if (ret != PAM_SUCCESS) { + D(("pam_putenv failed.")); + break; + } + } + + if (type == SSS_ENV_ITEM || type == SSS_ALL_ENV_ITEM) { + env_item = strdup((char *)&buf[p]); + if (env_item == NULL) { + D(("strdup failed")); + break; + } + ret = putenv(env_item); + if (ret == -1) { + D(("putenv failed.")); + break; + } + } + break; + case SSS_PAM_USER_INFO: + ret = eval_user_info_response(pamh, len, &buf[p]); + if (ret != PAM_SUCCESS) { + D(("eval_user_info_response failed")); + } + break; + case SSS_PAM_TEXT_MSG: + if (buf[p + (len -1)] != '\0') { + D(("system info does not end with \\0.")); + break; + } + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, (char *) &buf[p], + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + } + break; + case SSS_OTP: + D(("OTP was used, removing authtokens.")); + overwrite_and_free_authtoks(pi); + ret = pam_set_item(pamh, PAM_AUTHTOK, NULL); + if (ret != PAM_SUCCESS) { + D(("Failed to remove PAM_AUTHTOK after using otp [%s]", + pam_strerror(pamh,ret))); + } + break; + case SSS_PAM_OTP_INFO: + if (buf[p + (len - 1)] != '\0') { + D(("otp info does not end with \\0.")); + break; + } + + free(pi->otp_vendor); + pi->otp_vendor = strdup((char *) &buf[p]); + if (pi->otp_vendor == NULL) { + D(("strdup failed")); + break; + } + + offset = strlen(pi->otp_vendor) + 1; + if (offset >= len) { + D(("OTP message size mismatch")); + free(pi->otp_vendor); + pi->otp_vendor = NULL; + break; + } + free(pi->otp_token_id); + pi->otp_token_id = strdup((char *) &buf[p + offset]); + if (pi->otp_token_id == NULL) { + D(("strdup failed")); + break; + } + + offset += strlen(pi->otp_token_id) + 1; + if (offset >= len) { + D(("OTP message size mismatch")); + free(pi->otp_token_id); + pi->otp_token_id = NULL; + break; + } + free(pi->otp_challenge); + pi->otp_challenge = strdup((char *) &buf[p + offset]); + if (pi->otp_challenge == NULL) { + D(("strdup failed")); + break; + } + + break; + case SSS_PAM_CERT_INFO: + case SSS_PAM_CERT_INFO_WITH_HINT: + if (buf[p + (len - 1)] != '\0') { + D(("cert info does not end with \\0.")); + break; + } + + if (type == SSS_PAM_CERT_INFO_WITH_HINT) { + pi->user_name_hint = true; + } else { + pi->user_name_hint = false; + } + + ret = parse_cert_info(pi, buf, len, &p, &cert_user, + &pam_cert_user); + if (ret != 0) { + D(("Failed to parse cert info")); + break; + } + + if ((pi->pam_user == NULL || *(pi->pam_user) == '\0') + && *cert_user != '\0' && *pam_cert_user != '\0') { + ret = pam_set_item(pamh, PAM_USER, pam_cert_user); + if (ret != PAM_SUCCESS) { + D(("Failed to set PAM_USER during " + "Smartcard authentication [%s]", + pam_strerror(pamh, ret))); + break; + } + + pi->pam_user = cert_user; + pi->pam_user_size = strlen(pi->pam_user) + 1; + } + break; + case SSS_PASSWORD_PROMPTING: + 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; + case SSS_CHILD_KEEP_ALIVE: + memcpy(&pi->child_pid, &buf[p], len); + break; + case SSS_PAM_OAUTH2_INFO: + if (buf[p + (len - 1)] != '\0') { + D(("oauth2 info does not end with \\0.")); + break; + } + + free(pi->oauth2_url); + pi->oauth2_url = strdup((char *) &buf[p]); + if (pi->oauth2_url == NULL) { + D(("strdup failed")); + break; + } + + offset = strlen(pi->oauth2_url) + 1; + if (offset >= len) { + D(("OAuth2 message size mismatch")); + free(pi->oauth2_url); + pi->oauth2_url = NULL; + break; + } + + free(pi->oauth2_url_complete); + pi->oauth2_url_complete = strdup((char *) &buf[p + offset]); + if (pi->oauth2_url_complete == NULL) { + D(("strdup failed")); + break; + } + + offset = offset + strlen(pi->oauth2_url_complete) + 1; + if (offset >= len) { + D(("OAuth2 message size mismatch")); + free(pi->oauth2_url_complete); + pi->oauth2_url_complete = NULL; + break; + } + + /* This field is optional. */ + if (pi->oauth2_url_complete[0] == '\0') { + free(pi->oauth2_url_complete); + pi->oauth2_url_complete = NULL; + } + + free(pi->oauth2_pin); + pi->oauth2_pin = strdup((char *) &buf[p + offset]); + if (pi->oauth2_pin == NULL) { + D(("strdup failed")); + break; + } + + break; + case SSS_PAM_PASSKEY_KRB_INFO: + free(pi->passkey_prompt_pin); + pi->passkey_prompt_pin = strdup((char *) &buf[p]); + if (pi->passkey_prompt_pin == NULL) { + D(("strdup failed")); + break; + } + + offset = strlen(pi->passkey_prompt_pin) + 1; + if (offset >= len) { + D(("Passkey message size mismatch")); + free(pi->passkey_prompt_pin); + pi->passkey_prompt_pin = NULL; + break; + } + + free(pi->passkey_key); + pi->passkey_key = strdup((char *) &buf[p + offset]); + if (pi->passkey_key == NULL) { + D(("strdup failed")); + break; + } + /* devinfo shall be retrieved according key handle(s) + */ + free (pi->passkey_devinfo); + pi->passkey_devinfo = NULL; + + break; + case SSS_PAM_PASSKEY_INFO: + if (buf[p + (len - 1)] != '\0') { + D(("passkey info does not end with \\0.")); + break; + } + + free(pi->passkey_prompt_pin); + pi->passkey_prompt_pin = strdup((char *) &buf[p]); + if (pi->passkey_prompt_pin == NULL) { + D(("strdup failed")); + break; + } + /* devinfo shall be retrieved according key handle(s) + */ + free (pi->passkey_devinfo); + pi->passkey_devinfo = NULL; + + break; + case SSS_PAM_PASSKEY_DEVINFO: + if (buf[p + (len - 1)] != '\0') { + D(("passkey devinfo does not end with \\0.")); + break; + } + + free(pi->passkey_devinfo); + pi->passkey_devinfo = strdup((char *) &buf[p]); + if (pi->passkey_devinfo == NULL) { + D(("strdup failed")); + break; + } + break; + default: + D(("Unknown response type [%d]", type)); + } + p += len; + + --c; + } + + return PAM_SUCCESS; +} + +bool is_string_empty_or_whitespace(const char *str) +{ + int i; + + if (str == NULL) { + return true; + } + + for (i = 0; str[i] != '\0'; i++) { + if (!isspace(str[i])) { + return false; + } + } + + return true; +} + +static int get_pam_items(pam_handle_t *pamh, uint32_t flags, + struct pam_items *pi) +{ + int ret; + + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; + pi->pam_authtok = NULL; + pi->pam_authtok_size = 0; + pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_EMPTY; + pi->pam_newauthtok = NULL; + pi->pam_newauthtok_size = 0; + pi->first_factor = NULL; + + ret = pam_get_item(pamh, PAM_SERVICE, (const void **) &(pi->pam_service)); + if (ret != PAM_SUCCESS) return ret; + if (pi->pam_service == NULL) pi->pam_service=""; + pi->pam_service_size=strlen(pi->pam_service)+1; + + ret = pam_get_item(pamh, PAM_USER, (const void **) &(pi->pam_user)); + if (ret == PAM_PERM_DENIED && (flags & PAM_CLI_FLAGS_ALLOW_MISSING_NAME)) { + pi->pam_user = ""; + ret = PAM_SUCCESS; + } + if (ret != PAM_SUCCESS) return ret; + if (flags & PAM_CLI_FLAGS_ALLOW_MISSING_NAME) { + if (is_string_empty_or_whitespace(pi->pam_user)) { + pi->pam_user = ""; + } + } + if (pi->pam_user == NULL) { + D(("No user found, aborting.")); + return PAM_BAD_ITEM; + } + if (strcmp(pi->pam_user, "root") == 0) { + D(("pam_sss will not handle root.")); + return PAM_USER_UNKNOWN; + } + pi->pam_user_size=strlen(pi->pam_user)+1; + + + ret = pam_get_item(pamh, PAM_TTY, (const void **) &(pi->pam_tty)); + if (ret != PAM_SUCCESS) return ret; + if (pi->pam_tty == NULL) pi->pam_tty=""; + pi->pam_tty_size=strlen(pi->pam_tty)+1; + + ret = pam_get_item(pamh, PAM_RUSER, (const void **) &(pi->pam_ruser)); + if (ret != PAM_SUCCESS) return ret; + if (pi->pam_ruser == NULL) pi->pam_ruser=""; + pi->pam_ruser_size=strlen(pi->pam_ruser)+1; + + ret = pam_get_item(pamh, PAM_RHOST, (const void **) &(pi->pam_rhost)); + if (ret != PAM_SUCCESS) return ret; + if (pi->pam_rhost == NULL) pi->pam_rhost=""; + pi->pam_rhost_size=strlen(pi->pam_rhost)+1; + + ret = pam_get_item(pamh, PAM_AUTHTOK, + (const void **) &(pi->pamstack_authtok)); + if (ret != PAM_SUCCESS) return ret; + if (pi->pamstack_authtok == NULL) pi->pamstack_authtok=""; + + ret = pam_get_item(pamh, PAM_OLDAUTHTOK, + (const void **) &(pi->pamstack_oldauthtok)); + if (ret != PAM_SUCCESS) return ret; + if (pi->pamstack_oldauthtok == NULL) pi->pamstack_oldauthtok=""; + + pi->cli_pid = getpid(); + + pi->login_name = pam_modutil_getlogin(pamh); + if (pi->login_name == NULL) pi->login_name=""; + + pi->domain_name = NULL; + + if (pi->requested_domains == NULL) pi->requested_domains = ""; + pi->requested_domains_size = strlen(pi->requested_domains) + 1; + + pi->otp_vendor = NULL; + pi->otp_token_id = NULL; + pi->otp_challenge = NULL; + pi->password_prompting = false; + + pi->cert_list = NULL; + pi->selected_cert = NULL; + + pi->pc = NULL; + + pi->flags = flags; + + return PAM_SUCCESS; +} + +static void print_pam_items(struct pam_items *pi) +{ + if (pi == NULL) return; + + D(("Service: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_service))); + D(("User: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_user))); + D(("Tty: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_tty))); + D(("Ruser: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_ruser))); + D(("Rhost: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_rhost))); + D(("Pamstack_Authtok: %s", + CHECK_AND_RETURN_PI_STRING(pi->pamstack_authtok))); + D(("Pamstack_Oldauthtok: %s", + CHECK_AND_RETURN_PI_STRING(pi->pamstack_oldauthtok))); + D(("Authtok: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_authtok))); + D(("Newauthtok: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_newauthtok))); + D(("Cli_PID: %d", pi->cli_pid)); + D(("Child_PID: %d", pi->child_pid)); + D(("Requested domains: %s", pi->requested_domains)); + D(("Flags: %d", pi->flags)); +} + +static int send_and_receive(pam_handle_t *pamh, struct pam_items *pi, + enum sss_cli_command task, bool quiet_mode) +{ + int ret; + int sret; + int errnop; + struct sss_cli_req_data rd; + uint8_t *buf = NULL; + uint8_t *repbuf = NULL; + size_t replen; + int pam_status = PAM_SYSTEM_ERR; + + print_pam_items(pi); + + ret = pack_message_v3(pi, &rd.len, &buf); + if (ret != 0) { + D(("pack_message failed.")); + pam_status = PAM_SYSTEM_ERR; + goto done; + } + rd.data = buf; + + errnop = 0; + ret = sss_pam_make_request(task, &rd, &repbuf, &replen, &errnop); + + sret = pam_set_data(pamh, FD_DESTRUCTOR, NULL, close_fd); + if (sret != PAM_SUCCESS) { + D(("pam_set_data failed, client might leaks fds")); + } + + if (ret != PAM_SUCCESS) { + /* If there is no PAM responder socket during the access control step + * we assume this is on purpose, i.e. PAM responder is not configured. + * PAM_USER_UNKNOWN is returned to the PAM stack to avoid unexpected + * denials. */ + if (errnop == ESSS_NO_SOCKET && task == SSS_PAM_ACCT_MGMT) { + pam_status = PAM_USER_UNKNOWN; + } else { + if (errnop != 0 && errnop != ESSS_NO_SOCKET) { + logger(pamh, LOG_ERR, "Request to sssd failed. %s", + ssscli_err2string(errnop)); + } + + pam_status = PAM_AUTHINFO_UNAVAIL; + } + goto done; + } + +/* FIXME: add an end signature */ + if (replen < (2*sizeof(int32_t))) { + D(("response not in expected format.")); + pam_status = PAM_SYSTEM_ERR; + goto done; + } + + SAFEALIGN_COPY_UINT32(&pam_status, repbuf, NULL); + ret = eval_response(pamh, replen, repbuf, pi); + if (ret != PAM_SUCCESS) { + D(("eval_response failed.")); + pam_status = ret; + goto done; + } + + switch (task) { + case SSS_PAM_AUTHENTICATE: + logger(pamh, (pam_status == PAM_SUCCESS ? LOG_INFO : LOG_NOTICE), + "authentication %s; logname=%s uid=%lu euid=%d tty=%s " + "ruser=%s rhost=%s user=%s", + pam_status == PAM_SUCCESS ? "success" : "failure", + pi->login_name, getuid(), (unsigned long) geteuid(), + pi->pam_tty, pi->pam_ruser, pi->pam_rhost, pi->pam_user); + if (pam_status != PAM_SUCCESS) { + /* don't log if quiet_mode is on and pam_status is + * User not known to the underlying authentication module + */ + if (!quiet_mode || pam_status != 10) { + logger(pamh, LOG_NOTICE, "received for user %s: %d (%s)", + pi->pam_user, pam_status, + pam_strerror(pamh,pam_status)); + } + } + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + if (pam_status != PAM_SUCCESS) { + /* don't log if quiet_mode is on and pam_status is + * User not known to the underlying authentication module + */ + if (!quiet_mode || pam_status != 10) { + logger(pamh, LOG_NOTICE, + "Authentication failed for user %s: %d (%s)", + pi->pam_user, pam_status, + pam_strerror(pamh,pam_status)); + } + } + break; + case SSS_PAM_CHAUTHTOK: + if (pam_status != PAM_SUCCESS) { + logger(pamh, LOG_NOTICE, + "Password change failed for user %s: %d (%s)", + pi->pam_user, pam_status, + pam_strerror(pamh,pam_status)); + } + break; + case SSS_PAM_ACCT_MGMT: + if (pam_status != PAM_SUCCESS) { + /* don't log if quiet_mode is on and pam_status is + * User not known to the underlying authentication module + */ + if (!quiet_mode || pam_status != 10) { + logger(pamh, LOG_NOTICE, + "Access denied for user %s: %d (%s)", + pi->pam_user, pam_status, + pam_strerror(pamh,pam_status)); + } + } + break; + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_SETCRED: + case SSS_PAM_CLOSE_SESSION: + case SSS_PAM_PREAUTH: + break; + default: + D(("Illegal task [%#x]", task)); + pam_status = PAM_SYSTEM_ERR; + } + +done: + if (buf != NULL ) { + sss_erase_mem_securely((void *)buf, rd.len); + free(buf); + } + free(repbuf); + + return pam_status; +} + +static int prompt_password(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); + sss_erase_mem_securely((void *)answer, strlen(answer)); + free(answer); + answer=NULL; + if (pi->pam_authtok == NULL) { + return PAM_BUF_ERR; + } + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD; + pi->pam_authtok_size=strlen(pi->pam_authtok); + } + + return PAM_SUCCESS; +} + +static int prompt_2fa(pam_handle_t *pamh, struct pam_items *pi, + bool second_factor_optional, + const char *prompt_fa1, const char *prompt_fa2) +{ + int ret; + const struct pam_conv *conv; + const struct pam_message *mesg[2] = { NULL, NULL }; + struct pam_message m[2] = { {0}, {0} }; + struct pam_response *resp = NULL; + size_t needed_size; + + ret = pam_get_item(pamh, PAM_CONV, (const void **) &conv); + if (ret != PAM_SUCCESS) { + return ret; + } + if (conv == NULL || conv->conv == NULL) { + logger(pamh, LOG_ERR, "No conversation function"); + return PAM_SYSTEM_ERR; + } + + m[0].msg_style = PAM_PROMPT_ECHO_OFF; + m[0].msg = prompt_fa1; + m[1].msg_style = PAM_PROMPT_ECHO_OFF; + m[1].msg = prompt_fa2; + + mesg[0] = (const struct pam_message *) m; + /* The following assignment might look a bit odd but is recommended in the + * pam_conv man page to make sure that the second argument of the PAM + * conversation function can be interpreted in two different ways. + * Basically it is important that both the actual struct pam_message and + * the pointers to the struct pam_message are arrays. Since the assignment + * makes clear that mesg[] and (*mesg)[] are arrays it should be kept this + * way and not be replaced by other equivalent assignments. */ + mesg[1] = & (( *mesg )[1]); + + ret = conv->conv(2, mesg, &resp, conv->appdata_ptr); + if (ret != PAM_SUCCESS) { + D(("Conversation failure: %s.", pam_strerror(pamh, ret))); + return ret; + } + + if (resp == NULL) { + D(("response expected, but resp==NULL")); + return PAM_SYSTEM_ERR; + } + + if (resp[0].resp == NULL || *(resp[0].resp) == '\0') { + D(("Missing factor.")); + ret = PAM_CRED_INSUFFICIENT; + goto done; + } + + if (resp[1].resp == NULL || *(resp[1].resp) == '\0') { + /* Missing second factor, assume first factor contains combined 2FA + * credentials if the second factor is not optional. If it is optional + * then it is assumed that the first factor contain the password. */ + pi->pam_authtok = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); + if (pi->pam_authtok == NULL) { + D(("strndup failed.")); + ret = PAM_BUF_ERR; + goto done; + } + pi->pam_authtok_size = strlen(pi->pam_authtok) + 1; + pi->pam_authtok_type = second_factor_optional + ? SSS_AUTHTOK_TYPE_PASSWORD + : SSS_AUTHTOK_TYPE_2FA_SINGLE; + } else if (pi->pam_service != NULL && strcmp(pi->pam_service, "sshd") == 0 + && strcmp(resp[0].resp, resp[1].resp) == 0) { + /* Special handling for SSH with password authentication (ssh's + * 'PasswordAuthentication' option. In this mode the ssh client + * directly prompts the user for a password and the prompts we are + * sending are ignored. Since we send two prompts ssh * will create two + * response as well with the same content. We assume that the combined + * 2FA credentials are used even if the second factor is optional + * because there is no indication about the intention of the user. As a + * result we prefer the more secure variant. */ + + pi->pam_authtok = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); + if (pi->pam_authtok == NULL) { + D(("strndup failed.")); + ret = PAM_BUF_ERR; + goto done; + } + pi->pam_authtok_size = strlen(pi->pam_authtok) + 1; + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_2FA_SINGLE; + } else { + + ret = sss_auth_pack_2fa_blob(resp[0].resp, 0, resp[1].resp, 0, NULL, 0, + &needed_size); + if (ret != EAGAIN) { + D(("sss_auth_pack_2fa_blob failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + pi->pam_authtok = malloc(needed_size); + if (pi->pam_authtok == NULL) { + D(("malloc failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + ret = sss_auth_pack_2fa_blob(resp[0].resp, 0, resp[1].resp, 0, + (uint8_t *) pi->pam_authtok, needed_size, + &needed_size); + if (ret != EOK) { + D(("sss_auth_pack_2fa_blob failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + pi->pam_authtok_size = needed_size; + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_2FA; + pi->first_factor = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); + if (pi->first_factor == NULL) { + D(("strndup failed.")); + ret = PAM_BUF_ERR; + goto done; + } + } + + ret = PAM_SUCCESS; + +done: + if (resp != NULL) { + if (resp[0].resp != NULL) { + sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); + free(resp[0].resp); + } + if (resp[1].resp != NULL) { + sss_erase_mem_securely((void *)resp[1].resp, strlen(resp[1].resp)); + free(resp[1].resp); + } + + free(resp); + resp = NULL; + } + + 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); + sss_erase_mem_securely((void *)answer, strlen(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; +} + +static int prompt_oauth2(pam_handle_t *pamh, struct pam_items *pi) +{ + char *answer = NULL; + char *msg; + int ret; + + if (pi->oauth2_url_complete != NULL) { + ret = asprintf(&msg, _("Authenticate at %1$s and press ENTER."), + pi->oauth2_url_complete); + } else { + ret = asprintf(&msg, _("Authenticate with PIN %1$s at %2$s and press " + "ENTER."), pi->oauth2_pin, pi->oauth2_url); + } + if (ret == -1) { + return PAM_SYSTEM_ERR; + } + + ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, msg, NULL, &answer); + free(msg); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return ret; + } + + /* We don't care about answer here. We just need to notify that the + * authentication has finished. */ + free(answer); + + pi->pam_authtok = strdup(pi->oauth2_pin); + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_OAUTH2; + pi->pam_authtok_size=strlen(pi->oauth2_pin); + + return PAM_SUCCESS; +} + +static int prompt_passkey(pam_handle_t *pamh, struct pam_items *pi) +{ + int ret; + const struct pam_conv *conv; + const struct pam_message *mesg[4] = { NULL, NULL, NULL, NULL }; + struct pam_message m[4] = { {0}, {0}, {0}, {0} }; + struct pam_response *resp = NULL; + bool kerberos_preauth; + bool prompt_pin = false; + int pin_idx = 0; + int msg_idx = 0; + size_t needed_size; + + ret = pam_get_item(pamh, PAM_CONV, (const void **) &conv); + if (ret != PAM_SUCCESS) { + return ret; + } + if (conv == NULL || conv->conv == NULL) { + logger(pamh, LOG_ERR, "No conversation function"); + return PAM_SYSTEM_ERR; + } + + /* check device capabilities + */ + if ((strcasecmp(pi->passkey_devinfo, "dopin") != 0) && + (strcasecmp(pi->passkey_devinfo, "pinonly") != 0) && + (strcasecmp(pi->passkey_devinfo, "pinuv") != 0)) { + /* invalid device or no connected device + * fallback to passord + */ + return EIO; + } + kerberos_preauth = pi->passkey_key != NULL ? true : false; + if (!kerberos_preauth) { + m[msg_idx].msg_style = PAM_TEXT_INFO; + m[msg_idx].msg = PASSKEY_LOCAL_AUTH_MSG; + msg_idx++; + } + + if ( strcasecmp(pi->passkey_prompt_pin, "true") == 0) { + if ((strcasecmp(pi->passkey_devinfo, "dopin") == 0) || + (strcasecmp(pi->passkey_devinfo, "pinonly") == 0)) { + prompt_pin = true; + + m[msg_idx].msg_style = PAM_PROMPT_ECHO_OFF; + m[msg_idx].msg = PASSKEY_DEFAULT_PIN_MSG; + pin_idx = msg_idx; + msg_idx++; + } else { + /* let try UV according device verification capabilities */ + prompt_pin = false; + + /* Prompt to remind the user to perform verification (.eg fingerprint) */ + m[msg_idx].msg_style = PAM_TEXT_INFO; + m[msg_idx].msg = _("Perform User Verification on your device"); + msg_idx++; + } + } + + mesg[0] = (const struct pam_message *) m; + /* The following assignment might look a bit odd but is recommended in the + * pam_conv man page to make sure that the second argument of the PAM + * conversation function can be interpreted in two different ways. + * Basically it is important that both the actual struct pam_message and + * the pointers to the struct pam_message are arrays. Since the assignment + * makes clear that mesg[] and (*mesg)[] are arrays it should be kept this + * way and not be replaced by other equivalent assignments. */ + for (int i = 1; i < msg_idx; i++) { + mesg[i] = & (( *mesg )[i]); + } + + ret = conv->conv(msg_idx, mesg, &resp, conv->appdata_ptr); + if (ret != PAM_SUCCESS) { + D(("Conversation failure: %s.", pam_strerror(pamh, ret))); + return ret; + } + + if (kerberos_preauth) { + if (!prompt_pin) { + resp[pin_idx].resp = NULL; + /* fix passkey_child_credentials.c when NO PIN */ + resp[pin_idx].resp = strdup ("NULL"); + /* end fix */ +} + + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY_KRB; + sss_auth_passkey_calc_size(pi->passkey_prompt_pin, + pi->passkey_key, + resp[pin_idx].resp, + &needed_size); + + pi->pam_authtok = malloc(needed_size); + if (pi->pam_authtok == NULL) { + D(("malloc failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + sss_auth_pack_passkey_blob((uint8_t *)pi->pam_authtok, + pi->passkey_prompt_pin, pi->passkey_key, + resp[pin_idx].resp); + + } else { + if (!prompt_pin) { + /* user verification = false, SSS_AUTHTOK_TYPE_PASSKEY will be reset to + * SSS_AUTHTOK_TYPE_NULL in PAM responder + */ + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY; + pi->pam_authtok = NULL; + pi->pam_authtok_size = 0; + /* fix passkey_child_credentials.c when NO PIN */ + pi->pam_authtok = strdup("null"); + pi->pam_authtok_size = strlen(pi->pam_authtok); + /* end fix */ + ret = PAM_SUCCESS; + goto done; + } else { + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY; + pi->pam_authtok = strdup(resp[pin_idx].resp); + needed_size = strlen(pi->pam_authtok); + } + } + + pi->pam_authtok_size = needed_size; + + /* Fallback to password auth if no PIN was entered */ + if (prompt_pin) { + if (resp[pin_idx].resp == NULL || resp[pin_idx].resp[0] == '\0') { + ret = EIO; + goto done; + } + } + + ret = PAM_SUCCESS; + +done: + if (resp != NULL) { + if (resp[pin_idx].resp != NULL) { + sss_erase_mem_securely((void *)resp[pin_idx].resp, + strlen(resp[pin_idx].resp)); + free(resp[pin_idx].resp); + } + + free(resp); + resp = NULL; + } + + return ret; +} + +static int passkey_get_current_device_info(pam_handle_t *pamh, struct pam_items *pi, + bool quiet_mode) +{ + int ret; + // const char* prompt = _("Connect your passkey device, then press ENTER."); + const char* prompt = "Connect your passkey device, then press ENTER."; + char* answer; + + int ori_flags = pi->flags; + + pi->flags |= PAM_CLI_FLAGS_REQUIRE_PASSKEY_CACHED_DEVINFO; + + ret = send_and_receive(pamh, pi, SSS_PAM_PASSKEY_PREAUTH, quiet_mode); + if (ret != PAM_SUCCESS) { + D(("send_and_receive returned [%d] (ignored) during passkey_preauth", ret)); + ret = PAM_SUCCESS; + } + pi->flags = ori_flags; + + if (pi->passkey_devinfo == NULL) { + ret = PAM_SYSTEM_ERR; + goto done; + } + + if (strcasecmp(pi->passkey_devinfo, "dopin") == 0) { + /* this is an authentication retry (after a wrong UV or PIN) + * which assumes the device is connected + * the user will be prompted for a PIN + */ + goto done; + } + if (strcasecmp(pi->passkey_devinfo, "nodev") == 0) { + /* the cache indicates that no valid device has been + * connected, there we shall fallback to password authentication + */ + goto done; + } + + /* user shall confirm or reconfirm its device is ready */ + ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, prompt, NULL, &answer); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed ignored - we do inot need response but just ENTER")); + ret = PAM_SUCCESS; + } + + + if ((strcasecmp(pi->passkey_devinfo, "pinuv") == 0) || + (strcasecmp(pi->passkey_devinfo, "pinonly") == 0)) { + /* OK */ + goto done; + } + /* cache is empty or indicate invalid info + * request fresh info + */ + + if (pi->passkey_key != NULL) { + + size_t needed_size; + + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY_KRB; + sss_auth_passkey_calc_size(pi->passkey_prompt_pin, + pi->passkey_key, + "", // empty PIN but unsued + &needed_size); + + pi->pam_authtok = malloc(needed_size); + if (pi->pam_authtok == NULL) { + D(("malloc failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + sss_auth_pack_passkey_blob((uint8_t *)pi->pam_authtok, + pi->passkey_prompt_pin, + pi->passkey_key, + ""); + pi->pam_authtok_size = needed_size; + + } else { + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; + pi->pam_authtok = NULL; + pi->pam_authtok_size = 0; + } + ret = send_and_receive(pamh, pi, SSS_PAM_PASSKEY_PREAUTH, quiet_mode); + if (ret != PAM_SUCCESS) { + D(("send_and_receive returned [%d] (ignored) during passkey_preauth", ret)); + ret = PAM_SUCCESS; + } + + if (pi->passkey_devinfo != NULL && + ((strcasecmp(pi->passkey_devinfo, "pinuv") == 0) || + (strcasecmp(pi->passkey_devinfo, "pinonly") == 0))) { + /* OK */ + goto done; + } + + /* inform user she/he shall fallback to password authntication and wait its ENTER + */ + { + char* _prompt; + int r = asprintf(&_prompt, "%s %s", + pi->passkey_devinfo == NULL ? "SYSTEM ERROR DEVINFO" : pi->passkey_devinfo, + " Fallback to password authentication"); + if (r == -1) { + ret = ENOMEM; + goto done; + } + + ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, _prompt, NULL, &answer); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed ignored - we doi not need response but just ENTER")); + ret = PAM_SUCCESS; + } + free (_prompt); + } + + if (pi->passkey_devinfo == NULL) { + ret = PAM_SYSTEM_ERR; + goto done; + } + + ret = PAM_SUCCESS; + + done: + return ret; +} + +#define SC_PROMPT_FMT "PIN for %s: " + +#ifndef discard_const +#define discard_const(ptr) ((void *)((uintptr_t)(ptr))) +#endif + +#define CERT_SEL_PROMPT_FMT "%s" +#define SEL_TITLE discard_const("Please select a certificate") + +static int prompt_multi_cert_gdm(pam_handle_t *pamh, struct pam_items *pi) +{ +#ifdef HAVE_GDM_PAM_EXTENSIONS + int ret; + size_t cert_count = 0; + size_t c; + const struct pam_conv *conv; + struct cert_auth_info *cai; + GdmPamExtensionChoiceListRequest *request = NULL; + GdmPamExtensionChoiceListResponse *response = NULL; + struct pam_message prompt_message; + const struct pam_message *prompt_messages[1]; + struct pam_response *reply = NULL; + char *prompt; + + if (!GDM_PAM_EXTENSION_SUPPORTED(GDM_PAM_EXTENSION_CHOICE_LIST)) { + return ENOTSUP; + } + + if (pi->cert_list == NULL) { + return EINVAL; + } + + DLIST_FOR_EACH(cai, pi->cert_list) { + cert_count++; + } + + ret = pam_get_item(pamh, PAM_CONV, (const void **)&conv); + if (ret != PAM_SUCCESS) { + ret = EIO; + return ret; + } + + request = calloc(1, GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_SIZE(cert_count)); + if (request == NULL) { + ret = ENOMEM; + goto done; + } + GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_INIT(request, SEL_TITLE, cert_count); + + c = 0; + DLIST_FOR_EACH(cai, pi->cert_list) { + ret = asprintf(&prompt, CERT_SEL_PROMPT_FMT, cai->prompt_str); + if (ret == -1) { + ret = ENOMEM; + goto done; + } + free(cai->choice_list_id); + ret = asprintf(&cai->choice_list_id, "%zu", c); + if (ret == -1) { + cai->choice_list_id = NULL; + free(prompt); + ret = ENOMEM; + goto done; + } + + request->list.items[c].key = cai->choice_list_id; + request->list.items[c++].text = prompt; + } + + GDM_PAM_EXTENSION_MESSAGE_TO_BINARY_PROMPT_MESSAGE(request, + &prompt_message); + prompt_messages[0] = &prompt_message; + + ret = conv->conv(1, prompt_messages, &reply, conv->appdata_ptr); + if (ret != PAM_SUCCESS) { + ret = EIO; + goto done; + } + + ret = EIO; + response = GDM_PAM_EXTENSION_REPLY_TO_CHOICE_LIST_RESPONSE(reply); + if (response->key == NULL) { + goto done; + } + + DLIST_FOR_EACH(cai, pi->cert_list) { + if (strcmp(response->key, cai->choice_list_id) == 0) { + pam_info(pamh, "Certificate ‘%s’ selected", cai->key_id); + pi->selected_cert = cai; + ret = 0; + break; + } + } + +done: + if (request != NULL) { + for (c = 0; c < cert_count; c++) { + free(discard_const(request->list.items[c++].text)); + } + free(request); + } + free(response); + + return ret; +#else + return ENOTSUP; +#endif +} + +#define TEXT_CERT_SEL_PROMPT_FMT "%s\n[%zu]:\n%s\n" +#define TEXT_SEL_TITLE discard_const("Please select a certificate by typing " \ + "the corresponding number\n") + +static int prompt_multi_cert(pam_handle_t *pamh, struct pam_items *pi) +{ + int ret; + size_t cert_count = 0; + size_t tries = 0; + long int resp = -1; + struct cert_auth_info *cai; + char *prompt; + char *tmp; + char *answer; + char *ep; + + /* First check if gdm extension is supported */ + ret = prompt_multi_cert_gdm(pamh, pi); + if (ret != ENOTSUP) { + return ret; + } + + if (pi->cert_list == NULL) { + return EINVAL; + } + + prompt = strdup(TEXT_SEL_TITLE); + if (prompt == NULL) { + return ENOMEM; + } + + DLIST_FOR_EACH(cai, pi->cert_list) { + cert_count++; + ret = asprintf(&tmp, TEXT_CERT_SEL_PROMPT_FMT, prompt, cert_count, + cai->prompt_str); + free(prompt); + if (ret == -1) { + return ENOMEM; + } + + prompt = tmp; + } + + do { + ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_ON, prompt, NULL, + &answer); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + break; + } + + errno = 0; + resp = strtol(answer, &ep, 10); + if (errno == 0 && *ep == '\0' && resp > 0 && resp <= cert_count) { + /* do not free answer ealier because ep is pointing to it */ + free(answer); + break; + } + free(answer); + resp = -1; + } while (++tries < 5); + free(prompt); + + pi->selected_cert = NULL; + ret = ENOENT; + if (resp > 0 && resp <= cert_count) { + cert_count = 0; + DLIST_FOR_EACH(cai, pi->cert_list) { + cert_count++; + if (resp == cert_count) { + pam_info(pamh, "Certificate ‘%s’ selected", cai->key_id); + pi->selected_cert = cai; + ret = 0; + break; + } + } + } + + return ret; +} + +#define SC_INSERT_PROMPT _("Please (re)insert (different) Smartcard") + +static int prompt_sc_pin(pam_handle_t *pamh, struct pam_items *pi) +{ + int ret; + char *answer = NULL; + char *prompt = NULL; + size_t needed_size; + const struct pam_conv *conv; + const struct pam_message *mesg[2] = { NULL, NULL }; + struct pam_message m[2] = { { 0 }, { 0 } }; + struct pam_response *resp = NULL; + struct cert_auth_info *cai = pi->selected_cert; + + if (cai == NULL && (SERVICE_IS_GDM_SMARTCARD(pi) + || (pi->flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH))) { + ret = asprintf(&prompt, SC_INSERT_PROMPT); + } else if (cai == NULL || cai->token_name == NULL + || *cai->token_name == '\0') { + return PAM_SYSTEM_ERR; + } else { + ret = asprintf(&prompt, SC_PROMPT_FMT, cai->token_name); + } + + if (ret == -1) { + D(("asprintf failed.")); + return PAM_SYSTEM_ERR; + } + + if (cai == NULL) { + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, prompt, NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("Conversation failure: %s, ignored", pam_strerror(pamh, ret))); + } + } + + if (pi->user_name_hint) { + ret = pam_get_item(pamh, PAM_CONV, (const void **)&conv); + if (ret != PAM_SUCCESS) { + free(prompt); + return ret; + } + if (conv == NULL || conv->conv == NULL) { + logger(pamh, LOG_ERR, "No conversation function"); + free(prompt); + return PAM_SYSTEM_ERR; + } + + m[0].msg_style = PAM_PROMPT_ECHO_OFF; + m[0].msg = prompt; + m[1].msg_style = PAM_PROMPT_ECHO_ON; + m[1].msg = "User name hint: "; + + mesg[0] = (const struct pam_message *)m; + /* The following assignment might look a bit odd but is recommended in the + * pam_conv man page to make sure that the second argument of the PAM + * conversation function can be interpreted in two different ways. + * Basically it is important that both the actual struct pam_message and + * the pointers to the struct pam_message are arrays. Since the assignment + * makes clear that mesg[] and (*mesg)[] are arrays it should be kept this + * way and not be replaced by other equivalent assignments. */ + mesg[1] = &((*mesg)[1]); + + ret = conv->conv(2, mesg, &resp, conv->appdata_ptr); + free(prompt); + if (ret != PAM_SUCCESS) { + D(("Conversation failure: %s.", pam_strerror(pamh, ret))); + return ret; + } + + if (resp == NULL) { + D(("response expected, but resp==NULL")); + return PAM_SYSTEM_ERR; + } + + if (resp[0].resp == NULL || *(resp[0].resp) == '\0') { + D(("Missing PIN.")); + ret = PAM_CRED_INSUFFICIENT; + goto done; + } + + answer = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); + sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); + free(resp[0].resp); + resp[0].resp = NULL; + if (answer == NULL) { + D(("strndup failed")); + ret = PAM_BUF_ERR; + goto done; + } + + if (resp[1].resp != NULL && *(resp[1].resp) != '\0') { + ret = pam_set_item(pamh, PAM_USER, resp[1].resp); + free(resp[1].resp); + resp[1].resp = NULL; + if (ret != PAM_SUCCESS) { + D(("Failed to set PAM_USER with user name hint [%s]", + pam_strerror(pamh, ret))); + goto done; + } + + ret = pam_get_item(pamh, PAM_USER, (const void **)&(pi->pam_user)); + if (ret != PAM_SUCCESS) { + D(("Failed to get PAM_USER with user name hint [%s]", + pam_strerror(pamh, ret))); + goto done; + } + + pi->pam_user_size = strlen(pi->pam_user) + 1; + } + } else { + ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, prompt, NULL, + &answer); + free(prompt); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return ret; + } + } + + if (cai == NULL) { + /* it is expected that the user just replaces the Smartcard which + * would trigger gdm to restart the PAM module, so it is not + * expected that this part of the code is reached. */ + ret = PAM_AUTHINFO_UNAVAIL; + goto done; + } + + if (answer == NULL || *answer == '\0') { + D(("Missing PIN.")); + ret = PAM_CRED_INSUFFICIENT; + goto done; + } else { + + ret = sss_auth_pack_sc_blob(answer, 0, cai->token_name, 0, + cai->module_name, 0, + cai->key_id, 0, + cai->label, 0, + NULL, 0, &needed_size); + if (ret != EAGAIN) { + D(("sss_auth_pack_sc_blob failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + pi->pam_authtok = malloc(needed_size); + if (pi->pam_authtok == NULL) { + D(("malloc failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + ret = sss_auth_pack_sc_blob(answer, 0, cai->token_name, 0, + cai->module_name, 0, + cai->key_id, 0, + cai->label, 0, + (uint8_t *) pi->pam_authtok, needed_size, + &needed_size); + if (ret != EOK) { + D(("sss_auth_pack_sc_blob failed.")); + free((void *)pi->pam_authtok); + ret = PAM_BUF_ERR; + goto done; + } + + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_SC_PIN; + pi->pam_authtok_size = needed_size; + } + + ret = PAM_SUCCESS; + +done: + if (answer != NULL) { + sss_erase_mem_securely((void *)answer, strlen(answer)); + free(answer); + answer=NULL; + } + + if (resp != NULL) { + if (resp[0].resp != NULL) { + sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); + free(resp[0].resp); + } + if (resp[1].resp != NULL) { + sss_erase_mem_securely((void *)resp[1].resp, strlen(resp[1].resp)); + free(resp[1].resp); + } + + free(resp); + resp = NULL; + } + + return ret; +} + +static int prompt_new_password(pam_handle_t *pamh, struct pam_items *pi) +{ + int ret; + char *answer = NULL; + + ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, + _("New Password: "), + _("Reenter new Password: "), + &answer); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return ret; + } + if (answer == NULL) { + pi->pam_newauthtok = NULL; + pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_EMPTY; + pi->pam_newauthtok_size=0; + } else { + pi->pam_newauthtok = strdup(answer); + sss_erase_mem_securely((void *)answer, strlen(answer)); + free(answer); + answer=NULL; + if (pi->pam_newauthtok == NULL) { + return PAM_BUF_ERR; + } + pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_PASSWORD; + pi->pam_newauthtok_size=strlen(pi->pam_newauthtok); + } + + return PAM_SUCCESS; +} + +static void eval_argv(pam_handle_t *pamh, int argc, const char **argv, + uint32_t *flags, int *retries, bool *quiet_mode, + const char **domains) +{ + char *ep; + + *quiet_mode = false; + + for (; argc-- > 0; ++argv) { + if (strcmp(*argv, "forward_pass") == 0) { + *flags |= PAM_CLI_FLAGS_FORWARD_PASS; + } else if (strcmp(*argv, "use_first_pass") == 0) { + *flags |= PAM_CLI_FLAGS_USE_FIRST_PASS; + } else if (strcmp(*argv, "use_authtok") == 0) { + *flags |= PAM_CLI_FLAGS_USE_AUTHTOK; + } else if (strncmp(*argv, OPT_DOMAINS_KEY, strlen(OPT_DOMAINS_KEY)) == 0) { + if (*(*argv+strlen(OPT_DOMAINS_KEY)) == '\0') { + logger(pamh, LOG_ERR, "Missing argument to option domains."); + *domains = ""; + } else { + *domains = *argv+strlen(OPT_DOMAINS_KEY); + } + + } else if (strncmp(*argv, OPT_RETRY_KEY, strlen(OPT_RETRY_KEY)) == 0) { + if (*(*argv+6) == '\0') { + logger(pamh, LOG_ERR, "Missing argument to option retry."); + *retries = 0; + } else { + errno = 0; + *retries = strtol(*argv+6, &ep, 10); + if (errno != 0) { + D(("strtol failed [%d][%s]", errno, strerror(errno))); + *retries = 0; + } + if (*ep != '\0') { + logger(pamh, LOG_ERR, "Argument to option retry contains " + "extra characters."); + *retries = 0; + } + if (*retries < 0) { + logger(pamh, LOG_ERR, "Argument to option retry must not " + "be negative."); + *retries = 0; + } + } + } else if (strcmp(*argv, "quiet") == 0) { + *quiet_mode = true; + } else if (strcmp(*argv, "allow_chauthtok_by_root") == 0) { + *flags |= PAM_CLI_FLAGS_ALLOW_CHAUTHTOK_BY_ROOT; + } else if (strcmp(*argv, "ignore_unknown_user") == 0) { + *flags |= PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER; + } else if (strcmp(*argv, "ignore_authinfo_unavail") == 0) { + *flags |= PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL; + } else if (strcmp(*argv, "use_2fa") == 0) { + *flags |= PAM_CLI_FLAGS_USE_2FA; + } else if (strcmp(*argv, "allow_missing_name") == 0) { + *flags |= PAM_CLI_FLAGS_ALLOW_MISSING_NAME; + } else if (strcmp(*argv, "prompt_always") == 0) { + *flags |= PAM_CLI_FLAGS_PROMPT_ALWAYS; + } else if (strcmp(*argv, "try_cert_auth") == 0) { + *flags |= PAM_CLI_FLAGS_TRY_CERT_AUTH; + } else if (strcmp(*argv, "require_cert_auth") == 0) { + *flags |= PAM_CLI_FLAGS_REQUIRE_CERT_AUTH; + } else { + logger(pamh, LOG_WARNING, "unknown option: %s", *argv); + } + } + + return; +} + +static int prompt_by_config(pam_handle_t *pamh, struct pam_items *pi) +{ + size_t c; + int ret = PAM_SUCCESS; + + if (pi->pc == NULL || *pi->pc == NULL) { + return PAM_SYSTEM_ERR; + } + + 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: + if (pi->password_prompting) { + ret = prompt_2fa(pamh, pi, true, pc_get_2fa_1st_prompt(pi->pc[c]), + pc_get_2fa_2nd_prompt(pi->pc[c])); + } else { + ret = prompt_2fa(pamh, pi, false, 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_PASSKEY: + ret = prompt_passkey(pamh, pi); + /* no more supported. need devinfo. Cannot be done here + pc_get_passkey_inter_prompt(pi->pc[c]), + pc_get_passkey_touch_prompt(pi->pc[c])); + */ + break; + case PC_TYPE_SC_PIN: + ret = prompt_sc_pin(pamh, pi); + /* Todo: add extra string option */ + break; + default: + ret = PAM_SYSTEM_ERR; + } + + /* 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) +{ + int ret; + const char *pin = NULL; + + if ((flags & PAM_CLI_FLAGS_USE_FIRST_PASS) + || ( pi->pamstack_authtok != NULL + && *(pi->pamstack_authtok) != '\0' + && !(flags & PAM_CLI_FLAGS_PROMPT_ALWAYS))) { + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PAM_STACKED; + pi->pam_authtok = strdup(pi->pamstack_authtok); + if (pi->pam_authtok == NULL) { + D(("option use_first_pass set, but no password found")); + return PAM_BUF_ERR; + } + pi->pam_authtok_size = strlen(pi->pam_authtok); + } else { + if (pi->oauth2_url != NULL) { + /* Prompt config is not supported for OAuth2. */ + ret = prompt_oauth2(pamh, pi); + } else if (pi->pc != NULL) { + ret = prompt_by_config(pamh, pi); + } 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 if (SERVICE_IS_GDM_SMARTCARD(pi) + || (pi->flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH)) { + /* Use pin prompt as fallback for gdm-smartcard */ + ret = prompt_sc_pin(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, true, _("First Factor: "), + _("Second Factor (optional): ")); + } else { + ret = prompt_2fa(pamh, pi, false, _("First Factor: "), + _("Second Factor: ")); + } + } else if (pi->passkey_prompt_pin != NULL && pi->passkey_devinfo != NULL) { + ret = prompt_passkey(pamh, pi); + /* Fallback to password auth if no PIN was entered */ + if (ret == EIO) { + ret = prompt_password(pamh, pi, _("Password: ")); + if (pi->pam_authtok_size == 0) { + D(("Empty password failure")); + pi->passkey_prompt_pin = NULL; + return PAM_AUTHTOK_ERR; + } + } + } else { + ret = prompt_password(pamh, pi, _("Password: ")); + } + } + if (ret != PAM_SUCCESS) { + D(("failed to get password from user")); + return ret; + } + + if (flags & PAM_CLI_FLAGS_FORWARD_PASS) { + if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_PASSWORD + || pi->pam_authtok_type == SSS_AUTHTOK_TYPE_PAM_STACKED) { + ret = pam_set_item(pamh, PAM_AUTHTOK, pi->pam_authtok); + } else if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_SC_PIN) { + pin = sss_auth_get_pin_from_sc_blob((uint8_t *) pi->pam_authtok, + pi->pam_authtok_size); + if (pin != NULL) { + ret = pam_set_item(pamh, PAM_AUTHTOK, pin); + } else { + ret = PAM_SYSTEM_ERR; + } + } else if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_2FA + && pi->first_factor != NULL) { + ret = pam_set_item(pamh, PAM_AUTHTOK, pi->first_factor); + } else { + ret = PAM_SYSTEM_ERR; + } + if (ret != PAM_SUCCESS) { + D(("Failed to set PAM_AUTHTOK [%s], " + "authtok may not be available for other modules", + pam_strerror(pamh,ret))); + } + } + } + + return PAM_SUCCESS; +} + +static int check_authtok_data(pam_handle_t *pamh, struct pam_items *pi) +{ + int pam_status; + int *authtok_type; + size_t *authtok_size; + char *authtok_data; + + pam_status = pam_get_data(pamh, PAM_SSS_AUTHOK_TYPE, + (const void **) &authtok_type); + if (pam_status != PAM_SUCCESS) { + D(("pam_get_data failed.")); + return EIO; + } + + pam_status = pam_get_data(pamh, PAM_SSS_AUTHOK_SIZE, + (const void **) &authtok_size); + if (pam_status != PAM_SUCCESS) { + D(("pam_get_data failed.")); + return EIO; + } + + pam_status = pam_get_data(pamh, PAM_SSS_AUTHOK_DATA, + (const void **) &authtok_data); + if (pam_status != PAM_SUCCESS) { + D(("pam_get_data failed.")); + return EIO; + } + + pi->pam_authtok = malloc(*authtok_size); + if (pi->pam_authtok == NULL) { + D(("malloc failed.")); + return ENOMEM; + } + memcpy(pi->pam_authtok, authtok_data, *authtok_size); + + pi->pam_authtok_type = *authtok_type; + pi->pam_authtok_size = *authtok_size; + + return 0; +} + +static int keep_authtok_data(pam_handle_t *pamh, struct pam_items *pi) +{ + int pam_status; + int *authtok_type; + size_t *authtok_size; + char *authtok_data; + + authtok_type = malloc(sizeof(int)); + if (authtok_type == NULL) { + D(("malloc failed.")); + return ENOMEM; + } + *authtok_type = pi->pam_authtok_type; + + pam_status = pam_set_data(pamh, PAM_SSS_AUTHOK_TYPE, authtok_type, + free_exp_data); + if (pam_status != PAM_SUCCESS) { + free(authtok_type); + D(("pam_set_data failed.")); + return EIO; + } + + authtok_size = malloc(sizeof(size_t)); + if (authtok_size == NULL) { + D(("malloc failed.")); + return ENOMEM; + } + *authtok_size = pi->pam_authtok_size; + + pam_status = pam_set_data(pamh, PAM_SSS_AUTHOK_SIZE, authtok_size, + free_exp_data); + if (pam_status != PAM_SUCCESS) { + free(authtok_size); + D(("pam_set_data failed.")); + return EIO; + } + + authtok_data = malloc(pi->pam_authtok_size); + if (authtok_data == NULL) { + D(("malloc failed.")); + return ENOMEM; + } + memcpy(authtok_data, pi->pam_authtok, pi->pam_authtok_size); + + pam_status = pam_set_data(pamh, PAM_SSS_AUTHOK_DATA, authtok_data, + free_exp_data); + if (pam_status != PAM_SUCCESS) { + free(authtok_data); + D(("pam_set_data failed.")); + return EIO; + } + + return 0; +} + +static int get_authtok_for_password_change(pam_handle_t *pamh, + struct pam_items *pi, + uint32_t flags, + int pam_flags) +{ + int ret; + const int *exp_data = NULL; + ret = pam_get_data(pamh, PWEXP_FLAG, (const void **) &exp_data); + if (ret != PAM_SUCCESS) { + exp_data = NULL; + } + + if (pam_flags & PAM_PRELIM_CHECK) { + if (!(flags & PAM_CLI_FLAGS_ALLOW_CHAUTHTOK_BY_ROOT) && getuid() == 0 && !exp_data ) + return PAM_SUCCESS; + + 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, true, + _("First Factor (Current Password): "), + _("Second Factor (optional): ")); + } else { + ret = prompt_2fa(pamh, pi, false, + _("First Factor (Current Password): "), + _("Second Factor: ")); + } + } else if ((flags & PAM_CLI_FLAGS_USE_FIRST_PASS) + && check_authtok_data(pamh, pi) != 0) { + if (pi->pamstack_oldauthtok == NULL) { + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; + pi->pam_authtok = NULL; + pi->pam_authtok_size = 0; + } else { + pi->pam_authtok = strdup(pi->pamstack_oldauthtok); + if (pi->pam_authtok == NULL) { + D(("strdup failed")); + return PAM_BUF_ERR; + } + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD; + pi->pam_authtok_size = strlen(pi->pam_authtok); + } + ret = PAM_SUCCESS; + } else { + ret = prompt_password(pamh, pi, _("Current Password: ")); + } + if (ret != PAM_SUCCESS) { + D(("failed to get credentials from user")); + return ret; + } + + ret = pam_set_item(pamh, PAM_OLDAUTHTOK, pi->pam_authtok); + if (ret != PAM_SUCCESS) { + D(("Failed to set PAM_OLDAUTHTOK [%s], " + "oldauthtok may not be available", + pam_strerror(pamh,ret))); + return ret; + } + + if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_2FA) { + ret = keep_authtok_data(pamh, pi); + if (ret != 0) { + D(("Failed to store authtok data to pam handle. Password " + "change might fail.")); + } + } + + return PAM_SUCCESS; + } + + if (check_authtok_data(pamh, pi) != 0) { + if (pi->pamstack_oldauthtok == NULL) { + if (getuid() != 0) { + D(("no password found for chauthtok")); + return PAM_BUF_ERR; + } else { + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; + pi->pam_authtok = NULL; + pi->pam_authtok_size = 0; + } + } else { + pi->pam_authtok = strdup(pi->pamstack_oldauthtok); + if (pi->pam_authtok == NULL) { + D(("strdup failed")); + return PAM_BUF_ERR; + } + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD; + pi->pam_authtok_size = strlen(pi->pam_authtok); + } + } + + if (flags & PAM_CLI_FLAGS_USE_AUTHTOK) { + pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_PASSWORD; + pi->pam_newauthtok = strdup(pi->pamstack_authtok); + if (pi->pam_newauthtok == NULL) { + D(("option use_authtok set, but no new password found")); + return PAM_BUF_ERR; + } + pi->pam_newauthtok_size = strlen(pi->pam_newauthtok); + } else { + ret = prompt_new_password(pamh, pi); + if (ret != PAM_SUCCESS) { + D(("failed to get new password from user")); + return ret; + } + + if (flags & PAM_CLI_FLAGS_FORWARD_PASS) { + ret = pam_set_item(pamh, PAM_AUTHTOK, pi->pam_newauthtok); + if (ret != PAM_SUCCESS) { + D(("Failed to set PAM_AUTHTOK [%s], " + "oldauthtok may not be available", + pam_strerror(pamh,ret))); + } + } + } + + return PAM_SUCCESS; +} + +#define SC_ENTER_LABEL_FMT "Please insert smart card labeled\n %s" +#define SC_ENTER_FMT "Please insert smart card" + +static int check_login_token_name(pam_handle_t *pamh, struct pam_items *pi, + int retries, bool quiet_mode) +{ + int ret; + int pam_status; + char *login_token_name; + char *prompt = NULL; + uint32_t orig_flags = pi->flags; + + login_token_name = getenv("PKCS11_LOGIN_TOKEN_NAME"); + if (login_token_name == NULL + && !(pi->flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH)) { + return PAM_SUCCESS; + } + + if (login_token_name == NULL) { + ret = asprintf(&prompt, SC_ENTER_FMT); + } else { + ret = asprintf(&prompt, SC_ENTER_LABEL_FMT, login_token_name); + } + if (ret == -1) { + return ENOMEM; + } + + pi->flags |= PAM_CLI_FLAGS_REQUIRE_CERT_AUTH; + + /* TODO: check multiple cert case */ + while (pi->cert_list == NULL || pi->cert_list->token_name == NULL + || (login_token_name != NULL + && strcmp(login_token_name, + pi->cert_list->token_name) != 0)) { + + free_cert_list(pi->cert_list); + pi->cert_list = NULL; + if (retries < 0) { + ret = PAM_AUTHINFO_UNAVAIL; + goto done; + } + retries--; + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, prompt, NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + goto done; + } + + pam_status = send_and_receive(pamh, pi, SSS_PAM_PREAUTH, quiet_mode); + if (pam_status != PAM_SUCCESS) { + D(("send_and_receive returned [%d] during pre-auth", pam_status)); + /* + * Since we are waiting for the right Smartcard to be inserted errors + * can be ignored here. + */ + } + } + + ret = PAM_SUCCESS; + +done: + + pi->flags = orig_flags; + free(prompt); + + return ret; +} + +static int pam_sss(enum sss_cli_command task, pam_handle_t *pamh, + int pam_flags, int argc, const char **argv) +{ + int ret; + int pam_status; + struct pam_items pi = { 0 }; + uint32_t flags = 0; + const int *exp_data; + int *pw_exp_data; + bool retry = false; + bool quiet_mode = false; + int retries = 0; + const char *domains = NULL; + + bindtextdomain(PACKAGE, LOCALEDIR); + + D(("Hello pam_sssd: %#x", task)); + + eval_argv(pamh, argc, argv, &flags, &retries, &quiet_mode, &domains); + + /* Fail all authentication on misconfigured domains= parameter. The admin + * probably wanted to restrict authentication, so it's safer to fail */ + if (domains && strcmp(domains, "") == 0) { + return PAM_SYSTEM_ERR; + } + + pi.requested_domains = domains; + + ret = get_pam_items(pamh, flags, &pi); + if (ret != PAM_SUCCESS) { + D(("get items returned error: %s", pam_strerror(pamh,ret))); + if ((flags & PAM_CLI_FLAGS_TRY_CERT_AUTH) + || (flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) ) { + return PAM_AUTHINFO_UNAVAIL; + } + if (flags & PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER && ret == PAM_USER_UNKNOWN) { + ret = PAM_IGNORE; + } + if (flags & PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL + && ret == PAM_AUTHINFO_UNAVAIL) { + ret = PAM_IGNORE; + } + return ret; + } + + do { + retry = false; + + switch(task) { + case SSS_PAM_AUTHENTICATE: + /* + * Only do preauth if + * - PAM_CLI_FLAGS_USE_FIRST_PASS is not set + * - no password is on the stack or PAM_CLI_FLAGS_PROMPT_ALWAYS is set + * - preauth indicator file exists. + */ + if ( !(flags & PAM_CLI_FLAGS_USE_FIRST_PASS) + && (pi.pam_authtok == NULL + || (flags & PAM_CLI_FLAGS_PROMPT_ALWAYS)) + && access(PAM_PREAUTH_INDICATOR, F_OK) == 0) { + + if (flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) { + /* Do not use PAM_CLI_FLAGS_REQUIRE_CERT_AUTH in the first + * SSS_PAM_PREAUTH run. In case a card is already inserted + * we do not have to prompt to insert a card. */ + pi.flags &= ~PAM_CLI_FLAGS_REQUIRE_CERT_AUTH; + pi.flags |= PAM_CLI_FLAGS_TRY_CERT_AUTH; + } + + pam_status = send_and_receive(pamh, &pi, SSS_PAM_PREAUTH, + quiet_mode); + + pi.flags = flags; + if (pam_status != PAM_SUCCESS) { + D(("send_and_receive returned [%d] during pre-auth", + pam_status)); + /* + * Since we are only interested in the result message + * and will always use password authentication + * as a fallback (except for gdm-smartcard), + * errors can be ignored here. + */ + } + } + + if (flags & PAM_CLI_FLAGS_TRY_CERT_AUTH + && pi.cert_list == NULL) { + D(("No certificates for authentication available.")); + overwrite_and_free_pam_items(&pi); + return PAM_AUTHINFO_UNAVAIL; + } + + if (SERVICE_IS_GDM_SMARTCARD(&pi) + || (flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH)) { + ret = check_login_token_name(pamh, &pi, retries, + quiet_mode); + if (ret != PAM_SUCCESS) { + D(("check_login_token_name failed.\n")); + } + } + + if (pi.passkey_prompt_pin != NULL && pi.passkey_devinfo == NULL) { + ret = passkey_get_current_device_info(pamh, &pi, quiet_mode); + if (ret != PAM_SUCCESS) { + D(("passkey_get_current_device_info failed.\n")); + overwrite_and_free_pam_items(&pi); + return ret; + } + } + + ret = get_authtok_for_authentication(pamh, &pi, flags); + if (ret != PAM_SUCCESS) { + D(("failed to get authentication token: %s", + pam_strerror(pamh, ret))); + overwrite_and_free_pam_items(&pi); + return ret; + } + break; + case SSS_PAM_CHAUTHTOK: + /* + * Even if we only want to change the (long term) password + * there are cases where more than the password is needed to + * get the needed privileges in a backend to change the + * password. + * + * E.g. with mandatory 2-factor authentication we have to ask + * not only for the current password but for the second + * factor, e.g. the one-time token value, as well. + * + * The means the preauth step has to be done here as well but + * only if + * - PAM_PRELIM_CHECK is set + * - PAM_CLI_FLAGS_USE_FIRST_PASS is not set + * - no password is on the stack or PAM_CLI_FLAGS_PROMPT_ALWAYS is set + * - preauth indicator file exists. + */ + if ( (pam_flags & PAM_PRELIM_CHECK) + && !(flags & PAM_CLI_FLAGS_USE_FIRST_PASS) + && (pi.pam_authtok == NULL + || (flags & PAM_CLI_FLAGS_PROMPT_ALWAYS)) + && access(PAM_PREAUTH_INDICATOR, F_OK) == 0) { + pam_status = send_and_receive(pamh, &pi, SSS_PAM_PREAUTH, + quiet_mode); + if (pam_status != PAM_SUCCESS) { + D(("send_and_receive returned [%d] during pre-auth", + pam_status)); + /* + * Since we are only interested in the result message + * and will always use password authentication + * as a fallback, errors can be ignored here. + */ + } + } + + ret = get_authtok_for_password_change(pamh, &pi, flags, pam_flags); + if (ret != PAM_SUCCESS) { + D(("failed to get tokens for password change: %s", + pam_strerror(pamh, ret))); + overwrite_and_free_pam_items(&pi); + return ret; + } + + if (pam_flags & PAM_PRELIM_CHECK) { + if (pi.pam_authtok_type == SSS_AUTHTOK_TYPE_2FA) { + /* We cannot validate the credentials with an OTP + * token value during PAM_PRELIM_CHECK because it + * would be invalid for the actual password change. So + * we are done. */ + + overwrite_and_free_pam_items(&pi); + return PAM_SUCCESS; + } + task = SSS_PAM_CHAUTHTOK_PRELIM; + } + break; + case SSS_PAM_ACCT_MGMT: + case SSS_PAM_SETCRED: + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_CLOSE_SESSION: + break; + default: + D(("Illegal task [%#x]", task)); + overwrite_and_free_pam_items(&pi); + return PAM_SYSTEM_ERR; + } + + pam_status = send_and_receive(pamh, &pi, task, quiet_mode); + + if (flags & PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER + && pam_status == PAM_USER_UNKNOWN) { + pam_status = PAM_IGNORE; + } + if (flags & PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL + && pam_status == PAM_AUTHINFO_UNAVAIL) { + pam_status = PAM_IGNORE; + } + + switch (task) { + case SSS_PAM_AUTHENTICATE: + /* We allow sssd to send the return code PAM_NEW_AUTHTOK_REQD during + * authentication, see sss_cli.h for details */ + if (pam_status == PAM_NEW_AUTHTOK_REQD) { + D(("Authtoken expired, trying to change it")); + + pw_exp_data = malloc(sizeof(int)); + if (pw_exp_data == NULL) { + D(("malloc failed.")); + pam_status = PAM_BUF_ERR; + break; + } + *pw_exp_data = 1; + + pam_status = pam_set_data(pamh, PWEXP_FLAG, pw_exp_data, + free_exp_data); + if (pam_status != PAM_SUCCESS) { + D(("pam_set_data failed.")); + } + } + break; + case SSS_PAM_ACCT_MGMT: + if (pam_status == PAM_SUCCESS && + pam_get_data(pamh, PWEXP_FLAG, (const void **) &exp_data) == + PAM_SUCCESS) { + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, + _("Password expired. Change your password now."), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + } + pam_status = PAM_NEW_AUTHTOK_REQD; + } + break; + case SSS_PAM_CHAUTHTOK: + if (pam_status != PAM_SUCCESS && pam_status != PAM_USER_UNKNOWN) { + ret = pam_set_item(pamh, PAM_AUTHTOK, NULL); + if (ret != PAM_SUCCESS) { + D(("Failed to unset PAM_AUTHTOK [%s]", + pam_strerror(pamh,ret))); + } + ret = pam_set_item(pamh, PAM_OLDAUTHTOK, NULL); + if (ret != PAM_SUCCESS) { + D(("Failed to unset PAM_OLDAUTHTOK [%s]", + pam_strerror(pamh,ret))); + } + } + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + if (pam_status == PAM_PERM_DENIED && pi.pam_authtok_size == 0 && + getuid() == 0 && + pam_get_data(pamh, PWEXP_FLAG, (const void **) &exp_data) != + PAM_SUCCESS) { + + ret = select_pw_reset_message(pamh, &pi); + if (ret != 0) { + D(("select_pw_reset_message failed.\n")); + } + } + default: + /* nothing to do */ + break; + } + + overwrite_and_free_pam_items(&pi); + + D(("retries [%d].", retries)); + + if (pam_status != PAM_SUCCESS && + (task == SSS_PAM_AUTHENTICATE || task == SSS_PAM_CHAUTHTOK_PRELIM) && + retries > 0) { + retry = true; + retries--; + + flags &= ~PAM_CLI_FLAGS_USE_FIRST_PASS; + ret = pam_set_item(pamh, PAM_AUTHTOK, NULL); + if (ret != PAM_SUCCESS) { + D(("Failed to unset PAM_AUTHTOK [%s]", + pam_strerror(pamh,ret))); + } + ret = pam_set_item(pamh, PAM_OLDAUTHTOK, NULL); + if (ret != PAM_SUCCESS) { + D(("Failed to unset PAM_OLDAUTHTOK [%s]", + pam_strerror(pamh,ret))); + } + } + } while(retry); + + return pam_status; +} + +PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, + const char **argv ) +{ + return pam_sss(SSS_PAM_AUTHENTICATE, pamh, flags, argc, argv); +} + + +PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, + const char **argv ) +{ + return pam_sss(SSS_PAM_SETCRED, pamh, flags, argc, argv); +} + +PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, + const char **argv ) +{ + return pam_sss(SSS_PAM_ACCT_MGMT, pamh, flags, argc, argv); +} + +PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, + const char **argv ) +{ + return pam_sss(SSS_PAM_CHAUTHTOK, pamh, flags, argc, argv); +} + +PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, + const char **argv ) +{ + return pam_sss(SSS_PAM_OPEN_SESSION, pamh, flags, argc, argv); +} + +PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, + const char **argv ) +{ + return pam_sss(SSS_PAM_CLOSE_SESSION, pamh, flags, argc, argv); +} + + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_sssd_modstruct ={ + "pam_sssd", + pam_sm_authenticate, + pam_sm_setcred, + pam_sm_acct_mgmt, + pam_sm_open_session, + pam_sm_close_session, + pam_sm_chauthtok +}; + +#endif diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h index d3570f505a1..817a61bb266 100644 --- a/src/sss_client/sss_cli.h +++ b/src/sss_client/sss_cli.h @@ -1,814 +1,824 @@ -/* - SSSD - - Client Interface for NSS and PAM. - - Authors: - Simo Sorce - - Copyright (C) Red Hat, Inc 2007 - - 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 . -*/ - -#ifndef _SSSCLI_H -#define _SSSCLI_H - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "shared/safealign.h" - -#ifndef HAVE_ERRNO_T -#define HAVE_ERRNO_T -typedef int errno_t; -#else -#include -#endif - -#ifndef EOK -#define EOK 0 -#endif - -#ifndef NETDB_INTERNAL -#define NETDB_INTERNAL (-1) -#endif - -#define SSS_NSS_PROTOCOL_VERSION 1 -#define SSS_PAM_PROTOCOL_VERSION 3 -#define SSS_SUDO_PROTOCOL_VERSION 1 -#define SSS_AUTOFS_PROTOCOL_VERSION 1 -#define SSS_SSH_PROTOCOL_VERSION 0 -#define SSS_PAC_PROTOCOL_VERSION 1 - -#ifdef LOGIN_NAME_MAX -#define SSS_NAME_MAX LOGIN_NAME_MAX -#else -#define SSS_NAME_MAX 256 -#endif - -/** - * @defgroup sss_cli_command SSS client commands - * @{ - */ - -/** The allowed commands an SSS client can send to the SSSD */ - -enum sss_cli_command { -/* null */ - SSS_CLI_NULL = 0x0000, - -/* version */ - SSS_GET_VERSION = 0x0001, - -/* passwd */ - - SSS_NSS_GETPWNAM = 0x0011, - SSS_NSS_GETPWUID = 0x0012, - SSS_NSS_SETPWENT = 0x0013, - SSS_NSS_GETPWENT = 0x0014, - SSS_NSS_ENDPWENT = 0x0015, - - SSS_NSS_GETPWNAM_EX = 0x0019, - SSS_NSS_GETPWUID_EX = 0x001A, - -/* group */ - - SSS_NSS_GETGRNAM = 0x0021, - SSS_NSS_GETGRGID = 0x0022, - SSS_NSS_SETGRENT = 0x0023, - SSS_NSS_GETGRENT = 0x0024, - SSS_NSS_ENDGRENT = 0x0025, - SSS_NSS_INITGR = 0x0026, - - SSS_NSS_GETGRNAM_EX = 0x0029, - SSS_NSS_GETGRGID_EX = 0x002A, - SSS_NSS_INITGR_EX = 0x002E, - -#if 0 -/* aliases */ - - SSS_NSS_GETALIASBYNAME = 0x0031, - SSS_NSS_GETALIASBYPORT = 0x0032, - SSS_NSS_SETALIASENT = 0x0033, - SSS_NSS_GETALIASENT = 0x0034, - SSS_NSS_ENDALIASENT = 0x0035, - -/* ethers */ - - SSS_NSS_GETHOSTTON = 0x0041, - SSS_NSS_GETNTOHOST = 0x0042, - SSS_NSS_SETETHERENT = 0x0043, - SSS_NSS_GETETHERENT = 0x0044, - SSS_NSS_ENDETHERENT = 0x0045, -#endif - -/* hosts */ - - SSS_NSS_GETHOSTBYNAME = 0x0051, - SSS_NSS_GETHOSTBYNAME2 = 0x0052, - SSS_NSS_GETHOSTBYADDR = 0x0053, - SSS_NSS_SETHOSTENT = 0x0054, - SSS_NSS_GETHOSTENT = 0x0055, - SSS_NSS_ENDHOSTENT = 0x0056, - -/* netgroup */ - - SSS_NSS_SETNETGRENT = 0x0061, - SSS_NSS_GETNETGRENT = 0x0062, - SSS_NSS_ENDNETGRENT = 0x0063, - -/* networks */ - - SSS_NSS_GETNETBYNAME = 0x0071, - SSS_NSS_GETNETBYADDR = 0x0072, - SSS_NSS_SETNETENT = 0x0073, - SSS_NSS_GETNETENT = 0x0074, - SSS_NSS_ENDNETENT = 0x0075, - -#if 0 -/* protocols */ - - SSS_NSS_GETPROTOBYNAME = 0x0081, - SSS_NSS_GETPROTOBYNUM = 0x0082, - SSS_NSS_SETPROTOENT = 0x0083, - SSS_NSS_GETPROTOENT = 0x0084, - SSS_NSS_ENDPROTOENT = 0x0085, - -/* rpc */ - - SSS_NSS_GETRPCBYNAME = 0x0091, - SSS_NSS_GETRPCBYNUM = 0x0092, - SSS_NSS_SETRPCENT = 0x0093, - SSS_NSS_GETRPCENT = 0x0094, - SSS_NSS_ENDRPCENT = 0x0095, -#endif - -/* services */ - - SSS_NSS_GETSERVBYNAME = 0x00A1, - SSS_NSS_GETSERVBYPORT = 0x00A2, - SSS_NSS_SETSERVENT = 0x00A3, - SSS_NSS_GETSERVENT = 0x00A4, - SSS_NSS_ENDSERVENT = 0x00A5, - -#if 0 -/* shadow */ - - SSS_NSS_GETSPNAM = 0x00B1, - SSS_NSS_GETSPUID = 0x00B2, - SSS_NSS_SETSPENT = 0x00B3, - SSS_NSS_GETSPENT = 0x00B4, - SSS_NSS_ENDSPENT = 0x00B5, -#endif - -/* SUDO */ - SSS_SUDO_GET_SUDORULES = 0x00C1, - SSS_SUDO_GET_DEFAULTS = 0x00C2, - -/* autofs */ - SSS_AUTOFS_SETAUTOMNTENT = 0x00D1, - SSS_AUTOFS_GETAUTOMNTENT = 0x00D2, - SSS_AUTOFS_GETAUTOMNTBYNAME = 0x00D3, - SSS_AUTOFS_ENDAUTOMNTENT = 0x00D4, - -/* SSH */ - SSS_SSH_GET_USER_PUBKEYS = 0x00E1, - SSS_SSH_GET_HOST_PUBKEYS = 0x00E2, - -/* PAM related calls */ - SSS_PAM_AUTHENTICATE = 0x00F1, /**< see pam_sm_authenticate(3) for - * details. - * - * Additionally we allow sssd to send - * the return code PAM_NEW_AUTHTOK_REQD - * during authentication if the - * authentication was successful but - * the authentication token is expired. - * To meet the standards of libpam we - * return PAM_SUCCESS for - * authentication and set a flag so - * that the account management module - * can return PAM_NEW_AUTHTOK_REQD if - * sssd return success for account - * management. We do this to reduce the - * communication with external servers, - * because there are cases, e.g. - * Kerberos authentication, where the - * information that the password is - * expired is already available during - * authentication. */ - SSS_PAM_SETCRED = 0x00F2, /**< see pam_sm_setcred(3) for - * details */ - SSS_PAM_ACCT_MGMT = 0x00F3, /**< see pam_sm_acct_mgmt(3) for - * details */ - SSS_PAM_OPEN_SESSION = 0x00F4, /**< see pam_sm_open_session(3) for - * details */ - SSS_PAM_CLOSE_SESSION = 0x00F5, /**< see pam_sm_close_session(3) for - *details */ - SSS_PAM_CHAUTHTOK = 0x00F6, /**< second run of the password change - * operation where the PAM_UPDATE_AUTHTOK - * flag is set and the real change may - * happen, see pam_sm_chauthtok(3) for - * details */ - SSS_PAM_CHAUTHTOK_PRELIM = 0x00F7, /**< first run of the password change - * operation where the PAM_PRELIM_CHECK - * flag is set, see pam_sm_chauthtok(3) - * for details */ - SSS_CMD_RENEW = 0x00F8, /**< Renew a credential with a limited - * lifetime, e.g. a Kerberos Ticket - * Granting Ticket (TGT) */ - SSS_PAM_PREAUTH = 0x00F9, /**< Request which can be run before - * an authentication request to find - * out which authentication methods - * are available for the given user. */ - SSS_GSSAPI_INIT = 0x00FA, /**< Initialize GSSAPI authentication. */ - SSS_GSSAPI_SEC_CTX = 0x00FB, /**< Establish GSSAPI security ctx. */ - -/* PAC responder calls */ - SSS_PAC_ADD_PAC_USER = 0x0101, - -/* ID-SID mapping calls */ -SSS_NSS_GETSIDBYNAME = 0x0111, /**< Takes a zero terminated fully qualified - name and returns the zero terminated - string representation of the SID of the - object with the given name. */ -SSS_NSS_GETSIDBYID = 0x0112, /**< Takes an unsigned 32bit integer (POSIX ID) - and returns the zero terminated string - representation of the SID of the object - with the given ID. */ -SSS_NSS_GETNAMEBYSID = 0x0113, /**< Takes the zero terminated string - representation of a SID and returns the - zero terminated fully qualified name of - the related object. */ -SSS_NSS_GETIDBYSID = 0x0114, /**< Takes the zero terminated string - representation of a SID and returns and - returns the POSIX ID of the related object - as unsigned 32bit integer value and - another unsigned 32bit integer value - indicating the type (unknown, user, group, - both) of the object. */ -SSS_NSS_GETORIGBYNAME = 0x0115, /**< Takes a zero terminated fully qualified - name and returns a list of zero - terminated strings with key-value pairs - where the first string is the key and - second the value. Hence the list should - have an even number of strings, if not - the whole list is invalid. */ -SSS_NSS_GETNAMEBYCERT = 0x0116, /**< Takes the zero terminated string - of the base64 encoded DER representation - of a X509 certificate and returns the zero - terminated fully qualified name of the - related object. */ -SSS_NSS_GETLISTBYCERT = 0x0117, /**< Takes the zero terminated string - of the base64 encoded DER representation - of a X509 certificate and returns a list - of zero terminated fully qualified names - of the related objects. */ -SSS_NSS_GETSIDBYUID = 0x0118, /**< Takes an unsigned 32bit integer (POSIX UID) - and return the zero terminated string - representation of the SID of the object - with the given UID. */ -SSS_NSS_GETSIDBYGID = 0x0119, /**< Takes an unsigned 32bit integer (POSIX GID) - and return the zero terminated string - representation of the SID of the object - with the given UID. */ -SSS_NSS_GETORIGBYUSERNAME = 0x011A, /**< Takes a zero terminated fully qualified - user name and returns a list of zero - terminated strings with key-value pairs - where the first string is the key and - second the value. Hence the list should - have an even number of strings, if not - the whole list is invalid. */ -SSS_NSS_GETORIGBYGROUPNAME = 0x011B, /**< Takes a zero terminated fully qualified - group name and returns a list of zero - terminated strings with key-value pairs - where the first string is the key and - second the value. Hence the list should - have an even number of strings, if not - the whole list is invalid. */ -SSS_NSS_GETSIDBYUSERNAME = 0x011C, /**< Takes a zero terminated fully qualified - name and returns the zero terminated - string representation of the SID of the - user with the given name. */ -SSS_NSS_GETSIDBYGROUPNAME = 0x011D, /**< Takes a zero terminated fully qualified - name and returns the zero terminated - string representation of the SID of the - group with the given name. */ - - -/* subid */ - SSS_NSS_GET_SUBID_RANGES = 0x0130, /**< Requests both subuid and subgid ranges - defined for a user. */ -}; - -/** - * @} - */ /* end of group sss_cli_command */ - - -/** - * @defgroup sss_pam SSSD and PAM - * - * SSSD offers authentication and authorization via PAM - * - * The SSSD provides a PAM client modules pam_sss which can be called from the - * PAM stack of the operation system. pam_sss will collect all the data about - * the user from the PAM stack and sends them via a socket to the PAM - * responder of the SSSD. The PAM responder selects the appropriate backend - * and forwards the data via D-BUS to the backend. The backend preforms the - * requested operation and sends the result expressed by a PAM return value - * and optional additional information back to the PAM responder. Finally the - * PAM responder forwards the response back to the client. - * - * @{ - */ - -/** - * @} - */ /* end of group sss_pam */ - -/** - * @defgroup sss_authtok_type Authentication Tokens - * @ingroup sss_pam - * - * To indicate to the components of the SSSD how to handle the authentication - * token the client sends the type of the authentication token to the SSSD. - * - * @{ - */ - -/** The different types of authentication tokens */ - -enum sss_authtok_type { - SSS_AUTHTOK_TYPE_EMPTY = 0x0000, /**< No authentication token - * available */ - SSS_AUTHTOK_TYPE_PASSWORD = 0x0001, /**< Authentication token is a - * password, it may or may no contain - * a trailing \\0 */ - SSS_AUTHTOK_TYPE_CCFILE = 0x0002, /**< Authentication token is a path to - * a Kerberos credential cache file, - * it may or may no contain - * a trailing \\0 */ - SSS_AUTHTOK_TYPE_2FA = 0x0003, /**< Authentication token has two - * factors, they may or may no contain - * a trailing \\0 */ - SSS_AUTHTOK_TYPE_SC_PIN = 0x0004, /**< Authentication token is a Smart - * Card PIN, it may or may no contain - * a trailing \\0 */ - SSS_AUTHTOK_TYPE_SC_KEYPAD = 0x0005, /**< Authentication token indicates - * 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 */ - SSS_AUTHTOK_TYPE_OAUTH2 = 0x0007, /**< Authentication token is a - * oauth2 token for presented - * challenge that is acquired from - * Kerberos. It may or may no - * contain a trailing \\0 */ - SSS_AUTHTOK_TYPE_PASSKEY = 0x0008, /**< Authentication token is a Passkey - * PIN, it may or may not contain - * a trailing \\0 */ - SSS_AUTHTOK_TYPE_PASSKEY_KRB = 0x0009, /**< Authentication token contains - * Passkey data used for Kerberos - * pre-authentication */ - SSS_AUTHTOK_TYPE_PASSKEY_REPLY = 0x0010, /**< Authentication token contains - * Passkey reply data presented as - * a kerberos challenge answer */ - SSS_AUTHTOK_TYPE_PAM_STACKED = 0x0011, /**< Authentication token contains - * either 2FA_SINGLE or PASSWORD - * via PAM use_first_pass */ -}; - -/** - * @} - */ /* end of group sss_authtok_type */ - -#define SSS_START_OF_PAM_REQUEST 0x4d415049 -#define SSS_END_OF_PAM_REQUEST 0x4950414d - -#define PAM_PREAUTH_INDICATOR PUBCONF_PATH"/pam_preauth_available" - -enum pam_item_type { - SSS_PAM_ITEM_EMPTY = 0x0000, - SSS_PAM_ITEM_USER, - SSS_PAM_ITEM_SERVICE, - SSS_PAM_ITEM_TTY, - SSS_PAM_ITEM_RUSER, - SSS_PAM_ITEM_RHOST, - SSS_PAM_ITEM_AUTHTOK, - SSS_PAM_ITEM_NEWAUTHTOK, - SSS_PAM_ITEM_CLI_LOCALE, - SSS_PAM_ITEM_CLI_PID, - SSS_PAM_ITEM_CHILD_PID, - SSS_PAM_ITEM_REQUESTED_DOMAINS, - SSS_PAM_ITEM_FLAGS, -}; - -#define PAM_CLI_FLAGS_USE_FIRST_PASS (1 << 0) -#define PAM_CLI_FLAGS_FORWARD_PASS (1 << 1) -#define PAM_CLI_FLAGS_USE_AUTHTOK (1 << 2) -#define PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER (1 << 3) -#define PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL (1 << 4) -#define PAM_CLI_FLAGS_USE_2FA (1 << 5) -#define PAM_CLI_FLAGS_ALLOW_MISSING_NAME (1 << 6) -#define PAM_CLI_FLAGS_PROMPT_ALWAYS (1 << 7) -#define PAM_CLI_FLAGS_TRY_CERT_AUTH (1 << 8) -#define PAM_CLI_FLAGS_REQUIRE_CERT_AUTH (1 << 9) -#define PAM_CLI_FLAGS_ALLOW_CHAUTHTOK_BY_ROOT (1 << 10) - -#define SSS_NSS_MAX_ENTRIES 256 -#define SSS_NSS_HEADER_SIZE (sizeof(uint32_t) * 4) -struct sss_cli_req_data { - size_t len; - const void *data; -}; - -/* this is in milliseconds, wait up to 300 seconds */ -#define SSS_CLI_SOCKET_TIMEOUT 300000 - -enum sss_status { - SSS_STATUS_TRYAGAIN, - SSS_STATUS_UNAVAIL, - SSS_STATUS_SUCCESS -}; - -/** - * @defgroup sss_pam_cli Responses to the PAM client - * @ingroup sss_pam - * @{ - */ - -/** - * @defgroup response_type Messages from the server - * @ingroup sss_pam_cli - * - * SSSD can send different kind of information back to the client. - * A response from the SSSD can contain 0 or more messages. Each message - * contains a type tag and the size of the message data, both are unsigned - * 32-bit integer values, followed be the message specific data. - * - * If the message is generated by a backend it is send back to the PAM - * responder via a D-BUS message in an array of D-BUS structs. The struct - * consists of a DBUS_TYPE_UINT32 for the tag and a DBUS_TYPE_ARRAY to hold - * the message. - * - * Examples: - * - #SSS_PAM_ENV_ITEM, - uint32_t | uint32_t | uint8_t[4] - ----------|----------|------------ - 0x03 | 0x04 | a=b\\0 - * @{ - */ - -/** Types of different messages */ - -enum response_type { - SSS_PAM_SYSTEM_INFO = 0x01, /**< Message for the system log. - * @param String, zero terminated. */ - SSS_PAM_DOMAIN_NAME, /**< Name of the domain the user belongs too. - * This messages is generated by the PAM responder. - * @param String, zero terminated, with the domain - * name. */ - SSS_PAM_ENV_ITEM, /**< Set and environment variable with pam_putenv(3). - * @param String, zero terminated, of the form - * name=value. See pam_putenv(3) for details. */ - SSS_ENV_ITEM, /**< Set and environment variable with putenv(3). - * @param String, zero terminated, of the form - * name=value. See putenv(3) for details. */ - SSS_ALL_ENV_ITEM, /**< Set and environment variable with putenv(3) and - * pam_putenv(3). - * @param String, zero terminated, of the form - * name=value. See putenv(3) and pam_putenv(3) for - * details. */ - SSS_PAM_USER_INFO, /**< A message which should be displayed to the user. - * @param User info message, see #user_info_type - * for details. */ - SSS_PAM_TEXT_MSG, /**< A plain text message which should be displayed to - * the user. This should only be used in the case where - * it is not possible to use SSS_PAM_USER_INFO. - * @param A zero terminated string. */ - SSS_PAM_OTP_INFO, /**< A message which optionally may contain the name - * of the vendor, the ID of an OTP token and a - * challenge. - * @param Three zero terminated strings, if one of the - * strings is missing the message will contain only - * an empty string (\0) for that component. */ - SSS_PAM_CERT_INFO, /**< A message indicating that Smartcard/certificate - * based authentication is available and contains - * details about the found Smartcard. - * @param user name, zero terminated - * @param token name, zero terminated - * @param PKCS#11 module name, zero terminated - * @param key id, zero terminated */ - SSS_OTP, /**< Indicates that the authtok was a OTP, so don't - * cache it. There is no message. - * @param None. */ - SSS_PASSWORD_PROMPTING, /**< Indicates that password prompting is possible. - * This might be used together with - * SSS_PAM_OTP_INFO to determine the type of - * prompting. There is no message. - * @param None. */ - SSS_CERT_AUTH_PROMPTING, /**< Indicates that on the server side - * Smartcard/certificate based authentication is - * available for the selected account. This might - * be used together with other prompting options - * to determine the type of prompting. - * @param None. */ - 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. */ - SSS_CHILD_KEEP_ALIVE, /**< Indicates that the child process is kept alived - * and further communication must be done with the - * same child. The message is the pid of the child - * process. */ - SSS_PAM_OAUTH2_INFO, /**< A message which contains the oauth2 - * parameters for the user. - * @param Three zero terminated strings: - * - verification_uri - * - verification_uri_complete - * - user_code - */ - SSS_PAM_PASSKEY_INFO, /**< Indicates that passkey authentication is available. - * including a parameter string which dictates whether - * prompting for PIN is needed. - * @param - * - prompt_pin - */ - SSS_PAM_PASSKEY_KRB_INFO, /**< A message containing the passkey parameters - * for the user. The key is the cryptographic challenge - * used as the key to the passkey hash table entry. - * @param - * - user verification (string) - * - key (string) - */ -}; - -/** - * @defgroup user_info_type User info messages - * @ingroup response_type - * - * To achieve a consistent user experience and to facilitate - * internationalization all messages show to the user are generate by the PAM - * client and not by the SSSD server components. To indicate what message the - * client should display to the user SSSD can send a #SSS_PAM_USER_INFO message - * where the data part contains one of the following tags as an unsigned - * 32-bit integer value and optional data. - * - * Examples: - * - #SSS_PAM_USER_INFO_OFFLINE_CHPASS - * uint32_t | uint32_t | uint32_t - * ----------|----------|---------- - * 0x06 | 0x04 | 0x03 - * - * - #SSS_PAM_USER_INFO_CHPASS_ERROR - * uint32_t | uint32_t | uint32_t | uint32_t | uint8_t[3] - * ----------|----------|----------|----------|------------ - * 0x06 | 0x0B | 0x04 | 0x03 | abc - * @{ - */ - -/** Different types of user messages */ - -enum user_info_type { - SSS_PAM_USER_INFO_OFFLINE_AUTH = 0x01, /**< Inform the user that the - * authentication happened offline. - * This message is generated by the - * PAM responder. - * @param Time when the cached - * password will expire in seconds - * since the UNIX Epoch as returned - * by time(2) as int64_t. A value - * of zero indicates that the - * cached password will never - * expire. */ - SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED, /**< Tell the user how low a new - * authentication is delayed. This - * message is generated by the PAM - * responder. - * @param Time when an - * authentication is allowed again - * in seconds since the UNIX Epoch - * as returned by time(2) as - * int64_t. */ - SSS_PAM_USER_INFO_OFFLINE_CHPASS, /**< * Tell the user that it is not - * possible to change the password while - * the system is offline. This message - * is generated by the PAM responder. */ - SSS_PAM_USER_INFO_OTP_CHPASS, /**< Tell the user that he needs to kinit - * or login and logout to get a TGT after - * an OTP password change */ - SSS_PAM_USER_INFO_CHPASS_ERROR, /**< Tell the user that a password change - * failed and optionally give a reason. - * @param Size of the message as unsigned - * 32-bit integer value. A value of 0 - * indicates that no message is following. - * @param String with the specified - * length. */ - - SSS_PAM_USER_INFO_GRACE_LOGIN, /**< Warn the user that the password is - * expired and inform about the remaining - * number of grace logins. - * @param The number of remaining grace - * logins as uint32_t */ - SSS_PAM_USER_INFO_EXPIRE_WARN, /**< Warn the user that the password will - * expire soon. - * @param Number of seconds before the - * user's password will expire. */ - - SSS_PAM_USER_INFO_ACCOUNT_EXPIRED, /**< Tell the user that the account - * has expired and optionally give - * a reason. - * @param Size of the message as - * unsigned 32-bit integer value. A - * value of 0 indicates that no message - * is following. @param String with the - * specified length. */ - - SSS_PAM_USER_INFO_PIN_LOCKED, /**< Tell the user that the PIN is locked */ - SSS_PAM_USER_INFO_NO_KRB_TGT, /**< Tell the user that Kerberos local/offline - auth was performed, therefore no TGT - is granted */ -}; -/** - * @} - */ /* end of group user_info_type */ - -/** - * @} - */ /* end of group response_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_PASSKEY, - 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); -const char *pc_get_passkey_inter_prompt(struct prompt_config *pc); -const char *pc_get_passkey_touch_prompt(struct prompt_config *pc); -errno_t pc_list_add_passkey(struct prompt_config ***pc_list, - const char *inter_prompt, - const char *touch_prompt); -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 -}; - -enum sss_cli_error_codes { - ESSS_SSS_CLI_ERROR_START = 0x1000, - ESSS_BAD_SOCKET, - ESSS_BAD_CRED_MSG, - ESSS_SERVER_NOT_TRUSTED, - ESSS_NO_SOCKET, - ESSS_SOCKET_STAT_ERROR, - - ESS_SSS_CLI_ERROR_MAX -}; - -const char *ssscli_err2string(int err); - -enum sss_status sss_cli_make_request_with_checks(enum sss_cli_command cmd, - struct sss_cli_req_data *rd, - int timeout, - uint8_t **repbuf, size_t *replen, - int *errnop, - const char *socket_name, - bool check_server_creds, - bool allow_custom_errors); - -enum nss_status sss_nss_make_request(enum sss_cli_command cmd, - struct sss_cli_req_data *rd, - uint8_t **repbuf, size_t *replen, - int *errnop); - -enum nss_status sss_nss_make_request_timeout(enum sss_cli_command cmd, - struct sss_cli_req_data *rd, - int timeout, - uint8_t **repbuf, size_t *replen, - int *errnop); - -int sss_pam_make_request(enum sss_cli_command cmd, - struct sss_cli_req_data *rd, - uint8_t **repbuf, size_t *replen, - int *errnop); - -void sss_cli_close_socket(void); - -/* Checks access to the PAC responder and opens the socket, if available. - * Required for processes like krb5_child that need to open the socket - * before dropping privs. - */ -int sss_pac_check_and_open(void); - -int sss_pac_make_request(enum sss_cli_command cmd, - struct sss_cli_req_data *rd, - uint8_t **repbuf, size_t *replen, - int *errnop); - -int sss_pac_make_request_with_lock(enum sss_cli_command cmd, - struct sss_cli_req_data *rd, - uint8_t **repbuf, size_t *replen, - int *errnop); - -#if 0 - -/* GETSPNAM Request: - * - * 0-X: string with name - * - * Replies: - * - * 0-3: 32bit unsigned number of results - * 4-7: 32bit unsigned (reserved/padding) - * For each result: - * 0-7: 64bit unsigned with Date of last change - * 8-15: 64bit unsigned with Min #days between changes - * 16-23: 64bit unsigned with Max #days between changes - * 24-31: 64bit unsigned with #days before pwd expires - * 32-39: 64bit unsigned with #days after pwd expires until account is disabled - * 40-47: 64bit unsigned with expiration date in days since 1970-01-01 - * 48-55: 64bit unsigned (flags/reserved) - * 56-X: sequence of 2, 0 terminated, strings (name, pwd) 64bit padded - */ -#endif - -/* Return strlen(str) or maxlen, whichever is shorter - * Returns EINVAL if str is NULL, EFBIG if str is longer than maxlen - * _len will return the result - */ -errno_t sss_strnlen(const char *str, size_t maxlen, size_t *len); - -void sss_nss_lock(void); -void sss_nss_unlock(void); -void sss_pam_lock(void); -void sss_pam_unlock(void); -void sss_nss_mc_lock(void); -void sss_nss_mc_unlock(void); -void sss_pac_lock(void); -void sss_pac_unlock(void); - -errno_t sss_readrep_copy_string(const char *in, - size_t *offset, - size_t *slen, - size_t *dlen, - char **out, - size_t *size); - -enum pam_gssapi_cmd { - PAM_GSSAPI_GET_NAME, - PAM_GSSAPI_INIT, - PAM_GSSAPI_SENTINEL -}; - -#endif /* _SSSCLI_H */ +/* + SSSD + + Client Interface for NSS and PAM. + + Authors: + Simo Sorce + + Copyright (C) Red Hat, Inc 2007 + + 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 . +*/ + +#ifndef _SSSCLI_H +#define _SSSCLI_H + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "shared/safealign.h" + +#ifndef HAVE_ERRNO_T +#define HAVE_ERRNO_T +typedef int errno_t; +#else +#include +#endif + +#ifndef EOK +#define EOK 0 +#endif + +#ifndef NETDB_INTERNAL +#define NETDB_INTERNAL (-1) +#endif + +#define SSS_NSS_PROTOCOL_VERSION 1 +#define SSS_PAM_PROTOCOL_VERSION 3 +#define SSS_SUDO_PROTOCOL_VERSION 1 +#define SSS_AUTOFS_PROTOCOL_VERSION 1 +#define SSS_SSH_PROTOCOL_VERSION 0 +#define SSS_PAC_PROTOCOL_VERSION 1 + +#ifdef LOGIN_NAME_MAX +#define SSS_NAME_MAX LOGIN_NAME_MAX +#else +#define SSS_NAME_MAX 256 +#endif + +/** + * @defgroup sss_cli_command SSS client commands + * @{ + */ + +/** The allowed commands an SSS client can send to the SSSD */ + +enum sss_cli_command { +/* null */ + SSS_CLI_NULL = 0x0000, + +/* version */ + SSS_GET_VERSION = 0x0001, + +/* passwd */ + + SSS_NSS_GETPWNAM = 0x0011, + SSS_NSS_GETPWUID = 0x0012, + SSS_NSS_SETPWENT = 0x0013, + SSS_NSS_GETPWENT = 0x0014, + SSS_NSS_ENDPWENT = 0x0015, + + SSS_NSS_GETPWNAM_EX = 0x0019, + SSS_NSS_GETPWUID_EX = 0x001A, + +/* group */ + + SSS_NSS_GETGRNAM = 0x0021, + SSS_NSS_GETGRGID = 0x0022, + SSS_NSS_SETGRENT = 0x0023, + SSS_NSS_GETGRENT = 0x0024, + SSS_NSS_ENDGRENT = 0x0025, + SSS_NSS_INITGR = 0x0026, + + SSS_NSS_GETGRNAM_EX = 0x0029, + SSS_NSS_GETGRGID_EX = 0x002A, + SSS_NSS_INITGR_EX = 0x002E, + +#if 0 +/* aliases */ + + SSS_NSS_GETALIASBYNAME = 0x0031, + SSS_NSS_GETALIASBYPORT = 0x0032, + SSS_NSS_SETALIASENT = 0x0033, + SSS_NSS_GETALIASENT = 0x0034, + SSS_NSS_ENDALIASENT = 0x0035, + +/* ethers */ + + SSS_NSS_GETHOSTTON = 0x0041, + SSS_NSS_GETNTOHOST = 0x0042, + SSS_NSS_SETETHERENT = 0x0043, + SSS_NSS_GETETHERENT = 0x0044, + SSS_NSS_ENDETHERENT = 0x0045, +#endif + +/* hosts */ + + SSS_NSS_GETHOSTBYNAME = 0x0051, + SSS_NSS_GETHOSTBYNAME2 = 0x0052, + SSS_NSS_GETHOSTBYADDR = 0x0053, + SSS_NSS_SETHOSTENT = 0x0054, + SSS_NSS_GETHOSTENT = 0x0055, + SSS_NSS_ENDHOSTENT = 0x0056, + +/* netgroup */ + + SSS_NSS_SETNETGRENT = 0x0061, + SSS_NSS_GETNETGRENT = 0x0062, + SSS_NSS_ENDNETGRENT = 0x0063, + +/* networks */ + + SSS_NSS_GETNETBYNAME = 0x0071, + SSS_NSS_GETNETBYADDR = 0x0072, + SSS_NSS_SETNETENT = 0x0073, + SSS_NSS_GETNETENT = 0x0074, + SSS_NSS_ENDNETENT = 0x0075, + +#if 0 +/* protocols */ + + SSS_NSS_GETPROTOBYNAME = 0x0081, + SSS_NSS_GETPROTOBYNUM = 0x0082, + SSS_NSS_SETPROTOENT = 0x0083, + SSS_NSS_GETPROTOENT = 0x0084, + SSS_NSS_ENDPROTOENT = 0x0085, + +/* rpc */ + + SSS_NSS_GETRPCBYNAME = 0x0091, + SSS_NSS_GETRPCBYNUM = 0x0092, + SSS_NSS_SETRPCENT = 0x0093, + SSS_NSS_GETRPCENT = 0x0094, + SSS_NSS_ENDRPCENT = 0x0095, +#endif + +/* services */ + + SSS_NSS_GETSERVBYNAME = 0x00A1, + SSS_NSS_GETSERVBYPORT = 0x00A2, + SSS_NSS_SETSERVENT = 0x00A3, + SSS_NSS_GETSERVENT = 0x00A4, + SSS_NSS_ENDSERVENT = 0x00A5, + +#if 0 +/* shadow */ + + SSS_NSS_GETSPNAM = 0x00B1, + SSS_NSS_GETSPUID = 0x00B2, + SSS_NSS_SETSPENT = 0x00B3, + SSS_NSS_GETSPENT = 0x00B4, + SSS_NSS_ENDSPENT = 0x00B5, +#endif + +/* SUDO */ + SSS_SUDO_GET_SUDORULES = 0x00C1, + SSS_SUDO_GET_DEFAULTS = 0x00C2, + +/* autofs */ + SSS_AUTOFS_SETAUTOMNTENT = 0x00D1, + SSS_AUTOFS_GETAUTOMNTENT = 0x00D2, + SSS_AUTOFS_GETAUTOMNTBYNAME = 0x00D3, + SSS_AUTOFS_ENDAUTOMNTENT = 0x00D4, + +/* SSH */ + SSS_SSH_GET_USER_PUBKEYS = 0x00E1, + SSS_SSH_GET_HOST_PUBKEYS = 0x00E2, + +/* PAM related calls */ + SSS_PAM_AUTHENTICATE = 0x00F1, /**< see pam_sm_authenticate(3) for + * details. + * + * Additionally we allow sssd to send + * the return code PAM_NEW_AUTHTOK_REQD + * during authentication if the + * authentication was successful but + * the authentication token is expired. + * To meet the standards of libpam we + * return PAM_SUCCESS for + * authentication and set a flag so + * that the account management module + * can return PAM_NEW_AUTHTOK_REQD if + * sssd return success for account + * management. We do this to reduce the + * communication with external servers, + * because there are cases, e.g. + * Kerberos authentication, where the + * information that the password is + * expired is already available during + * authentication. */ + SSS_PAM_SETCRED = 0x00F2, /**< see pam_sm_setcred(3) for + * details */ + SSS_PAM_ACCT_MGMT = 0x00F3, /**< see pam_sm_acct_mgmt(3) for + * details */ + SSS_PAM_OPEN_SESSION = 0x00F4, /**< see pam_sm_open_session(3) for + * details */ + SSS_PAM_CLOSE_SESSION = 0x00F5, /**< see pam_sm_close_session(3) for + *details */ + SSS_PAM_CHAUTHTOK = 0x00F6, /**< second run of the password change + * operation where the PAM_UPDATE_AUTHTOK + * flag is set and the real change may + * happen, see pam_sm_chauthtok(3) for + * details */ + SSS_PAM_CHAUTHTOK_PRELIM = 0x00F7, /**< first run of the password change + * operation where the PAM_PRELIM_CHECK + * flag is set, see pam_sm_chauthtok(3) + * for details */ + SSS_CMD_RENEW = 0x00F8, /**< Renew a credential with a limited + * lifetime, e.g. a Kerberos Ticket + * Granting Ticket (TGT) */ + SSS_PAM_PREAUTH = 0x00F9, /**< Request which can be run before + * an authentication request to find + * out which authentication methods + * are available for the given user. */ + SSS_GSSAPI_INIT = 0x00FA, /**< Initialize GSSAPI authentication. */ + SSS_GSSAPI_SEC_CTX = 0x00FB, /**< Establish GSSAPI security ctx. */ + SSS_PAM_PASSKEY_PREAUTH = 0x00FC, /**< could be called to request device + * information of device supporting + * passkey credential. + * typically, the command returns + * SSS_PAM_PASSKEY_DEVINFO */ +/* PAC responder calls */ + SSS_PAC_ADD_PAC_USER = 0x0101, + +/* ID-SID mapping calls */ +SSS_NSS_GETSIDBYNAME = 0x0111, /**< Takes a zero terminated fully qualified + name and returns the zero terminated + string representation of the SID of the + object with the given name. */ +SSS_NSS_GETSIDBYID = 0x0112, /**< Takes an unsigned 32bit integer (POSIX ID) + and returns the zero terminated string + representation of the SID of the object + with the given ID. */ +SSS_NSS_GETNAMEBYSID = 0x0113, /**< Takes the zero terminated string + representation of a SID and returns the + zero terminated fully qualified name of + the related object. */ +SSS_NSS_GETIDBYSID = 0x0114, /**< Takes the zero terminated string + representation of a SID and returns and + returns the POSIX ID of the related object + as unsigned 32bit integer value and + another unsigned 32bit integer value + indicating the type (unknown, user, group, + both) of the object. */ +SSS_NSS_GETORIGBYNAME = 0x0115, /**< Takes a zero terminated fully qualified + name and returns a list of zero + terminated strings with key-value pairs + where the first string is the key and + second the value. Hence the list should + have an even number of strings, if not + the whole list is invalid. */ +SSS_NSS_GETNAMEBYCERT = 0x0116, /**< Takes the zero terminated string + of the base64 encoded DER representation + of a X509 certificate and returns the zero + terminated fully qualified name of the + related object. */ +SSS_NSS_GETLISTBYCERT = 0x0117, /**< Takes the zero terminated string + of the base64 encoded DER representation + of a X509 certificate and returns a list + of zero terminated fully qualified names + of the related objects. */ +SSS_NSS_GETSIDBYUID = 0x0118, /**< Takes an unsigned 32bit integer (POSIX UID) + and return the zero terminated string + representation of the SID of the object + with the given UID. */ +SSS_NSS_GETSIDBYGID = 0x0119, /**< Takes an unsigned 32bit integer (POSIX GID) + and return the zero terminated string + representation of the SID of the object + with the given UID. */ +SSS_NSS_GETORIGBYUSERNAME = 0x011A, /**< Takes a zero terminated fully qualified + user name and returns a list of zero + terminated strings with key-value pairs + where the first string is the key and + second the value. Hence the list should + have an even number of strings, if not + the whole list is invalid. */ +SSS_NSS_GETORIGBYGROUPNAME = 0x011B, /**< Takes a zero terminated fully qualified + group name and returns a list of zero + terminated strings with key-value pairs + where the first string is the key and + second the value. Hence the list should + have an even number of strings, if not + the whole list is invalid. */ +SSS_NSS_GETSIDBYUSERNAME = 0x011C, /**< Takes a zero terminated fully qualified + name and returns the zero terminated + string representation of the SID of the + user with the given name. */ +SSS_NSS_GETSIDBYGROUPNAME = 0x011D, /**< Takes a zero terminated fully qualified + name and returns the zero terminated + string representation of the SID of the + group with the given name. */ + + +/* subid */ + SSS_NSS_GET_SUBID_RANGES = 0x0130, /**< Requests both subuid and subgid ranges + defined for a user. */ +}; + +/** + * @} + */ /* end of group sss_cli_command */ + + +/** + * @defgroup sss_pam SSSD and PAM + * + * SSSD offers authentication and authorization via PAM + * + * The SSSD provides a PAM client modules pam_sss which can be called from the + * PAM stack of the operation system. pam_sss will collect all the data about + * the user from the PAM stack and sends them via a socket to the PAM + * responder of the SSSD. The PAM responder selects the appropriate backend + * and forwards the data via D-BUS to the backend. The backend preforms the + * requested operation and sends the result expressed by a PAM return value + * and optional additional information back to the PAM responder. Finally the + * PAM responder forwards the response back to the client. + * + * @{ + */ + +/** + * @} + */ /* end of group sss_pam */ + +/** + * @defgroup sss_authtok_type Authentication Tokens + * @ingroup sss_pam + * + * To indicate to the components of the SSSD how to handle the authentication + * token the client sends the type of the authentication token to the SSSD. + * + * @{ + */ + +/** The different types of authentication tokens */ + +enum sss_authtok_type { + SSS_AUTHTOK_TYPE_EMPTY = 0x0000, /**< No authentication token + * available */ + SSS_AUTHTOK_TYPE_PASSWORD = 0x0001, /**< Authentication token is a + * password, it may or may no contain + * a trailing \\0 */ + SSS_AUTHTOK_TYPE_CCFILE = 0x0002, /**< Authentication token is a path to + * a Kerberos credential cache file, + * it may or may no contain + * a trailing \\0 */ + SSS_AUTHTOK_TYPE_2FA = 0x0003, /**< Authentication token has two + * factors, they may or may no contain + * a trailing \\0 */ + SSS_AUTHTOK_TYPE_SC_PIN = 0x0004, /**< Authentication token is a Smart + * Card PIN, it may or may no contain + * a trailing \\0 */ + SSS_AUTHTOK_TYPE_SC_KEYPAD = 0x0005, /**< Authentication token indicates + * 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 */ + SSS_AUTHTOK_TYPE_OAUTH2 = 0x0007, /**< Authentication token is a + * oauth2 token for presented + * challenge that is acquired from + * Kerberos. It may or may no + * contain a trailing \\0 */ + SSS_AUTHTOK_TYPE_PASSKEY = 0x0008, /**< Authentication token is a Passkey + * PIN, it may or may not contain + * a trailing \\0 */ + SSS_AUTHTOK_TYPE_PASSKEY_KRB = 0x0009, /**< Authentication token contains + * Passkey data used for Kerberos + * pre-authentication */ + SSS_AUTHTOK_TYPE_PASSKEY_REPLY = 0x0010, /**< Authentication token contains + * Passkey reply data presented as + * a kerberos challenge answer */ + SSS_AUTHTOK_TYPE_PAM_STACKED = 0x0011, /**< Authentication token contains + * either 2FA_SINGLE or PASSWORD + * via PAM use_first_pass */ +}; + +/** + * @} + */ /* end of group sss_authtok_type */ + +#define SSS_START_OF_PAM_REQUEST 0x4d415049 +#define SSS_END_OF_PAM_REQUEST 0x4950414d + +#define PAM_PREAUTH_INDICATOR PUBCONF_PATH"/pam_preauth_available" + +enum pam_item_type { + SSS_PAM_ITEM_EMPTY = 0x0000, + SSS_PAM_ITEM_USER, + SSS_PAM_ITEM_SERVICE, + SSS_PAM_ITEM_TTY, + SSS_PAM_ITEM_RUSER, + SSS_PAM_ITEM_RHOST, + SSS_PAM_ITEM_AUTHTOK, + SSS_PAM_ITEM_NEWAUTHTOK, + SSS_PAM_ITEM_CLI_LOCALE, + SSS_PAM_ITEM_CLI_PID, + SSS_PAM_ITEM_CHILD_PID, + SSS_PAM_ITEM_REQUESTED_DOMAINS, + SSS_PAM_ITEM_FLAGS, +}; + +#define PAM_CLI_FLAGS_USE_FIRST_PASS (1 << 0) +#define PAM_CLI_FLAGS_FORWARD_PASS (1 << 1) +#define PAM_CLI_FLAGS_USE_AUTHTOK (1 << 2) +#define PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER (1 << 3) +#define PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL (1 << 4) +#define PAM_CLI_FLAGS_USE_2FA (1 << 5) +#define PAM_CLI_FLAGS_ALLOW_MISSING_NAME (1 << 6) +#define PAM_CLI_FLAGS_PROMPT_ALWAYS (1 << 7) +#define PAM_CLI_FLAGS_TRY_CERT_AUTH (1 << 8) +#define PAM_CLI_FLAGS_REQUIRE_CERT_AUTH (1 << 9) +#define PAM_CLI_FLAGS_ALLOW_CHAUTHTOK_BY_ROOT (1 << 10) +#define PAM_CLI_FLAGS_REQUIRE_PASSKEY_CACHED_DEVINFO (1 << 11) + +#define SSS_NSS_MAX_ENTRIES 256 +#define SSS_NSS_HEADER_SIZE (sizeof(uint32_t) * 4) +struct sss_cli_req_data { + size_t len; + const void *data; +}; + +/* this is in milliseconds, wait up to 300 seconds */ +#define SSS_CLI_SOCKET_TIMEOUT 300000 + +enum sss_status { + SSS_STATUS_TRYAGAIN, + SSS_STATUS_UNAVAIL, + SSS_STATUS_SUCCESS +}; + +/** + * @defgroup sss_pam_cli Responses to the PAM client + * @ingroup sss_pam + * @{ + */ + +/** + * @defgroup response_type Messages from the server + * @ingroup sss_pam_cli + * + * SSSD can send different kind of information back to the client. + * A response from the SSSD can contain 0 or more messages. Each message + * contains a type tag and the size of the message data, both are unsigned + * 32-bit integer values, followed be the message specific data. + * + * If the message is generated by a backend it is send back to the PAM + * responder via a D-BUS message in an array of D-BUS structs. The struct + * consists of a DBUS_TYPE_UINT32 for the tag and a DBUS_TYPE_ARRAY to hold + * the message. + * + * Examples: + * - #SSS_PAM_ENV_ITEM, + uint32_t | uint32_t | uint8_t[4] + ----------|----------|------------ + 0x03 | 0x04 | a=b\\0 + * @{ + */ + +/** Types of different messages */ + +enum response_type { + SSS_PAM_SYSTEM_INFO = 0x01, /**< Message for the system log. + * @param String, zero terminated. */ + SSS_PAM_DOMAIN_NAME, /**< Name of the domain the user belongs too. + * This messages is generated by the PAM responder. + * @param String, zero terminated, with the domain + * name. */ + SSS_PAM_ENV_ITEM, /**< Set and environment variable with pam_putenv(3). + * @param String, zero terminated, of the form + * name=value. See pam_putenv(3) for details. */ + SSS_ENV_ITEM, /**< Set and environment variable with putenv(3). + * @param String, zero terminated, of the form + * name=value. See putenv(3) for details. */ + SSS_ALL_ENV_ITEM, /**< Set and environment variable with putenv(3) and + * pam_putenv(3). + * @param String, zero terminated, of the form + * name=value. See putenv(3) and pam_putenv(3) for + * details. */ + SSS_PAM_USER_INFO, /**< A message which should be displayed to the user. + * @param User info message, see #user_info_type + * for details. */ + SSS_PAM_TEXT_MSG, /**< A plain text message which should be displayed to + * the user. This should only be used in the case where + * it is not possible to use SSS_PAM_USER_INFO. + * @param A zero terminated string. */ + SSS_PAM_OTP_INFO, /**< A message which optionally may contain the name + * of the vendor, the ID of an OTP token and a + * challenge. + * @param Three zero terminated strings, if one of the + * strings is missing the message will contain only + * an empty string (\0) for that component. */ + SSS_PAM_CERT_INFO, /**< A message indicating that Smartcard/certificate + * based authentication is available and contains + * details about the found Smartcard. + * @param user name, zero terminated + * @param token name, zero terminated + * @param PKCS#11 module name, zero terminated + * @param key id, zero terminated */ + SSS_OTP, /**< Indicates that the authtok was a OTP, so don't + * cache it. There is no message. + * @param None. */ + SSS_PASSWORD_PROMPTING, /**< Indicates that password prompting is possible. + * This might be used together with + * SSS_PAM_OTP_INFO to determine the type of + * prompting. There is no message. + * @param None. */ + SSS_CERT_AUTH_PROMPTING, /**< Indicates that on the server side + * Smartcard/certificate based authentication is + * available for the selected account. This might + * be used together with other prompting options + * to determine the type of prompting. + * @param None. */ + 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. */ + SSS_CHILD_KEEP_ALIVE, /**< Indicates that the child process is kept alived + * and further communication must be done with the + * same child. The message is the pid of the child + * process. */ + SSS_PAM_OAUTH2_INFO, /**< A message which contains the oauth2 + * parameters for the user. + * @param Three zero terminated strings: + * - verification_uri + * - verification_uri_complete + * - user_code + */ + SSS_PAM_PASSKEY_INFO, /**< Indicates that passkey authentication is available. + * including a parameter string which dictates whether + * prompting for PIN is needed. + * @param + * - prompt_pin + */ + SSS_PAM_PASSKEY_DEVINFO, /**< Indicates a passkey device information + * including a parameter string + * @param + * - device information string + */ + SSS_PAM_PASSKEY_KRB_INFO, /**< A message containing the passkey parameters + * for the user. The key is the cryptographic challenge + * used as the key to the passkey hash table entry. + * @param + * - user verification (string) + * - key (string) + */ +}; + +/** + * @defgroup user_info_type User info messages + * @ingroup response_type + * + * To achieve a consistent user experience and to facilitate + * internationalization all messages show to the user are generate by the PAM + * client and not by the SSSD server components. To indicate what message the + * client should display to the user SSSD can send a #SSS_PAM_USER_INFO message + * where the data part contains one of the following tags as an unsigned + * 32-bit integer value and optional data. + * + * Examples: + * - #SSS_PAM_USER_INFO_OFFLINE_CHPASS + * uint32_t | uint32_t | uint32_t + * ----------|----------|---------- + * 0x06 | 0x04 | 0x03 + * + * - #SSS_PAM_USER_INFO_CHPASS_ERROR + * uint32_t | uint32_t | uint32_t | uint32_t | uint8_t[3] + * ----------|----------|----------|----------|------------ + * 0x06 | 0x0B | 0x04 | 0x03 | abc + * @{ + */ + +/** Different types of user messages */ + +enum user_info_type { + SSS_PAM_USER_INFO_OFFLINE_AUTH = 0x01, /**< Inform the user that the + * authentication happened offline. + * This message is generated by the + * PAM responder. + * @param Time when the cached + * password will expire in seconds + * since the UNIX Epoch as returned + * by time(2) as int64_t. A value + * of zero indicates that the + * cached password will never + * expire. */ + SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED, /**< Tell the user how low a new + * authentication is delayed. This + * message is generated by the PAM + * responder. + * @param Time when an + * authentication is allowed again + * in seconds since the UNIX Epoch + * as returned by time(2) as + * int64_t. */ + SSS_PAM_USER_INFO_OFFLINE_CHPASS, /**< * Tell the user that it is not + * possible to change the password while + * the system is offline. This message + * is generated by the PAM responder. */ + SSS_PAM_USER_INFO_OTP_CHPASS, /**< Tell the user that he needs to kinit + * or login and logout to get a TGT after + * an OTP password change */ + SSS_PAM_USER_INFO_CHPASS_ERROR, /**< Tell the user that a password change + * failed and optionally give a reason. + * @param Size of the message as unsigned + * 32-bit integer value. A value of 0 + * indicates that no message is following. + * @param String with the specified + * length. */ + + SSS_PAM_USER_INFO_GRACE_LOGIN, /**< Warn the user that the password is + * expired and inform about the remaining + * number of grace logins. + * @param The number of remaining grace + * logins as uint32_t */ + SSS_PAM_USER_INFO_EXPIRE_WARN, /**< Warn the user that the password will + * expire soon. + * @param Number of seconds before the + * user's password will expire. */ + + SSS_PAM_USER_INFO_ACCOUNT_EXPIRED, /**< Tell the user that the account + * has expired and optionally give + * a reason. + * @param Size of the message as + * unsigned 32-bit integer value. A + * value of 0 indicates that no message + * is following. @param String with the + * specified length. */ + + SSS_PAM_USER_INFO_PIN_LOCKED, /**< Tell the user that the PIN is locked */ + SSS_PAM_USER_INFO_NO_KRB_TGT, /**< Tell the user that Kerberos local/offline + auth was performed, therefore no TGT + is granted */ +}; +/** + * @} + */ /* end of group user_info_type */ + +/** + * @} + */ /* end of group response_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_PASSKEY, + 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); +const char *pc_get_passkey_inter_prompt(struct prompt_config *pc); +const char *pc_get_passkey_touch_prompt(struct prompt_config *pc); +errno_t pc_list_add_passkey(struct prompt_config ***pc_list, + const char *inter_prompt, + const char *touch_prompt); +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 +}; + +enum sss_cli_error_codes { + ESSS_SSS_CLI_ERROR_START = 0x1000, + ESSS_BAD_SOCKET, + ESSS_BAD_CRED_MSG, + ESSS_SERVER_NOT_TRUSTED, + ESSS_NO_SOCKET, + ESSS_SOCKET_STAT_ERROR, + + ESS_SSS_CLI_ERROR_MAX +}; + +const char *ssscli_err2string(int err); + +enum sss_status sss_cli_make_request_with_checks(enum sss_cli_command cmd, + struct sss_cli_req_data *rd, + int timeout, + uint8_t **repbuf, size_t *replen, + int *errnop, + const char *socket_name, + bool check_server_creds, + bool allow_custom_errors); + +enum nss_status sss_nss_make_request(enum sss_cli_command cmd, + struct sss_cli_req_data *rd, + uint8_t **repbuf, size_t *replen, + int *errnop); + +enum nss_status sss_nss_make_request_timeout(enum sss_cli_command cmd, + struct sss_cli_req_data *rd, + int timeout, + uint8_t **repbuf, size_t *replen, + int *errnop); + +int sss_pam_make_request(enum sss_cli_command cmd, + struct sss_cli_req_data *rd, + uint8_t **repbuf, size_t *replen, + int *errnop); + +void sss_cli_close_socket(void); + +/* Checks access to the PAC responder and opens the socket, if available. + * Required for processes like krb5_child that need to open the socket + * before dropping privs. + */ +int sss_pac_check_and_open(void); + +int sss_pac_make_request(enum sss_cli_command cmd, + struct sss_cli_req_data *rd, + uint8_t **repbuf, size_t *replen, + int *errnop); + +int sss_pac_make_request_with_lock(enum sss_cli_command cmd, + struct sss_cli_req_data *rd, + uint8_t **repbuf, size_t *replen, + int *errnop); + +#if 0 + +/* GETSPNAM Request: + * + * 0-X: string with name + * + * Replies: + * + * 0-3: 32bit unsigned number of results + * 4-7: 32bit unsigned (reserved/padding) + * For each result: + * 0-7: 64bit unsigned with Date of last change + * 8-15: 64bit unsigned with Min #days between changes + * 16-23: 64bit unsigned with Max #days between changes + * 24-31: 64bit unsigned with #days before pwd expires + * 32-39: 64bit unsigned with #days after pwd expires until account is disabled + * 40-47: 64bit unsigned with expiration date in days since 1970-01-01 + * 48-55: 64bit unsigned (flags/reserved) + * 56-X: sequence of 2, 0 terminated, strings (name, pwd) 64bit padded + */ +#endif + +/* Return strlen(str) or maxlen, whichever is shorter + * Returns EINVAL if str is NULL, EFBIG if str is longer than maxlen + * _len will return the result + */ +errno_t sss_strnlen(const char *str, size_t maxlen, size_t *len); + +void sss_nss_lock(void); +void sss_nss_unlock(void); +void sss_pam_lock(void); +void sss_pam_unlock(void); +void sss_nss_mc_lock(void); +void sss_nss_mc_unlock(void); +void sss_pac_lock(void); +void sss_pac_unlock(void); + +errno_t sss_readrep_copy_string(const char *in, + size_t *offset, + size_t *slen, + size_t *dlen, + char **out, + size_t *size); + +enum pam_gssapi_cmd { + PAM_GSSAPI_GET_NAME, + PAM_GSSAPI_INIT, + PAM_GSSAPI_SENTINEL +}; + +#endif /* _SSSCLI_H */ From bdfb4622d2c79b00221baadd88caa2212592fe93 Mon Sep 17 00:00:00 2001 From: Philippe Smadja Date: Thu, 12 Jun 2025 16:18:47 +0200 Subject: [PATCH 2/8] fix files formatting --- src/passkey_child/passkey_child.c | 218 +- src/passkey_child/passkey_child.h | 16 + src/passkey_child/passkey_child_assert.c | 1014 +-- src/passkey_child/passkey_child_common.c | 1862 ++--- src/passkey_child/passkey_child_credentials.c | 1371 ++-- src/passkey_child/passkey_child_devices.c | 492 +- src/responder/pam/pamsrv_cmd.c | 6331 ++++++++-------- src/responder/pam/pamsrv_passkey.c | 3342 ++++---- src/responder/pam/pamsrv_passkey.h | 189 +- src/sss_client/pam_message.h | 161 +- src/sss_client/pam_sss.c | 6708 +++++++++-------- src/sss_client/sss_cli.h | 1638 ++-- 12 files changed, 12152 insertions(+), 11190 deletions(-) diff --git a/src/passkey_child/passkey_child.c b/src/passkey_child/passkey_child.c index 320996608ec..3ca35dd02a7 100644 --- a/src/passkey_child/passkey_child.c +++ b/src/passkey_child/passkey_child.c @@ -1,106 +1,112 @@ -/* - SSSD - - Helper child to commmunicate with passkey devices - - Authors: - Iker Pedrosa - - Copyright (C) 2022 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 . -*/ - -#include -#include - -#include "util/debug.h" -#include "util/util.h" - -#include "passkey_child.h" - -int main(int argc, const char *argv[]) -{ - TALLOC_CTX *main_ctx = NULL; - struct passkey_data data; - int init_flags = 0; - errno_t ret = EOK; - - main_ctx = talloc_new(NULL); - if (main_ctx == NULL) { - ERROR("talloc_new() failed.\n"); - talloc_free(discard_const(debug_prg_name)); - ret = ENOMEM; - goto done; - } - - ret = parse_arguments(main_ctx, argc, argv, &data); - if (ret != EOK) { - ERROR("Error parsing argument(s).\n"); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "passkey_child started.\n"); - talloc_steal(main_ctx, debug_prg_name); - - ret = check_arguments(&data); - if (ret != EOK) { - ERROR("Invalid argument(s).\n"); - goto done; - } - - init_flags = (int)data.debug_libfido2 | FIDO_DISABLE_U2F_FALLBACK; - fido_init(init_flags); - - if (data.action == ACTION_REGISTER) { - ret = register_key(&data); - if (ret != EOK) { - ERROR("Error registering key.\n"); - goto done; - } - } else if (data.action == ACTION_AUTHENTICATE) { - ret = authenticate(&data); - if (ret == EOK) { - PRINT("Authentication success.\n"); - goto done; - } else { - ERROR("Authentication error.\n"); - goto done; - } - } else if (data.action == ACTION_GET_ASSERT) { - ret = get_assert_data(&data); - if (ret != EOK) { - ERROR("Error getting assertion data.\n"); - goto done; - } - } else if (data.action == ACTION_VERIFY_ASSERT) { - ret = verify_assert_data(&data); - if (ret == EOK) { - PRINT("Verification success.\n"); - goto done; - } else { - ERROR("Verification error.\n"); - goto done; - } - } - -done: - talloc_free(main_ctx); - - if (ret != EOK) { - return EXIT_FAILURE; - } else { - return EXIT_SUCCESS; - } -} +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa + + Copyright (C) 2022 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 . +*/ + +#include +#include + +#include "util/debug.h" +#include "util/util.h" + +#include "passkey_child.h" + +int main(int argc, const char *argv[]) +{ + TALLOC_CTX *main_ctx = NULL; + struct passkey_data data; + int init_flags = 0; + errno_t ret = EOK; + + main_ctx = talloc_new(NULL); + if (main_ctx == NULL) { + ERROR("talloc_new() failed.\n"); + talloc_free(discard_const(debug_prg_name)); + ret = ENOMEM; + goto done; + } + + ret = parse_arguments(main_ctx, argc, argv, &data); + if (ret != EOK) { + ERROR("Error parsing argument(s).\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "passkey_child started.\n"); + talloc_steal(main_ctx, debug_prg_name); + + ret = check_arguments(&data); + if (ret != EOK) { + ERROR("Invalid argument(s).\n"); + goto done; + } + + init_flags = (int)data.debug_libfido2 | FIDO_DISABLE_U2F_FALLBACK; + fido_init(init_flags); + + if (data.action == ACTION_REGISTER) { + ret = register_key(&data); + if (ret != EOK) { + ERROR("Error registering key.\n"); + goto done; + } + } else if (data.action == ACTION_AUTHENTICATE) { + ret = authenticate(&data); + if (ret == EOK) { + PRINT("Authentication success.\n"); + goto done; + } else { + ERROR("Authentication error.\n"); + goto done; + } + } else if (data.action == ACTION_GET_ASSERT) { + ret = get_assert_data(&data); + if (ret != EOK) { + ERROR("Error getting assertion data.\n"); + goto done; + } + } else if (data.action == ACTION_GET_DEVINFO) { + ret = get_device_info(&data); + if (ret != EOK) { + ERROR("Error getting device information.\n"); + goto done; + } + } else if (data.action == ACTION_VERIFY_ASSERT) { + ret = verify_assert_data(&data); + if (ret == EOK) { + PRINT("Verification success.\n"); + goto done; + } else { + ERROR("Verification error.\n"); + goto done; + } + } + +done: + talloc_free(main_ctx); + + if (ret != EOK) { + return EXIT_FAILURE; + } else { + return EXIT_SUCCESS; + } +} diff --git a/src/passkey_child/passkey_child.h b/src/passkey_child/passkey_child.h index 185e7e63af7..e0d35397770 100644 --- a/src/passkey_child/passkey_child.h +++ b/src/passkey_child/passkey_child.h @@ -40,6 +40,7 @@ enum action_opt { ACTION_REGISTER, ACTION_AUTHENTICATE, ACTION_GET_ASSERT, + ACTION_GET_DEVINFO, ACTION_VERIFY_ASSERT }; @@ -540,6 +541,21 @@ print_assert_data(const char *key_handle, const char *crypto_challenge, errno_t get_assert_data(struct passkey_data *data); +/** + * @brief Obtain device information + * + * Obtain information of the device supporting one of the handles in parameters + * + * @param[in] data passkey data + * + * @return 0 if a device is available + * create file /var/run/passkey-pinonly or passkey-pinuv + * error code otherwise. + */ +errno_t +get_device_info(struct passkey_data *data); + + /** * @brief Verify assertion data * diff --git a/src/passkey_child/passkey_child_assert.c b/src/passkey_child/passkey_child_assert.c index 5139dc82ef0..55056c61ab5 100644 --- a/src/passkey_child/passkey_child_assert.c +++ b/src/passkey_child/passkey_child_assert.c @@ -1,448 +1,566 @@ -/* - SSSD - - Helper child to commmunicate with passkey devices - - Authors: - Iker Pedrosa - - Copyright (C) 2022 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 . -*/ - -#include -#include -#include -#include -#include -#include - -#include "util/crypto/sss_crypto.h" -#include "util/debug.h" -#include "util/util.h" - -#include "passkey_child.h" - -errno_t -set_assert_client_data_hash(const struct passkey_data *data, - fido_assert_t *_assert) -{ - TALLOC_CTX *tmp_ctx = NULL; - unsigned char cdh[32]; - unsigned char *crypto_challenge = NULL; - size_t crypto_challenge_len; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - return ENOMEM; - } - - if (data->action == ACTION_AUTHENTICATE) { - ret = sss_generate_csprng_buffer(cdh, sizeof(cdh)); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "sss_generate_csprng_buffer failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - ret = fido_assert_set_clientdata_hash(_assert, cdh, sizeof(cdh)); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_clientdata_hash failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - } else { - crypto_challenge = sss_base64_decode(tmp_ctx, data->crypto_challenge, - &crypto_challenge_len); - if (crypto_challenge == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "failed to decode client data hash.\n"); - ret = ENOMEM; - goto done; - } - - if (crypto_challenge_len != 32) { - DEBUG(SSSDBG_OP_FAILURE, - "cryptographic-challenge length [%ld] must be 32.\n", - crypto_challenge_len); - ret = EINVAL; - goto done; - } - - ret = fido_assert_set_clientdata_hash(_assert, crypto_challenge, - crypto_challenge_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_clientdata_hash failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - } - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -set_assert_options(fido_opt_t up, fido_opt_t uv, fido_assert_t *_assert) -{ - errno_t ret; - - ret = fido_assert_set_up(_assert, up); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_up failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - ret = fido_assert_set_uv(_assert, uv); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_uv failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - -done: - return ret; -} - -errno_t -get_assert_auth_data_signature(TALLOC_CTX *mem_ctx, fido_assert_t *assert, - const char **_auth_data, - const char **_signature) -{ - TALLOC_CTX *tmp_ctx = NULL; - const unsigned char *auth_data; - const unsigned char *signature; - const char *b64_auth_data; - const char *b64_signature; - size_t auth_data_len; - size_t signature_len; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - return ENOMEM; - } - - auth_data = fido_assert_authdata_ptr(assert, 0); - if (auth_data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_authdata_ptr failed.\n"); - ret = ENOMEM; - goto done; - } - - auth_data_len = fido_assert_authdata_len(assert, 0); - if (auth_data_len == 0) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_authdata_len failed.\n"); - ret = ENOMEM; - goto done; - } - - b64_auth_data = sss_base64_encode(tmp_ctx, auth_data, auth_data_len); - if (b64_auth_data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "failed to encode authenticator data.\n"); - ret = ENOMEM; - goto done; - } - - signature = fido_assert_sig_ptr(assert, 0); - if (signature == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_sig_ptr failed.\n"); - ret = ENOMEM; - goto done; - } - - signature_len = fido_assert_sig_len(assert, 0); - if (signature_len == 0) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_sig_len failed.\n"); - ret = ENOMEM; - goto done; - } - - b64_signature = sss_base64_encode(tmp_ctx, signature, signature_len); - if (b64_signature == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "failed to encode signature.\n"); - ret = ENOMEM; - goto done; - } - - *_auth_data = talloc_steal(mem_ctx, b64_auth_data); - *_signature = talloc_steal(mem_ctx, b64_signature); - ret = EOK; - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -set_assert_auth_data_signature(const struct passkey_data *data, - fido_assert_t *_assert) -{ - TALLOC_CTX *tmp_ctx = NULL; - const unsigned char *auth_data = NULL; - const unsigned char *signature = NULL; - size_t auth_data_len; - size_t signature_len; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - ret = fido_assert_set_count(_assert, 1); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_count failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - auth_data = sss_base64_decode(tmp_ctx, data->auth_data, &auth_data_len); - if (auth_data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "failed to decode authenticator data.\n"); - ret = ENOMEM; - goto done; - } - - ret = fido_assert_set_authdata(_assert, 0, auth_data, auth_data_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_authdata failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - signature = sss_base64_decode(tmp_ctx, data->signature, &signature_len); - if (signature == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "failed to decode signature.\n"); - ret = ENOMEM; - goto done; - } - - ret = fido_assert_set_sig(_assert, 0, signature, signature_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_sig failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -prepare_assert(const struct passkey_data *data, int index, - fido_assert_t *_assert) -{ - TALLOC_CTX *tmp_ctx = NULL; - unsigned char *key_handle; - size_t key_handle_len; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - ret = fido_assert_set_rp(_assert, data->domain); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_rp failed [%d]: %s.\n", ret, fido_strerr(ret)); - goto done; - } - - key_handle = sss_base64_decode(tmp_ctx, data->key_handle_list[index], - &key_handle_len); - if (key_handle == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to decode key handle.\n"); - ret = ENOMEM; - goto done; - } - - ret = fido_assert_allow_cred(_assert, key_handle, key_handle_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_allow_cred failed [%d]: %s.\n", ret, fido_strerr(ret)); - goto done; - } - - ret = set_assert_options(FIDO_OPT_FALSE, FIDO_OPT_OMIT, _assert); - if (ret != FIDO_OK) { - goto done; - } - - ret = set_assert_client_data_hash(data, _assert); - if (ret != EOK) { - goto done; - } - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -reset_public_key(struct pk_data_t *_pk_data) -{ - if (_pk_data->type == COSE_ES256) { - es256_pk_free((es256_pk_t **) &_pk_data->public_key); - } else if (_pk_data->type == COSE_RS256) { - rs256_pk_free((rs256_pk_t **) &_pk_data->public_key); - } else if (_pk_data->type == COSE_EDDSA) { - eddsa_pk_free((eddsa_pk_t **) &_pk_data->public_key); - } - - return EOK; -} - -errno_t -request_assert(struct passkey_data *data, fido_dev_t *dev, - fido_assert_t *_assert) -{ - TALLOC_CTX *tmp_ctx = NULL; - char *pin = NULL; - bool has_pin; - bool has_uv; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - return ENOMEM; - } - - has_pin = fido_dev_has_pin(dev); - has_uv = fido_dev_has_uv(dev); - if (has_uv == true && data->user_verification != FIDO_OPT_FALSE) { - ret = fido_dev_get_assert(dev, _assert, NULL); - if (ret != FIDO_OK && has_pin == true) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_dev_get_assert failed [%d]: %s. " - "Falling back to PIN authentication.\n", - ret, fido_strerr(ret)); - } else if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } else { - DEBUG(SSSDBG_TRACE_FUNC, "fido_dev_get_assert succeeded.\n"); - goto done; - } - } - - if (has_pin == true && data->user_verification != FIDO_OPT_FALSE) { - ret = passkey_recv_pin(tmp_ctx, STDIN_FILENO, &pin); - if (ret != EOK) { - goto done; - } - } - - ret = fido_dev_get_assert(dev, _assert, pin); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - ret = fido_assert_set_uv(_assert, data->user_verification); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_uv failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - -done: - if (pin != NULL) { - sss_erase_mem_securely(pin, strlen(pin)); - } - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -verify_assert(struct pk_data_t *pk_data, fido_assert_t *assert) -{ - errno_t ret; - - ret = fido_assert_verify(assert, 0, pk_data->type, pk_data->public_key); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_assert_verify failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - -done: - return ret; -} - -void -print_assert_data(const char *key_handle, const char *crypto_challenge, - const char *auth_data, const char *signature) -{ - json_t *passkey = NULL; - char* string = NULL; - - /* Kerberos expects the user_id field, thus it cannot be removed and there - * is nothing to set so it's an empty string. - */ - passkey = json_pack("{s:s*, s:s*, s:s*, s:s*, s:s*}", - "credential_id", key_handle, - "cryptographic_challenge", crypto_challenge, - "authenticator_data", auth_data, - "assertion_signature", signature, - "user_id", ""); - if (passkey == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to create passkey object.\n"); - goto done; - } - - string = json_dumps(passkey, 0); - if (string == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "json_dumps() failed.\n"); - goto done; - } - - puts(string); - free(string); - -done: - json_decref(passkey); - - return; -} +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa + + Copyright (C) 2022 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "util/crypto/sss_crypto.h" +#include "util/debug.h" +#include "util/util.h" + +#include "passkey_child.h" + +errno_t +set_assert_client_data_hash(const struct passkey_data *data, + fido_assert_t *_assert) +{ + TALLOC_CTX *tmp_ctx = NULL; + unsigned char cdh[32]; + unsigned char *crypto_challenge = NULL; + size_t crypto_challenge_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + if (data->action == ACTION_AUTHENTICATE) { + ret = sss_generate_csprng_buffer(cdh, sizeof(cdh)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_generate_csprng_buffer failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + ret = fido_assert_set_clientdata_hash(_assert, cdh, sizeof(cdh)); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_clientdata_hash failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } else if (data->action == ACTION_GET_ASSERT) { + crypto_challenge = sss_base64_decode(tmp_ctx, data->crypto_challenge, + &crypto_challenge_len); + if (crypto_challenge == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to decode client data hash.\n"); + ret = ENOMEM; + goto done; + } + + if (crypto_challenge_len != 32) { + DEBUG(SSSDBG_OP_FAILURE, + "cryptographic-challenge length [%ld] must be 32.\n", + crypto_challenge_len); + ret = EINVAL; + goto done; + } + + ret = fido_assert_set_clientdata_hash(_assert, crypto_challenge, + crypto_challenge_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_clientdata_hash failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } + else { + memset(cdh, 0, sizeof(cdh)); + ret = fido_assert_set_clientdata_hash(_assert, cdh, sizeof(cdh)); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_clientdata_hash failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +set_assert_options(fido_opt_t up, fido_opt_t uv, fido_assert_t *_assert) +{ + errno_t ret; + + ret = fido_assert_set_up(_assert, up); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_up failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + ret = fido_assert_set_uv(_assert, uv); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_uv failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + +done: + return ret; +} + +errno_t +get_assert_auth_data_signature(TALLOC_CTX *mem_ctx, fido_assert_t *assert, + const char **_auth_data, + const char **_signature) +{ + TALLOC_CTX *tmp_ctx = NULL; + const unsigned char *auth_data; + const unsigned char *signature; + const char *b64_auth_data; + const char *b64_signature; + size_t auth_data_len; + size_t signature_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + auth_data = fido_assert_authdata_ptr(assert, 0); + if (auth_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_authdata_ptr failed.\n"); + ret = ENOMEM; + goto done; + } + + auth_data_len = fido_assert_authdata_len(assert, 0); + if (auth_data_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_authdata_len failed.\n"); + ret = ENOMEM; + goto done; + } + + b64_auth_data = sss_base64_encode(tmp_ctx, auth_data, auth_data_len); + if (b64_auth_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to encode authenticator data.\n"); + ret = ENOMEM; + goto done; + } + + signature = fido_assert_sig_ptr(assert, 0); + if (signature == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_sig_ptr failed.\n"); + ret = ENOMEM; + goto done; + } + + signature_len = fido_assert_sig_len(assert, 0); + if (signature_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_sig_len failed.\n"); + ret = ENOMEM; + goto done; + } + + b64_signature = sss_base64_encode(tmp_ctx, signature, signature_len); + if (b64_signature == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to encode signature.\n"); + ret = ENOMEM; + goto done; + } + + *_auth_data = talloc_steal(mem_ctx, b64_auth_data); + *_signature = talloc_steal(mem_ctx, b64_signature); + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +set_assert_auth_data_signature(const struct passkey_data *data, + fido_assert_t *_assert) +{ + TALLOC_CTX *tmp_ctx = NULL; + const unsigned char *auth_data = NULL; + const unsigned char *signature = NULL; + size_t auth_data_len; + size_t signature_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = fido_assert_set_count(_assert, 1); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_count failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + auth_data = sss_base64_decode(tmp_ctx, data->auth_data, &auth_data_len); + if (auth_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to decode authenticator data.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_assert_set_authdata(_assert, 0, auth_data, auth_data_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_authdata failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + signature = sss_base64_decode(tmp_ctx, data->signature, &signature_len); + if (signature == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to decode signature.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_assert_set_sig(_assert, 0, signature, signature_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_sig failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +prepare_assert(const struct passkey_data *data, int index, + fido_assert_t *_assert) +{ + TALLOC_CTX *tmp_ctx = NULL; + unsigned char *key_handle; + size_t key_handle_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = fido_assert_set_rp(_assert, data->domain); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_rp failed [%d]: %s.\n", ret, fido_strerr(ret)); + goto done; + } + + key_handle = sss_base64_decode(tmp_ctx, data->key_handle_list[index], + &key_handle_len); + if (key_handle == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to decode key handle.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_assert_allow_cred(_assert, key_handle, key_handle_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_allow_cred failed [%d]: %s.\n", ret, fido_strerr(ret)); + goto done; + } + + ret = set_assert_options(FIDO_OPT_FALSE, FIDO_OPT_OMIT, _assert); + if (ret != FIDO_OK) { + goto done; + } + + ret = set_assert_client_data_hash(data, _assert); + if (ret != EOK) { + goto done; + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +reset_public_key(struct pk_data_t *_pk_data) +{ + if (_pk_data->type == COSE_ES256) { + es256_pk_free((es256_pk_t **) &_pk_data->public_key); + } else if (_pk_data->type == COSE_RS256) { + rs256_pk_free((rs256_pk_t **) &_pk_data->public_key); + } else if (_pk_data->type == COSE_EDDSA) { + eddsa_pk_free((eddsa_pk_t **) &_pk_data->public_key); + } + + return EOK; +} +#define DOPIN "/var/run/passkey-dopin" + +static +errno_t enable_dopin() +{ + int fd = creat (DOPIN, (mode_t)0000); + DEBUG(SSSDBG_TRACE_LIBS, "enable_dopin file [%s] fd=[%d], errno: [%d].\n", + DOPIN , fd, (fd == -1)? errno : 0); + close(fd); + + return EOK; +} + +static +errno_t disable_dopin() +{ + int ret = remove(DOPIN); + DEBUG(SSSDBG_TRACE_LIBS, "disable_dopin [%d]\n", ret); + return EOK; +} +#undef DOPIN + +errno_t +request_assert(struct passkey_data *data, fido_dev_t *dev, + fido_assert_t *_assert) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *pin = NULL; + bool has_pin; + bool has_uv; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + has_pin = fido_dev_has_pin(dev); + has_uv = fido_dev_has_uv(dev); + + if (has_pin == true && data->user_verification != FIDO_OPT_FALSE) { + ret = passkey_recv_pin(tmp_ctx, STDIN_FILENO, &pin); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "PIN is missing: try UV\n"); + ret = FIDO_ERR_PIN_REQUIRED; + /* and try UV that will reset the ret code. + */ + } else { + /* DEBUG(SSSDBG_TRACE_FUNC, "Proceed with PIN [%s]\n", pin); */ + /* ------------- */ + + ret = fido_dev_get_assert(dev, _assert, pin); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert PIN failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "fido_dev_get_assert PIN succeeded.\n"); + + ret = fido_assert_set_uv(_assert, data->user_verification); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_uv failed [%d]: %s.\n", + ret, fido_strerr(ret)); + } + /* error or not: finished */ + goto done; + } + } + + if (has_uv == true && data->user_verification != FIDO_OPT_FALSE) { + + ret = fido_dev_get_assert(dev, _assert, NULL); + if (ret != FIDO_OK && has_pin == true) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_dev_get_assert UV failed [%d]: %s. " + "User shall fall back to PIN authentication.\n", + ret, fido_strerr(ret)); + } else if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert UV failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "fido_dev_get_assert UV succeeded.\n"); + } + /* error not: finished */ + } + +done: + if (pin != NULL) { + sss_erase_mem_securely(pin, strlen(pin)); + } + talloc_free(tmp_ctx); + + if (ret == FIDO_OK && has_uv == true) { + /* PIN or UV has been OK */ + disable_dopin(); + } else if (ret == FIDO_ERR_PIN_REQUIRED || ret == FIDO_ERR_UV_INVALID || + ret == FIDO_ERR_PIN_INVALID || ret == FIDO_ERR_UV_BLOCKED) { + enable_dopin(); + } + return ret; +} + +/* + * + * errno_t + * request_assert(struct passkey_data *data, fido_dev_t *dev, + * fido_assert_t *_assert) + * { + * TALLOC_CTX *tmp_ctx = NULL; + * char *pin = NULL; + * bool has_pin; + * bool has_uv; + * errno_t ret; + * + * tmp_ctx = talloc_new(NULL); + * if (tmp_ctx == NULL) { + * DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + * return ENOMEM; + * } + * + * has_pin = fido_dev_has_pin(dev); + * has_uv = fido_dev_has_uv(dev); + * if (has_uv == true && data->user_verification != FIDO_OPT_FALSE) { + * ret = fido_dev_get_assert(dev, _assert, NULL); + * if (ret != FIDO_OK && has_pin == true) { + * DEBUG(SSSDBG_OP_FAILURE, + * "fido_dev_get_assert failed [%d]: %s. " + * "Falling back to PIN authentication.\n", + * ret, fido_strerr(ret)); + * } else if (ret != FIDO_OK) { + * DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert failed [%d]: %s.\n", + * ret, fido_strerr(ret)); + * goto done; + * } else { + * DEBUG(SSSDBG_TRACE_FUNC, "fido_dev_get_assert succeeded.\n"); + * goto done; + * } + * } + * + * if (has_pin == true && data->user_verification != FIDO_OPT_FALSE) { + * ret = passkey_recv_pin(tmp_ctx, STDIN_FILENO, &pin); + * if (ret != EOK) { + * goto done; + * } + * } + * + * ret = fido_deev_get_assert(dev, _assert, pin); + * if (ret != FIDO_OK) { + * DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert failed [%d]: %s.\n", + * ret, fido_strerr(ret)); + * goto done; + * } + * + * ret = fido_assert_set_uv(_assert, data->user_verification); + * if (ret != FIDO_OK) { + * DEBUG(SSSDBG_OP_FAILURE, + * "fido_assert_set_uv failed [%d]: %s.\n", + * ret, fido_strerr(ret)); + * goto done; + * } + * + * done: + * if (pin != NULL) { + * sss_erase_mem_securely(pin, strlen(pin)); + * } + * talloc_free(tmp_ctx); + * + * return ret; + * } + */ + +errno_t +verify_assert(struct pk_data_t *pk_data, fido_assert_t *assert) +{ + errno_t ret; + + ret = fido_assert_verify(assert, 0, pk_data->type, pk_data->public_key); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_assert_verify failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + +done: + return ret; +} + +void +print_assert_data(const char *key_handle, const char *crypto_challenge, + const char *auth_data, const char *signature) +{ + json_t *passkey = NULL; + char* string = NULL; + + /* Kerberos expects the user_id field, thus it cannot be removed and there + * is nothing to set so it's an empty string. + */ + passkey = json_pack("{s:s*, s:s*, s:s*, s:s*, s:s*}", + "credential_id", key_handle, + "cryptographic_challenge", crypto_challenge, + "authenticator_data", auth_data, + "assertion_signature", signature, + "user_id", ""); + if (passkey == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create passkey object.\n"); + goto done; + } + + string = json_dumps(passkey, 0); + if (string == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "json_dumps() failed.\n"); + goto done; + } + + puts(string); + free(string); + +done: + json_decref(passkey); + + return; +} diff --git a/src/passkey_child/passkey_child_common.c b/src/passkey_child/passkey_child_common.c index 6fadd024333..83eeeb5d821 100644 --- a/src/passkey_child/passkey_child_common.c +++ b/src/passkey_child/passkey_child_common.c @@ -1,889 +1,973 @@ -/* - SSSD - - Helper child to commmunicate with passkey devices - - Authors: - Iker Pedrosa - - Copyright (C) 2022 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 . -*/ - -#include -#include -#include -#include - -#include -#include - -#include "util/crypto/sss_crypto.h" -#include "util/debug.h" -#include "util/util.h" -#include "util/crypto/sss_crypto.h" -#include "util/sss_prctl.h" - -#include "passkey_child.h" - -#if OPENSSL_VERSION_NUMBER >= 0x30000000 -#define get_id(x) EVP_PKEY_get_base_id((x)) -#else -#define get_id(x) EVP_PKEY_base_id((x)) -#endif /* OPENSSL_VERSION_NUMBER */ - -errno_t -cose_str_to_int(const char *type, int *out) -{ - if (strcasecmp(type, "es256") == 0) { - *out = COSE_ES256; - } else if (strcasecmp(type, "rs256") == 0) { - *out = COSE_RS256; - } else if (strcasecmp(type, "eddsa") == 0) { - *out = COSE_EDDSA; - } else { - *out = 0; - return ERR_INVALID_CRED_TYPE; - } - - return EOK; -} - -static errno_t -cred_type_str_to_enum(const char *type, enum credential_type *out) -{ - if (strcasecmp(type, "server-side") == 0) { - *out = CRED_SERVER_SIDE; - } else if (strcasecmp(type, "discoverable") == 0) { - *out = CRED_DISCOVERABLE; - } else { - *out = 0; - return ERR_INVALID_CRED_TYPE; - } - - return EOK; -} - -static errno_t -parse_public_keys_and_handlers(TALLOC_CTX *mem_ctx, - const char *public_keys, - const char *key_handles, - struct passkey_data *_data) -{ - TALLOC_CTX *tmp_ctx = NULL; - char **pk_list = NULL; - char **kh_list = NULL; - int pk_num = 0; - int kh_num = 0; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ERROR("talloc_new() failed\n"); - return ENOMEM; - } - - ret = split_on_separator(tmp_ctx, public_keys, ',', true, true, &pk_list, &pk_num); - if (ret != EOK && _data->action == ACTION_AUTHENTICATE) { - ERROR("Incorrectly formatted public keys.\n"); - goto done; - } - - ret = split_on_separator(tmp_ctx, key_handles, ',', true, true, &kh_list, &kh_num); - if (ret != EOK) { - ERROR("Incorrectly formatted public keys.\n"); - goto done; - } - - if (_data->action == ACTION_AUTHENTICATE && pk_num != kh_num) { - ERROR("The number of public keys and key handles don't match.\n"); - goto done; - } - - _data->public_key_list = talloc_steal(mem_ctx, pk_list); - _data->key_handle_list = talloc_steal(mem_ctx, kh_list); - _data->public_key_size = pk_num; - _data->key_handle_size = kh_num; - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -parse_arguments(TALLOC_CTX *mem_ctx, int argc, const char *argv[], - struct passkey_data *data) -{ - int opt; - int dumpable = 1; - int backtrace = 1; - int debug_fd = -1; - char *user_verification = NULL; - char *public_keys = NULL; - char *key_handles = NULL; - const char *opt_logger = NULL; - const char *type = NULL; - const char *cred_type = NULL; - poptContext pc; - errno_t ret; - - /* Set defaults */ - data->action = ACTION_NONE; - data->shortname = NULL; - data->domain = NULL; - data->public_key_list = NULL; - data->key_handle_list = NULL; - data->public_key_size = 0; - data->key_handle_size = 0; - data->crypto_challenge = NULL; - data->auth_data = NULL; - data->signature = NULL; - data->type = COSE_ES256; - data->user_verification = FIDO_OPT_OMIT; - data->cred_type = CRED_SERVER_SIDE; - data->user_id = NULL; - data->mapping_file = NULL; - data->quiet = false; - data->debug_libfido2 = false; - - struct poptOption long_options[] = { - POPT_AUTOHELP - SSSD_DEBUG_OPTS - {"dumpable", 0, POPT_ARG_INT, &dumpable, 0, - _("Allow core dumps"), NULL }, - {"backtrace", 0, POPT_ARG_INT, &backtrace, 0, - _("Enable debug backtrace"), NULL }, - {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0, - _("An open file descriptor for the debug logs"), NULL}, - SSSD_LOGGER_OPTS - {"register", 0, POPT_ARG_NONE, NULL, 'r', - _("Register a passkey for a user"), NULL }, - {"authenticate", 0, POPT_ARG_NONE, NULL, 'a', - _("Authenticate a user with a passkey"), NULL }, - {"get-assert", 0, POPT_ARG_NONE, NULL, 'g', - _("Obtain assertion data"), NULL }, - {"verify-assert", 0, POPT_ARG_NONE, NULL, 'v', - _("Verify assertion data"), NULL }, - {"username", 0, POPT_ARG_STRING, &data->shortname, 0, - _("Shortname"), NULL }, - {"domain", 0, POPT_ARG_STRING, &data->domain, 0, - _("Domain"), NULL}, - {"public-key", 0, POPT_ARG_STRING, &public_keys, 0, - _("Public key"), NULL }, - {"key-handle", 0, POPT_ARG_STRING, &key_handles, 0, - _("Key handle"), NULL}, - {"cryptographic-challenge", 0, POPT_ARG_STRING, - &data->crypto_challenge, 0, - _("Cryptographic challenge"), NULL}, - {"auth-data", 0, POPT_ARG_STRING, &data->auth_data, 0, - _("Authenticator data"), NULL}, - {"signature", 0, POPT_ARG_STRING, &data->signature, 0, - _("Signature"), NULL}, - {"type", 0, POPT_ARG_STRING, &type, 0, - _("COSE type to use"), "es256|rs256|eddsa"}, - {"user-verification", 0, POPT_ARG_STRING, &user_verification, 0, - _("Require user-verification"), "true|false"}, - {"cred-type", 0, POPT_ARG_STRING, &cred_type, 0, - _("Credential type"), "server-side|discoverable"}, - {"output-file", 0, POPT_ARG_STRING, &data->mapping_file, 0, - _("Write key mapping data to file"), NULL}, - {"quiet", 0, POPT_ARG_NONE, NULL, 'q', - _("Supress prompts"), NULL}, - {"debug-libfido2", 0, POPT_ARG_NONE, NULL, 'd', - _("Enable debug in libfido2 library"), NULL}, - SSSD_LOGGER_OPTS - POPT_TABLEEND - }; - - /* 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) { - case 'r': - if (data->action != ACTION_NONE - && data->action != ACTION_REGISTER) { - fprintf(stderr, "\nActions are mutually exclusive and should" \ - " be used only once.\n\n"); - poptPrintUsage(pc, stderr, 0); - ret = EINVAL; - goto done; - } - data->action = ACTION_REGISTER; - break; - case 'a': - if (data->action != ACTION_NONE - && data->action != ACTION_AUTHENTICATE) { - fprintf(stderr, "\nActions are mutually exclusive and should" \ - " be used only once.\n\n"); - poptPrintUsage(pc, stderr, 0); - ret = EINVAL; - goto done; - } - data->action = ACTION_AUTHENTICATE; - break; - case 'g': - if (data->action != ACTION_NONE - && data->action != ACTION_GET_ASSERT) { - fprintf(stderr, "\nActions are mutually exclusive and should" \ - " be used only once.\n\n"); - poptPrintUsage(pc, stderr, 0); - ret = EINVAL; - goto done; - } - data->action = ACTION_GET_ASSERT; - break; - case 'v': - if (data->action != ACTION_NONE - && data->action != ACTION_VERIFY_ASSERT) { - fprintf(stderr, "\nActions are mutually exclusive and should" \ - " be used only once.\n\n"); - poptPrintUsage(pc, stderr, 0); - ret = EINVAL; - goto done; - } - data->action = ACTION_VERIFY_ASSERT; - break; - case 'q': - data->quiet = true; - break; - case 'd': - data->debug_libfido2 = true; - break; - default: - fprintf(stderr, "\nInvalid option %s: %s\n\n", - poptBadOption(pc, 0), poptStrerror(opt)); - poptPrintUsage(pc, stderr, 0); - ret = EINVAL; - goto done; - } - } - - poptFreeContext(pc); - - sss_prctl_set_dumpable((dumpable == 0) ? 0 : 1); - - if (user_verification != NULL) { - if (strcmp(user_verification, "true") == 0) { - data->user_verification = FIDO_OPT_TRUE; - } else if (strcmp(user_verification, "false") == 0) { - data->user_verification = FIDO_OPT_FALSE; - } else if (user_verification != NULL) { - ERROR("[%s] is not a valid user-verification value.\n", - user_verification); - ret = EINVAL; - goto done; - } - } - - if (type != NULL) { - ret = cose_str_to_int(type, &data->type); - if (ret != EOK) { - ERROR("[%s] is not a valid COSE type (es256, rs256 or eddsa).\n", - type); - goto done; - } - } - - if (public_keys != NULL || key_handles != NULL) { - ret = parse_public_keys_and_handlers(mem_ctx, public_keys, key_handles, - data); - if (ret != EOK) { - goto done; - } - } - - if (cred_type != NULL) { - ret = cred_type_str_to_enum(cred_type, &data->cred_type); - if (ret != EOK) { - ERROR("[%s] is not a valid credential type (server-side or" - " discoverable).\n", - cred_type); - goto done; - } - } - - debug_prg_name = talloc_asprintf(NULL, "passkey_child[%d]", getpid()); - if (debug_prg_name == NULL) { - ERROR("talloc_asprintf failed.\n"); - ret = ENOMEM; - goto done; - } - - if (debug_fd != -1) { - opt_logger = sss_logger_str[FILES_LOGGER]; - ret = set_debug_file_from_fd(debug_fd); - if (ret != EOK) { - opt_logger = sss_logger_str[STDERR_LOGGER]; - ERROR("set_debug_file_from_fd failed.\n"); - } - } - - DEBUG_INIT(debug_level, opt_logger); - sss_set_debug_backtrace_enable((backtrace == 0) ? false : true); - - ret = EOK; - -done: - return ret; -} - -errno_t -check_arguments(const struct passkey_data *data) -{ - errno_t ret = EOK; - - DEBUG(SSSDBG_TRACE_FUNC, "Argument values after parsing\n"); - DEBUG(SSSDBG_TRACE_FUNC, "action: %d\n", data->action); - DEBUG(SSSDBG_TRACE_FUNC, "shortname: %s, domain: %s\n", - data->shortname, data->domain); - DEBUG(SSSDBG_TRACE_FUNC, "Number of key handles %d\n", - data->key_handle_size); - for (int i = 0; i < data->key_handle_size; i++) { - DEBUG(SSSDBG_TRACE_FUNC, "key %d, key_handle: %s\n", - i + 1, data->key_handle_list[i]); - } - DEBUG(SSSDBG_TRACE_FUNC, "Number of public keys %d\n", - data->public_key_size); - for (int i = 0; i < data->public_key_size; i++) { - DEBUG(SSSDBG_TRACE_FUNC, "key %d, public_key: %s\n", - i + 1, data->public_key_list[i]); - } - DEBUG(SSSDBG_TRACE_FUNC, "cryptographic-challenge: %s\n", - data->crypto_challenge); - DEBUG(SSSDBG_TRACE_FUNC, "auth-data: %s\n", - data->auth_data); - DEBUG(SSSDBG_TRACE_FUNC, "signature: %s\n", - data->signature); - DEBUG(SSSDBG_TRACE_FUNC, "type: %d\n", data->type); - DEBUG(SSSDBG_TRACE_FUNC, "user_verification: %d\n", - data->user_verification); - DEBUG(SSSDBG_TRACE_FUNC, "cred_type: %d\n", - data->cred_type); - DEBUG(SSSDBG_TRACE_FUNC, "Mapping file: %s\n", data->mapping_file); - DEBUG(SSSDBG_TRACE_FUNC, "debug_libfido2: %d\n", data->debug_libfido2); - - if (data->action == ACTION_NONE) { - DEBUG(SSSDBG_OP_FAILURE, "No action set.\n"); - ret = ERR_INPUT_PARSE; - goto done; - } - - if (data->action == ACTION_REGISTER - && (data->shortname == NULL || data->domain == NULL)) { - DEBUG(SSSDBG_OP_FAILURE, "Too few arguments for register action.\n"); - ret = ERR_INPUT_PARSE; - goto done; - } - - if (data->action == ACTION_AUTHENTICATE - && (data->domain == NULL || data->public_key_list == NULL - || data->key_handle_list == NULL)) { - DEBUG(SSSDBG_OP_FAILURE, - "Too few arguments for authenticate action.\n"); - ret = ERR_INPUT_PARSE; - goto done; - } - - if (data->action == ACTION_GET_ASSERT - && (data->domain == NULL || data->key_handle_list == NULL - || data->crypto_challenge == NULL)) { - DEBUG(SSSDBG_OP_FAILURE, - "Too few arguments for get-assert action.\n"); - ret = ERR_INPUT_PARSE; - goto done; - } - - if (data->action == ACTION_VERIFY_ASSERT - && (data->domain == NULL || data->public_key_list == NULL - || data->key_handle_list == NULL || data->crypto_challenge == NULL - || data->auth_data == NULL || data->signature == NULL)) { - DEBUG(SSSDBG_OP_FAILURE, - "Too few arguments for verify-assert action.\n"); - ret = ERR_INPUT_PARSE; - goto done; - } - -done: - return ret; -} - -errno_t -register_key(struct passkey_data *data) -{ - TALLOC_CTX *tmp_ctx = NULL; - fido_cred_t *cred = NULL; - fido_dev_t *dev = NULL; - fido_dev_info_t *dev_list = NULL; - size_t dev_number = 0; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - return ENOMEM; - } - - data->user_id = talloc_array(tmp_ctx, unsigned char, USER_ID_SIZE); - if (data->user_id == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_array() failed.\n"); - ret = ENOMEM; - goto done; - } - - cred = fido_cred_new(); - if (cred == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_new failed.\n"); - ret = ENOMEM; - goto done; - } - - dev_list = fido_dev_info_new(DEVLIST_SIZE); - if (dev_list == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = list_devices(dev_list, &dev_number); - if (ret != EOK) { - goto done; - } - - ret = select_device(data->action, dev_list, dev_number, NULL, &dev); - if (ret != EOK) { - goto done; - } - - ret = prepare_credentials(data, dev, cred); - if (ret != EOK) { - goto done; - } - - ret = generate_credentials(data, dev, cred); - if (ret != EOK) { - ERROR("A problem occurred while generating the credentials.\n"); - goto done; - } - - ret = verify_credentials(cred); - if (ret != EOK) { - goto done; - } - - ret = print_credentials(data, cred); - if (ret != EOK) { - goto done; - } - - ret = EOK; - -done: - fido_cred_free(&cred); - fido_dev_info_free(&dev_list, dev_number); - if (dev != NULL) { - fido_dev_close(dev); - } - fido_dev_free(&dev); - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -public_key_to_base64(TALLOC_CTX *mem_ctx, const struct passkey_data *data, - const unsigned char *public_key, size_t pk_len, - char **_pem_key) -{ - EVP_PKEY *evp_pkey = NULL; - unsigned char *pub = NULL; - char *pem_key = NULL; - unsigned long err; - errno_t ret; - - if (_pem_key == NULL) { - ret = EINVAL; - goto done; - } - - switch (data->type) { - case COSE_ES256: - ret = es256_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); - break; - case COSE_RS256: - ret = rs256_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); - break; - case COSE_EDDSA: - ret = eddsa_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); - break; - default: - DEBUG(SSSDBG_OP_FAILURE, "Invalid key type.\n"); - ret = EINVAL; - break; - } - - if (ret != EOK) { - goto done; - } - - ret = i2d_PUBKEY(evp_pkey, &pub); - if (ret < 1) { - err = ERR_get_error(); - DEBUG(SSSDBG_OP_FAILURE, "i2d_PUBKEY failed [%lu][%s].\n", - err, ERR_error_string(err, NULL)); - ret = EIO; - goto done; - } - - pem_key = sss_base64_encode(mem_ctx, pub, ret); - if (pem_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "sss_base64_encode failed.\n"); - ret = ENOMEM; - goto done; - } - - *_pem_key = pem_key; - ret = EOK; - -done: - free(pub); - - if (evp_pkey != NULL) { - EVP_PKEY_free(evp_pkey); - } - - return ret; -} - -errno_t -select_authenticator(struct passkey_data *data, fido_dev_t **_dev, - fido_assert_t **_assert, int *_index) -{ - fido_dev_info_t *dev_list = NULL; - fido_dev_t *dev = NULL; - size_t dev_list_len = 0; - fido_assert_t *assert = NULL; - int index = 0; - errno_t ret; - - dev_list = fido_dev_info_new(DEVLIST_SIZE); - if (dev_list == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_new failed.\n"); - ret = ENOMEM; - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Checking for devices.\n"); - ret = list_devices(dev_list, &dev_list_len); - if (ret != EOK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "%d key handles provided.\n", - data->key_handle_size); - - while (index < data->key_handle_size) { - DEBUG(SSSDBG_TRACE_FUNC, - "Preparing assert request data with key handle %d.\n", index + 1); - - assert = fido_assert_new(); - if (assert == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_assert_new failed.\n"); - ret = ENOMEM; - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Preparing assert request data.\n"); - ret = prepare_assert(data, index, assert); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Selecting device.\n"); - ret = select_device(data->action, dev_list, dev_list_len, assert, &dev); - if (ret == EOK) { - /* Key handle found in device */ - break; - } - - if (dev != NULL) { - fido_dev_close(dev); - } - fido_dev_free(&dev); - fido_assert_free(&assert); - index++; - } - - *_dev = dev; - *_assert = assert; - *_index = index; - -done: - if (ret != EOK) { - fido_assert_free(&assert); - } - fido_dev_info_free(&dev_list, dev_list_len); - - return ret; -} - -errno_t -public_key_to_libfido2(const char *pem_public_key, struct pk_data_t *_pk_data) -{ - TALLOC_CTX *tmp_ctx = NULL; - const unsigned char *public_key = NULL; - size_t pk_len; - const EVP_PKEY *evp_pkey = NULL; - int base_id; - unsigned long err; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - public_key = sss_base64_decode(tmp_ctx, pem_public_key, &pk_len); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to decode public key.\n"); - ret = ENOMEM; - goto done; - } - - evp_pkey = d2i_PUBKEY(NULL, &public_key, pk_len); - if (evp_pkey == NULL) { - err = ERR_get_error(); - DEBUG(SSSDBG_OP_FAILURE, "d2i_pubkey failed [%lu][%s].\n", - err, ERR_error_string(err, NULL)); - ret = EIO; - goto done; - } - - base_id = get_id(evp_pkey); - if (base_id == EVP_PKEY_EC) { - _pk_data->type = COSE_ES256; - ret = evp_pkey_to_es256_pubkey(evp_pkey, _pk_data); - } else if (base_id == EVP_PKEY_RSA) { - _pk_data->type = COSE_RS256; - ret = evp_pkey_to_rs256_pubkey(evp_pkey, _pk_data); - } else if (base_id == EVP_PKEY_ED25519) { - _pk_data->type = COSE_EDDSA; - ret = evp_pkey_to_eddsa_pubkey(evp_pkey, _pk_data); - } else { - DEBUG(SSSDBG_OP_FAILURE, - "Unrecognized key type.\n"); - ret = EINVAL; - } - if (ret != EOK) { - goto done; - } - - ret = EOK; - -done: - if (evp_pkey != NULL) { - EVP_PKEY_free(discard_const(evp_pkey)); - } - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -authenticate(struct passkey_data *data) -{ - TALLOC_CTX *tmp_ctx = NULL; - fido_assert_t *assert = NULL; - fido_dev_t *dev = NULL; - struct pk_data_t pk_data = { 0 }; - int index; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ERROR("talloc_new() failed\n"); - return ENOMEM; - } - - ret = select_authenticator(data, &dev, &assert, &index); - if (ret != EOK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Comparing the device and policy options.\n"); - ret = get_device_options(dev, data); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert options.\n"); - ret = set_assert_options(FIDO_OPT_TRUE, data->user_verification, assert); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to reset assert options.\n"); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert client data.\n"); - ret = set_assert_client_data_hash(data, assert); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to reset client data hash.\n"); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Decoding public key.\n"); - ret = public_key_to_libfido2(data->public_key_list[index], &pk_data); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Getting assert.\n"); - ret = request_assert(data, dev, assert); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Verifying assert.\n"); - ret = verify_assert(&pk_data, assert); - if (ret != FIDO_OK) { - goto done; - } - - ret = FIDO_OK; - -done: - reset_public_key(&pk_data); - if (dev != NULL) { - fido_dev_close(dev); - } - fido_dev_free(&dev); - fido_assert_free(&assert); - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -get_assert_data(struct passkey_data *data) -{ - TALLOC_CTX *tmp_ctx = NULL; - fido_dev_t *dev = NULL; - fido_assert_t *assert = NULL; - const char *auth_data = NULL; - const char *signature = NULL; - int index; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - return ENOMEM; - } - - ret = select_authenticator(data, &dev, &assert, &index); - if (ret != EOK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Comparing the device and policy options.\n"); - ret = get_device_options(dev, data); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert options.\n"); - ret = set_assert_options(FIDO_OPT_TRUE, data->user_verification, assert); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to reset assert options.\n"); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Getting assert.\n"); - ret = request_assert(data, dev, assert); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Getting authentication data and signature.\n"); - ret = get_assert_auth_data_signature(tmp_ctx, assert, &auth_data, - &signature); - if (ret != EOK) { - goto done; - } - - print_assert_data(data->key_handle_list[index], data->crypto_challenge, - auth_data, signature); - -done: - if (dev != NULL) { - fido_dev_close(dev); - } - fido_dev_free(&dev); - fido_assert_free(&assert); - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -verify_assert_data(struct passkey_data *data) -{ - fido_assert_t *assert = NULL; - struct pk_data_t pk_data = { 0 }; - errno_t ret; - - assert = fido_assert_new(); - if (assert == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_assert_new failed.\n"); - ret = ENOMEM; - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Preparing assert data.\n"); - ret = prepare_assert(data, 0, assert); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, - "Preparing assert authenticator data and signature.\n"); - ret = set_assert_auth_data_signature(data, assert); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Decoding public key.\n"); - ret = public_key_to_libfido2(data->public_key_list[0], &pk_data); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Verifying assert.\n"); - ret = verify_assert(&pk_data, assert); - if (ret != FIDO_OK) { - goto done; - } - - ret = FIDO_OK; - -done: - reset_public_key(&pk_data); - fido_assert_free(&assert); - - return ret; -} +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa + + Copyright (C) 2022 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 . +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "util/crypto/sss_crypto.h" +#include "util/debug.h" +#include "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "util/sss_prctl.h" + +#include "passkey_child.h" + +#if OPENSSL_VERSION_NUMBER >= 0x30000000 +#define get_id(x) EVP_PKEY_get_base_id((x)) +#else +#define get_id(x) EVP_PKEY_base_id((x)) +#endif /* OPENSSL_VERSION_NUMBER */ + +errno_t +cose_str_to_int(const char *type, int *out) +{ + if (strcasecmp(type, "es256") == 0) { + *out = COSE_ES256; + } else if (strcasecmp(type, "rs256") == 0) { + *out = COSE_RS256; + } else if (strcasecmp(type, "eddsa") == 0) { + *out = COSE_EDDSA; + } else { + *out = 0; + return ERR_INVALID_CRED_TYPE; + } + + return EOK; +} + +static errno_t +cred_type_str_to_enum(const char *type, enum credential_type *out) +{ + if (strcasecmp(type, "server-side") == 0) { + *out = CRED_SERVER_SIDE; + } else if (strcasecmp(type, "discoverable") == 0) { + *out = CRED_DISCOVERABLE; + } else { + *out = 0; + return ERR_INVALID_CRED_TYPE; + } + + return EOK; +} + +static errno_t +parse_public_keys_and_handlers(TALLOC_CTX *mem_ctx, + const char *public_keys, + const char *key_handles, + struct passkey_data *_data) +{ + TALLOC_CTX *tmp_ctx = NULL; + char **pk_list = NULL; + char **kh_list = NULL; + int pk_num = 0; + int kh_num = 0; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ERROR("talloc_new() failed\n"); + return ENOMEM; + } + + ret = split_on_separator(tmp_ctx, public_keys, ',', true, true, &pk_list, &pk_num); + if (ret != EOK && _data->action == ACTION_AUTHENTICATE) { + ERROR("Incorrectly formatted public keys.\n"); + goto done; + } + + ret = split_on_separator(tmp_ctx, key_handles, ',', true, true, &kh_list, &kh_num); + if (ret != EOK) { + ERROR("Incorrectly formatted public keys.\n"); + goto done; + } + + if (_data->action == ACTION_AUTHENTICATE && pk_num != kh_num) { + ERROR("The number of public keys and key handles don't match.\n"); + goto done; + } + + _data->public_key_list = talloc_steal(mem_ctx, pk_list); + _data->key_handle_list = talloc_steal(mem_ctx, kh_list); + _data->public_key_size = pk_num; + _data->key_handle_size = kh_num; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +parse_arguments(TALLOC_CTX *mem_ctx, int argc, const char *argv[], + struct passkey_data *data) +{ + int opt; + int dumpable = 1; + int backtrace = 1; + int debug_fd = -1; + char *user_verification = NULL; + char *public_keys = NULL; + char *key_handles = NULL; + const char *opt_logger = NULL; + const char *type = NULL; + const char *cred_type = NULL; + poptContext pc; + errno_t ret; + + /* Set defaults */ + data->action = ACTION_NONE; + data->shortname = NULL; + data->domain = NULL; + data->public_key_list = NULL; + data->key_handle_list = NULL; + data->public_key_size = 0; + data->key_handle_size = 0; + data->crypto_challenge = NULL; + data->auth_data = NULL; + data->signature = NULL; + data->type = COSE_ES256; + data->user_verification = FIDO_OPT_OMIT; + data->cred_type = CRED_SERVER_SIDE; + data->user_id = NULL; + data->mapping_file = NULL; + data->quiet = false; + data->debug_libfido2 = false; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_DEBUG_OPTS + {"dumpable", 0, POPT_ARG_INT, &dumpable, 0, + _("Allow core dumps"), NULL }, + {"backtrace", 0, POPT_ARG_INT, &backtrace, 0, + _("Enable debug backtrace"), NULL }, + {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0, + _("An open file descriptor for the debug logs"), NULL}, + SSSD_LOGGER_OPTS + {"register", 0, POPT_ARG_NONE, NULL, 'r', + _("Register a passkey for a user"), NULL }, + {"authenticate", 0, POPT_ARG_NONE, NULL, 'a', + _("Authenticate a user with a passkey"), NULL }, + {"get-assert", 0, POPT_ARG_NONE, NULL, 'g', + _("Obtain assertion data"), NULL }, + {"get-device-info", 0, POPT_ARG_NONE, NULL, 'i', + _("Obtain device information"), NULL }, + {"verify-assert", 0, POPT_ARG_NONE, NULL, 'v', + _("Verify assertion data"), NULL }, + {"username", 0, POPT_ARG_STRING, &data->shortname, 0, + _("Shortname"), NULL }, + {"domain", 0, POPT_ARG_STRING, &data->domain, 0, + _("Domain"), NULL}, + {"public-key", 0, POPT_ARG_STRING, &public_keys, 0, + _("Public key"), NULL }, + {"key-handle", 0, POPT_ARG_STRING, &key_handles, 0, + _("Key handle"), NULL}, + {"cryptographic-challenge", 0, POPT_ARG_STRING, + &data->crypto_challenge, 0, + _("Cryptographic challenge"), NULL}, + {"auth-data", 0, POPT_ARG_STRING, &data->auth_data, 0, + _("Authenticator data"), NULL}, + {"signature", 0, POPT_ARG_STRING, &data->signature, 0, + _("Signature"), NULL}, + {"type", 0, POPT_ARG_STRING, &type, 0, + _("COSE type to use"), "es256|rs256|eddsa"}, + {"user-verification", 0, POPT_ARG_STRING, &user_verification, 0, + _("Require user-verification"), "true|false"}, + {"cred-type", 0, POPT_ARG_STRING, &cred_type, 0, + _("Credential type"), "server-side|discoverable"}, + {"output-file", 0, POPT_ARG_STRING, &data->mapping_file, 0, + _("Write key mapping data to file"), NULL}, + {"quiet", 0, POPT_ARG_NONE, NULL, 'q', + _("Supress prompts"), NULL}, + {"debug-libfido2", 0, POPT_ARG_NONE, NULL, 'd', + _("Enable debug in libfido2 library"), NULL}, + SSSD_LOGGER_OPTS + POPT_TABLEEND + }; + + /* 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) { + case 'r': + if (data->action != ACTION_NONE + && data->action != ACTION_REGISTER) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_REGISTER; + break; + case 'a': + if (data->action != ACTION_NONE + && data->action != ACTION_AUTHENTICATE) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_AUTHENTICATE; + break; + case 'g': + if (data->action != ACTION_NONE + && data->action != ACTION_GET_ASSERT) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_GET_ASSERT; + break; + case 'i': + if (data->action != ACTION_NONE + && data->action != ACTION_GET_DEVINFO) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_GET_DEVINFO; + break; + case 'v': + if (data->action != ACTION_NONE + && data->action != ACTION_VERIFY_ASSERT) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_VERIFY_ASSERT; + break; + case 'q': + data->quiet = true; + break; + case 'd': + data->debug_libfido2 = true; + break; + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + } + + poptFreeContext(pc); + + sss_prctl_set_dumpable((dumpable == 0) ? 0 : 1); + + if (user_verification != NULL) { + if (strcmp(user_verification, "true") == 0) { + data->user_verification = FIDO_OPT_TRUE; + } else if (strcmp(user_verification, "false") == 0) { + data->user_verification = FIDO_OPT_FALSE; + } else if (user_verification != NULL) { + ERROR("[%s] is not a valid user-verification value.\n", + user_verification); + ret = EINVAL; + goto done; + } + } + + if (type != NULL) { + ret = cose_str_to_int(type, &data->type); + if (ret != EOK) { + ERROR("[%s] is not a valid COSE type (es256, rs256 or eddsa).\n", + type); + goto done; + } + } + + if (public_keys != NULL || key_handles != NULL) { + ret = parse_public_keys_and_handlers(mem_ctx, public_keys, key_handles, + data); + if (ret != EOK) { + goto done; + } + } + + if (cred_type != NULL) { + ret = cred_type_str_to_enum(cred_type, &data->cred_type); + if (ret != EOK) { + ERROR("[%s] is not a valid credential type (server-side or" + " discoverable).\n", + cred_type); + goto done; + } + } + + debug_prg_name = talloc_asprintf(NULL, "passkey_child[%d]", getpid()); + if (debug_prg_name == NULL) { + ERROR("talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + if (debug_fd != -1) { + opt_logger = sss_logger_str[FILES_LOGGER]; + ret = set_debug_file_from_fd(debug_fd); + if (ret != EOK) { + opt_logger = sss_logger_str[STDERR_LOGGER]; + ERROR("set_debug_file_from_fd failed.\n"); + } + } + + DEBUG_INIT(debug_level, opt_logger); + sss_set_debug_backtrace_enable((backtrace == 0) ? false : true); + + ret = EOK; + +done: + return ret; +} + +errno_t +check_arguments(const struct passkey_data *data) +{ + errno_t ret = EOK; + + DEBUG(SSSDBG_TRACE_FUNC, "Argument values after parsing\n"); + DEBUG(SSSDBG_TRACE_FUNC, "action: %d\n", data->action); + DEBUG(SSSDBG_TRACE_FUNC, "shortname: %s, domain: %s\n", + data->shortname, data->domain); + DEBUG(SSSDBG_TRACE_FUNC, "Number of key handles %d\n", + data->key_handle_size); + for (int i = 0; i < data->key_handle_size; i++) { + DEBUG(SSSDBG_TRACE_FUNC, "key %d, key_handle: %s\n", + i + 1, data->key_handle_list[i]); + } + DEBUG(SSSDBG_TRACE_FUNC, "Number of public keys %d\n", + data->public_key_size); + for (int i = 0; i < data->public_key_size; i++) { + DEBUG(SSSDBG_TRACE_FUNC, "key %d, public_key: %s\n", + i + 1, data->public_key_list[i]); + } + DEBUG(SSSDBG_TRACE_FUNC, "cryptographic-challenge: %s\n", + data->crypto_challenge); + DEBUG(SSSDBG_TRACE_FUNC, "auth-data: %s\n", + data->auth_data); + DEBUG(SSSDBG_TRACE_FUNC, "signature: %s\n", + data->signature); + DEBUG(SSSDBG_TRACE_FUNC, "type: %d\n", data->type); + DEBUG(SSSDBG_TRACE_FUNC, "user_verification: %d\n", + data->user_verification); + DEBUG(SSSDBG_TRACE_FUNC, "cred_type: %d\n", + data->cred_type); + DEBUG(SSSDBG_TRACE_FUNC, "Mapping file: %s\n", data->mapping_file); + DEBUG(SSSDBG_TRACE_FUNC, "debug_libfido2: %d\n", data->debug_libfido2); + + if (data->action == ACTION_NONE) { + DEBUG(SSSDBG_OP_FAILURE, "No action set.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + + if (data->action == ACTION_REGISTER + && (data->shortname == NULL || data->domain == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, "Too few arguments for register action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + + if (data->action == ACTION_AUTHENTICATE + && (data->domain == NULL || data->public_key_list == NULL + || data->key_handle_list == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, + "Too few arguments for authenticate action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + + if (data->action == ACTION_GET_DEVINFO + && (data->domain == NULL || data->key_handle_list == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, + "Too few arguments for get-device-info action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + + if (data->action == ACTION_GET_ASSERT + && (data->domain == NULL || data->key_handle_list == NULL + || data->crypto_challenge == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, + "Too few arguments for get-assert action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + + if (data->action == ACTION_VERIFY_ASSERT + && (data->domain == NULL || data->public_key_list == NULL + || data->key_handle_list == NULL || data->crypto_challenge == NULL + || data->auth_data == NULL || data->signature == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, + "Too few arguments for verify-assert action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + +done: + return ret; +} + +errno_t +register_key(struct passkey_data *data) +{ + TALLOC_CTX *tmp_ctx = NULL; + fido_cred_t *cred = NULL; + fido_dev_t *dev = NULL; + fido_dev_info_t *dev_list = NULL; + size_t dev_number = 0; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + data->user_id = talloc_array(tmp_ctx, unsigned char, USER_ID_SIZE); + if (data->user_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array() failed.\n"); + ret = ENOMEM; + goto done; + } + + cred = fido_cred_new(); + if (cred == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_new failed.\n"); + ret = ENOMEM; + goto done; + } + + dev_list = fido_dev_info_new(DEVLIST_SIZE); + if (dev_list == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = list_devices(dev_list, &dev_number); + if (ret != EOK) { + goto done; + } + + ret = select_device(data->action, dev_list, dev_number, NULL, &dev); + if (ret != EOK) { + goto done; + } + + ret = prepare_credentials(data, dev, cred); + if (ret != EOK) { + goto done; + } + + ret = generate_credentials(data, dev, cred); + if (ret != EOK) { + ERROR("A problem occurred while generating the credentials.\n"); + goto done; + } + + ret = verify_credentials(cred); + if (ret != EOK) { + goto done; + } + + ret = print_credentials(data, cred); + if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + fido_cred_free(&cred); + fido_dev_info_free(&dev_list, dev_number); + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +public_key_to_base64(TALLOC_CTX *mem_ctx, const struct passkey_data *data, + const unsigned char *public_key, size_t pk_len, + char **_pem_key) +{ + EVP_PKEY *evp_pkey = NULL; + unsigned char *pub = NULL; + char *pem_key = NULL; + unsigned long err; + errno_t ret; + + if (_pem_key == NULL) { + ret = EINVAL; + goto done; + } + + switch (data->type) { + case COSE_ES256: + ret = es256_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); + break; + case COSE_RS256: + ret = rs256_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); + break; + case COSE_EDDSA: + ret = eddsa_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Invalid key type.\n"); + ret = EINVAL; + break; + } + + if (ret != EOK) { + goto done; + } + + ret = i2d_PUBKEY(evp_pkey, &pub); + if (ret < 1) { + err = ERR_get_error(); + DEBUG(SSSDBG_OP_FAILURE, "i2d_PUBKEY failed [%lu][%s].\n", + err, ERR_error_string(err, NULL)); + ret = EIO; + goto done; + } + + pem_key = sss_base64_encode(mem_ctx, pub, ret); + if (pem_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_encode failed.\n"); + ret = ENOMEM; + goto done; + } + + *_pem_key = pem_key; + ret = EOK; + +done: + free(pub); + + if (evp_pkey != NULL) { + EVP_PKEY_free(evp_pkey); + } + + return ret; +} + +errno_t +select_authenticator(struct passkey_data *data, fido_dev_t **_dev, + fido_assert_t **_assert, int *_index) +{ + fido_dev_info_t *dev_list = NULL; + fido_dev_t *dev = NULL; + size_t dev_list_len = 0; + fido_assert_t *assert = NULL; + int index = 0; + errno_t ret; + + dev_list = fido_dev_info_new(DEVLIST_SIZE); + if (dev_list == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_new failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Checking for devices.\n"); + ret = list_devices(dev_list, &dev_list_len); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "%d key handles provided.\n", + data->key_handle_size); + + while (index < data->key_handle_size) { + DEBUG(SSSDBG_TRACE_FUNC, + "Preparing assert request data with key handle %d.\n", index + 1); + + assert = fido_assert_new(); + if (assert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_assert_new failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Preparing assert request data.\n"); + ret = prepare_assert(data, index, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Selecting device.\n"); + ret = select_device(data->action, dev_list, dev_list_len, assert, &dev); + if (ret == EOK) { + /* Key handle found in device */ + /* update device current configuration + */ + bool has_pin = fido_dev_has_pin(dev); + bool has_uv = fido_dev_has_uv (dev); + int fd = -1; + if (has_pin && has_uv) { + fd = creat("/var/run/passkey-pinuv", 0000); + if (fd < 0) + DEBUG(SSSDBG_TRACE_FUNC, "rror creat pinuv errno = %d\n", errno); + close(fd); + } + else { + (void)remove ("/var/run/passkey-pinuv"); + if (has_pin) { + fd = creat("/var/run/passkey-pinonly", 0000); + if (fd < 0) + DEBUG(SSSDBG_TRACE_FUNC, "eror creat pinonly errno = %d\n", errno); + close (fd); + } else { + /* no pin and no uv; */ + (void)remove ("/var/run/passkey-pinonly"); + fido_dev_close(dev); + dev = NULL; + ret = FIDO_ERR_NOTFOUND; + } + } + break; + } + + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + fido_assert_free(&assert); + index++; + } + + *_dev = dev; + *_assert = assert; + *_index = index; + +done: + if (ret != EOK) { + fido_assert_free(&assert); + } + fido_dev_info_free(&dev_list, dev_list_len); + + return ret; +} + +errno_t +public_key_to_libfido2(const char *pem_public_key, struct pk_data_t *_pk_data) +{ + TALLOC_CTX *tmp_ctx = NULL; + const unsigned char *public_key = NULL; + size_t pk_len; + const EVP_PKEY *evp_pkey = NULL; + int base_id; + unsigned long err; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + public_key = sss_base64_decode(tmp_ctx, pem_public_key, &pk_len); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to decode public key.\n"); + ret = ENOMEM; + goto done; + } + + evp_pkey = d2i_PUBKEY(NULL, &public_key, pk_len); + if (evp_pkey == NULL) { + err = ERR_get_error(); + DEBUG(SSSDBG_OP_FAILURE, "d2i_pubkey failed [%lu][%s].\n", + err, ERR_error_string(err, NULL)); + ret = EIO; + goto done; + } + + base_id = get_id(evp_pkey); + if (base_id == EVP_PKEY_EC) { + _pk_data->type = COSE_ES256; + ret = evp_pkey_to_es256_pubkey(evp_pkey, _pk_data); + } else if (base_id == EVP_PKEY_RSA) { + _pk_data->type = COSE_RS256; + ret = evp_pkey_to_rs256_pubkey(evp_pkey, _pk_data); + } else if (base_id == EVP_PKEY_ED25519) { + _pk_data->type = COSE_EDDSA; + ret = evp_pkey_to_eddsa_pubkey(evp_pkey, _pk_data); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Unrecognized key type.\n"); + ret = EINVAL; + } + if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + if (evp_pkey != NULL) { + EVP_PKEY_free(discard_const(evp_pkey)); + } + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +authenticate(struct passkey_data *data) +{ + TALLOC_CTX *tmp_ctx = NULL; + fido_assert_t *assert = NULL; + fido_dev_t *dev = NULL; + struct pk_data_t pk_data = { 0 }; + int index; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ERROR("talloc_new() failed\n"); + return ENOMEM; + } + + ret = select_authenticator(data, &dev, &assert, &index); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Comparing the device and policy options.\n"); + ret = get_device_options(dev, data); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert options.\n"); + ret = set_assert_options(FIDO_OPT_TRUE, data->user_verification, assert); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to reset assert options.\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert client data.\n"); + ret = set_assert_client_data_hash(data, assert); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to reset client data hash.\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Decoding public key.\n"); + ret = public_key_to_libfido2(data->public_key_list[index], &pk_data); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Getting assert.\n"); + ret = request_assert(data, dev, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Verifying assert.\n"); + ret = verify_assert(&pk_data, assert); + if (ret != FIDO_OK) { + goto done; + } + + ret = FIDO_OK; + +done: + reset_public_key(&pk_data); + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + fido_assert_free(&assert); + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +get_assert_data(struct passkey_data *data) +{ + TALLOC_CTX *tmp_ctx = NULL; + fido_dev_t *dev = NULL; + fido_assert_t *assert = NULL; + const char *auth_data = NULL; + const char *signature = NULL; + int index; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + ret = select_authenticator(data, &dev, &assert, &index); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Comparing the device and policy options.\n"); + ret = get_device_options(dev, data); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert options.\n"); + ret = set_assert_options(FIDO_OPT_TRUE, data->user_verification, assert); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to reset assert options.\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Getting assert.\n"); + ret = request_assert(data, dev, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Getting authentication data and signature.\n"); + ret = get_assert_auth_data_signature(tmp_ctx, assert, &auth_data, + &signature); + if (ret != EOK) { + goto done; + } + + print_assert_data(data->key_handle_list[index], data->crypto_challenge, + auth_data, signature); + +done: + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + fido_assert_free(&assert); + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +get_device_info(struct passkey_data *data) +{ + TALLOC_CTX *tmp_ctx = NULL; + fido_dev_t *dev = NULL; + fido_assert_t *assert = NULL; + int index; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + ret = select_authenticator(data, &dev, &assert, &index); + if (ret != EOK) { + goto done; + } + /* unused */ + fido_assert_free(&assert); + + done: + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +verify_assert_data(struct passkey_data *data) +{ + fido_assert_t *assert = NULL; + struct pk_data_t pk_data = { 0 }; + errno_t ret; + + assert = fido_assert_new(); + if (assert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_assert_new failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Preparing assert data.\n"); + ret = prepare_assert(data, 0, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Preparing assert authenticator data and signature.\n"); + ret = set_assert_auth_data_signature(data, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Decoding public key.\n"); + ret = public_key_to_libfido2(data->public_key_list[0], &pk_data); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Verifying assert.\n"); + ret = verify_assert(&pk_data, assert); + if (ret != FIDO_OK) { + goto done; + } + + ret = FIDO_OK; + +done: + reset_public_key(&pk_data); + fido_assert_free(&assert); + + return ret; +} diff --git a/src/passkey_child/passkey_child_credentials.c b/src/passkey_child/passkey_child_credentials.c index e27afb411bb..3edce33b452 100644 --- a/src/passkey_child/passkey_child_credentials.c +++ b/src/passkey_child/passkey_child_credentials.c @@ -1,681 +1,690 @@ -/* - SSSD - - Helper child to commmunicate with passkey devices - - Authors: - Iker Pedrosa - - Copyright (C) 2022 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 . -*/ - -#include -#include -#include - -#include -#include -#include - -#include "util/crypto/sss_crypto.h" -#include "util/debug.h" -#include "util/util.h" - -#include "passkey_child.h" - -#define IN_BUF_SIZE 1024 - -errno_t -prepare_credentials(struct passkey_data *data, fido_dev_t *dev, - fido_cred_t *cred) -{ - unsigned char cdh[32]; - fido_opt_t rk = FIDO_OPT_OMIT; - bool has_pin; - bool has_uv; - errno_t ret = EOK; - - ret = fido_cred_set_type(cred, data->type); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_type failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - ret = sss_generate_csprng_buffer(cdh, sizeof(cdh)); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "sss_generate_csprng_buffer failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - ret = fido_cred_set_clientdata_hash(cred, cdh, sizeof(cdh)); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_cred_set_clientdata_hash failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Setting Relying Party ID and name to %s.\n", - data->domain); - - ret = fido_cred_set_rp(cred, data->domain, data->domain); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_rp failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - if (data->user_id == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "user_id must be allocated before using it.\n"); - ret = ENOMEM; - goto done; - } - - ret = sss_generate_csprng_buffer(data->user_id, USER_ID_SIZE); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "sss_generate_csprng_buffer failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Setting user: %s.\n", data->shortname); - - ret = fido_cred_set_user(cred, data->user_id, USER_ID_SIZE, - data->shortname, NULL, NULL); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_user failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - if (data->cred_type == CRED_DISCOVERABLE) { - rk = FIDO_OPT_TRUE; - } - - /* Set to FIDO_OPT_OMIT instead of FIDO_OPT_FALSE for compatibility reasons - */ - ret = fido_cred_set_rk(cred, rk); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_rk failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - has_uv = fido_dev_has_uv(dev); - has_pin = fido_dev_has_pin(dev); - if (data->user_verification == FIDO_OPT_TRUE && has_uv == false - && has_pin == false) { - DEBUG(SSSDBG_OP_FAILURE, - "Policy enabled user-verification but there isn't any " - "verification method set.\n"); - ret = EINVAL; - goto done; - } - - if (data->user_verification == FIDO_OPT_FALSE - && (has_uv == true || has_pin == true)) { - DEBUG(SSSDBG_CONF_SETTINGS, - "Policy disabled user-verification but the key settings are " - "enforcing it. Thus, enabling user-verification.\n"); - data->user_verification = FIDO_OPT_TRUE; - } - - if (has_uv == true) { - ret = fido_cred_set_uv(cred, FIDO_OPT_TRUE); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_uv failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - } - -done: - return ret; -} - -errno_t -passkey_recv_pin(TALLOC_CTX *mem_ctx, int fd, char **_pin) -{ - uint8_t buf[IN_BUF_SIZE]; - ssize_t len; - errno_t ret; - char *str; - - errno = 0; - len = sss_atomic_read_s(fd, buf, IN_BUF_SIZE); - if (len == -1) { - ret = errno; - ret = (ret == 0) ? EINVAL: ret; - DEBUG(SSSDBG_CRIT_FAILURE, - "read failed [%d][%s].\n", ret, strerror(ret)); - return ret; - } - - if (len == 0 || *buf == '\0') { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing PIN.\n"); - return EINVAL; - } - - str = talloc_strndup(mem_ctx, (char *) buf, len); - if (str == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n"); - return ENOMEM; - } - - if (strlen(str) != len) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Input contains additional data, only PIN expected.\n"); - talloc_free(str); - return EINVAL; - } - - *_pin = str; - - return EOK; -} - -ssize_t -read_pin(char **pin) -{ - char *line_ptr = NULL; - struct termios old, new; - size_t line_len = 0; - ssize_t bytes_read; - ssize_t ret; - - ret = tcgetattr(STDIN_FILENO, &old); - if (ret != 0) { - DEBUG(SSSDBG_OP_FAILURE, - "Unable to get the parameters associated with stdin [%d]: %s.\n", - errno, sss_strerror(errno)); - goto done; - } - new = old; - new.c_lflag &= ~ECHO; - ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &new); - if (ret != 0) { - DEBUG(SSSDBG_OP_FAILURE, "Unable to turn echoing off [%d]: %s.\n", - errno, sss_strerror(errno)); - goto done; - } - - PRINT("Enter PIN:\n"); - fflush(stdout); - bytes_read = getline(&line_ptr, &line_len, stdin); - if (bytes_read == -1) { - DEBUG(SSSDBG_OP_FAILURE, "getline failed [%d]: %s.\n", - errno, sss_strerror(errno)); - } else { - /* Remove the end of line '\n' character */ - line_ptr[--bytes_read] = '\0'; - } - PRINT("\n"); - - ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &old); - if (ret != 0) { - DEBUG(SSSDBG_OP_FAILURE, - "Unable to restore parameters associated with stdin [%d]: %s.\n", - errno, sss_strerror(errno)); - goto done; - } - - ret = bytes_read; - *pin = line_ptr; - -done: - return ret; -} - -errno_t -generate_credentials(struct passkey_data *data, fido_dev_t *dev, - fido_cred_t *cred) -{ - TALLOC_CTX *tmp_ctx = NULL; - char *pin = NULL; - char *tmp_pin = NULL; - bool has_pin; - ssize_t pin_len = 0; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - return ENOMEM; - } - - has_pin = fido_dev_has_pin(dev); - if (has_pin == true) { - if (data->quiet == true) { - ret = passkey_recv_pin(tmp_ctx, STDIN_FILENO, &pin); - if (ret != EOK) { - goto done; - } - } else { - pin_len = read_pin(&tmp_pin); - if (pin_len == -1) { - ret = ERR_INPUT_PARSE; - goto done; - } - pin = talloc_strdup(tmp_ctx, tmp_pin); - sss_erase_mem_securely(tmp_pin, pin_len); - free(tmp_pin); - } - } - - if (data->quiet == false) { - PRINT("Please touch the device.\n"); - fflush(stdout); - } - ret = fido_dev_make_cred(dev, cred, pin); - sss_erase_mem_securely(pin, pin_len); - - if (ret != FIDO_OK) { - if (ret == FIDO_ERR_PIN_INVALID) { - ERROR("Invalid PIN.\n"); - } - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_make_cred failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - if (has_pin == true) { - ret = fido_cred_set_uv(cred, FIDO_OPT_TRUE); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_uv failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - } - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -verify_credentials(const fido_cred_t *const cred) -{ - errno_t ret; - - if (fido_cred_x5c_ptr(cred) != NULL) { - ret = fido_cred_verify(cred); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_verify failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - } else { - DEBUG(SSSDBG_TRACE_FUNC, - "Attestation certificate missing. " - "Falling back to self attestation.\n"); - ret = fido_cred_verify_self(cred); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_cred_verify_self failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - } - -done: - return ret; -} - -errno_t -print_credentials(const struct passkey_data *data, - const fido_cred_t *const cred) -{ - TALLOC_CTX *tmp_ctx = NULL; - const unsigned char *cred_id = NULL; - const unsigned char *public_key = NULL; - const char *b64_cred_id = NULL; - char *pem_key = NULL; - size_t cred_id_len; - size_t user_key_len; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - cred_id = fido_cred_id_ptr(cred); - if (cred_id == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_id_ptr failed.\n"); - ret = ERR_CREDS_INVALID; - goto done; - } - - cred_id_len = fido_cred_id_len(cred); - if (cred_id_len == 0) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_id_len returned 0.\n"); - ret = ERR_CREDS_INVALID; - goto done; - } - - b64_cred_id = sss_base64_encode(tmp_ctx, cred_id, cred_id_len); - if (b64_cred_id == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "failed to encode key handle.\n"); - ret = ENOMEM; - goto done; - } - - public_key = fido_cred_pubkey_ptr(cred); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_pubkey_ptr failed.\n"); - ret = ERR_CREDS_INVALID; - goto done; - } - - user_key_len = fido_cred_pubkey_len(cred); - if (user_key_len == 0) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_pubkey_len returned 0.\n"); - ret = ERR_CREDS_INVALID; - goto done; - } - - ret = public_key_to_base64(tmp_ctx, data, public_key, user_key_len, - &pem_key); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "failed to format public key to b64 [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - PRINT("passkey:%s,%s\n", b64_cred_id, pem_key); - if (data->mapping_file != NULL) { - print_credentials_to_file(data, b64_cred_id, pem_key); - } - ret = EOK; - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -print_credentials_to_file(const struct passkey_data *data, - const char *b64_cred_id, - const char *pem_key) -{ - TALLOC_CTX *tmp_ctx = NULL; - char *mapping_data = NULL; - int mapping_data_len = 0; - int fd = -1; - ssize_t written = 0; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - mapping_data = talloc_asprintf(tmp_ctx, "passkey:%s,%s", - b64_cred_id, pem_key); - if (mapping_data == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); - ret = ENOMEM; - goto done; - } - mapping_data_len = strlen(mapping_data); - - fd = open(data->mapping_file, O_WRONLY|O_CREAT, 0640); - if (fd == -1) { - ret = errno; - DEBUG(SSSDBG_OP_FAILURE, - "open() failed [%d][%s]\n", ret, strerror(ret)); - ret = EIO; - goto done; - } - - errno = 0; - written = sss_atomic_write_s(fd, mapping_data, mapping_data_len); - if (written == -1) { - ret = errno; - DEBUG(SSSDBG_OP_FAILURE, - "Write failed [%d][%s].\n", ret, strerror(ret)); - goto done; - } - - if (written != mapping_data_len) { - DEBUG(SSSDBG_OP_FAILURE, - "Write error, wrote [%zd] bytes, expected [%d]\n", - written, mapping_data_len); - ret = EIO; - goto done; - } - - ret = EOK; - -done: - if (fd != -1) { - if (close(fd) == -1) { - DEBUG(SSSDBG_OP_FAILURE, - "Close failed [%s].\n", strerror(errno)); - } - } - talloc_free(tmp_ctx); - - return ret; -} - -int -es256_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *es256_key, - size_t es256_key_len, EVP_PKEY **_evp_pkey) -{ - EVP_PKEY *evp_pkey = NULL; - es256_pk_t *public_key = NULL; - errno_t ret; - - public_key = es256_pk_new(); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "es256_pk_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = es256_pk_from_ptr(public_key, es256_key, es256_key_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "es256_pk_from_ptr failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - evp_pkey = es256_pk_to_EVP_PKEY(public_key); - if (evp_pkey == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "es256_pk_to_EVP_PKEY failed.\n"); - ret = ENOMEM; - goto done; - } - - *_evp_pkey = evp_pkey; - ret = EOK; - -done: - es256_pk_free(&public_key); - - return ret; -} - -int -rs256_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *rs256_key, - size_t rs256_key_len, EVP_PKEY **_evp_pkey) -{ - EVP_PKEY *evp_pkey = NULL; - rs256_pk_t *public_key = NULL; - errno_t ret; - - public_key = rs256_pk_new(); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = rs256_pk_from_ptr(public_key, rs256_key, rs256_key_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_from_ptr failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - evp_pkey = rs256_pk_to_EVP_PKEY(public_key); - if (evp_pkey == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_to_EVP_PKEY failed.\n"); - ret = ENOMEM; - goto done; - } - - *_evp_pkey = evp_pkey; - ret = EOK; - -done: - rs256_pk_free(&public_key); - - return ret; -} - -int -eddsa_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *eddsa_key, - size_t eddsa_key_len, EVP_PKEY **_evp_pkey) -{ - EVP_PKEY *evp_pkey = NULL; - eddsa_pk_t *public_key = NULL; - errno_t ret; - - public_key = eddsa_pk_new(); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = eddsa_pk_from_ptr(public_key, eddsa_key, eddsa_key_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_from_ptr failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - evp_pkey = eddsa_pk_to_EVP_PKEY(public_key); - if (evp_pkey == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_to_EVP_PKEY failed.\n"); - ret = ENOMEM; - goto done; - } - - *_evp_pkey = evp_pkey; - ret = EOK; - -done: - eddsa_pk_free(&public_key); - - return ret; -} - -errno_t -evp_pkey_to_es256_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) -{ - void *public_key = NULL; - errno_t ret; - - public_key = es256_pk_new(); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "es256_pk_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = es256_pk_from_EVP_PKEY(public_key, evp_pkey); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "es256_pk_from_EVP_PKEY failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - _pk_data->public_key = public_key; - ret = EOK; - -done: - return ret; -} - -errno_t -evp_pkey_to_rs256_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) -{ - void *public_key = NULL; - errno_t ret; - - public_key = rs256_pk_new(); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = rs256_pk_from_EVP_PKEY(public_key, evp_pkey); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "rs256_pk_from_EVP_PKEY failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - _pk_data->public_key = public_key; - ret = EOK; - -done: - return ret; -} - -errno_t -evp_pkey_to_eddsa_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) -{ - void *public_key = NULL; - errno_t ret; - - public_key = eddsa_pk_new(); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = eddsa_pk_from_EVP_PKEY(public_key, evp_pkey); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "eddsa_pk_from_EVP_PKEY failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - _pk_data->public_key = public_key; - ret = EOK; - -done: - return ret; -} +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa + + Copyright (C) 2022 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 . +*/ + +#include +#include +#include + +#include +#include +#include + +#include "util/crypto/sss_crypto.h" +#include "util/debug.h" +#include "util/util.h" + +#include "passkey_child.h" + +#define IN_BUF_SIZE 1024 + +errno_t +prepare_credentials(struct passkey_data *data, fido_dev_t *dev, + fido_cred_t *cred) +{ + unsigned char cdh[32]; + fido_opt_t rk = FIDO_OPT_OMIT; + bool has_pin; + bool has_uv; + errno_t ret = EOK; + + ret = fido_cred_set_type(cred, data->type); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_type failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + ret = sss_generate_csprng_buffer(cdh, sizeof(cdh)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_generate_csprng_buffer failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + ret = fido_cred_set_clientdata_hash(cred, cdh, sizeof(cdh)); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_cred_set_clientdata_hash failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Setting Relying Party ID and name to %s.\n", + data->domain); + + ret = fido_cred_set_rp(cred, data->domain, data->domain); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_rp failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + if (data->user_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "user_id must be allocated before using it.\n"); + ret = ENOMEM; + goto done; + } + + ret = sss_generate_csprng_buffer(data->user_id, USER_ID_SIZE); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_generate_csprng_buffer failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Setting user: %s.\n", data->shortname); + + ret = fido_cred_set_user(cred, data->user_id, USER_ID_SIZE, + data->shortname, NULL, NULL); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_user failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + if (data->cred_type == CRED_DISCOVERABLE) { + rk = FIDO_OPT_TRUE; + } + + /* Set to FIDO_OPT_OMIT instead of FIDO_OPT_FALSE for compatibility reasons + */ + ret = fido_cred_set_rk(cred, rk); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_rk failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + has_uv = fido_dev_has_uv(dev); + has_pin = fido_dev_has_pin(dev); + if (data->user_verification == FIDO_OPT_TRUE && has_uv == false + && has_pin == false) { + DEBUG(SSSDBG_OP_FAILURE, + "Policy enabled user-verification but there isn't any " + "verification method set.\n"); + ret = EINVAL; + goto done; + } + + if (data->user_verification == FIDO_OPT_FALSE + && (has_uv == true || has_pin == true)) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Policy disabled user-verification but the key settings are " + "enforcing it. Thus, enabling user-verification.\n"); + data->user_verification = FIDO_OPT_TRUE; + } + + if (has_uv == true) { + ret = fido_cred_set_uv(cred, FIDO_OPT_TRUE); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_uv failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } + +done: + return ret; +} + +errno_t +passkey_recv_pin(TALLOC_CTX *mem_ctx, int fd, char **_pin) +{ + uint8_t buf[IN_BUF_SIZE]; + ssize_t len; + errno_t ret; + char *str; + + errno = 0; + len = sss_atomic_read_s(fd, buf, IN_BUF_SIZE); + if (len == -1) { + ret = errno; + ret = (ret == 0) ? EINVAL: ret; + DEBUG(SSSDBG_CRIT_FAILURE, + "read failed [%d][%s].\n", ret, strerror(ret)); + return ret; + } + + if (len == 0 || *buf == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing PIN.\n"); + return EINVAL; + } + + str = talloc_strndup(mem_ctx, (char *) buf, len); + if (str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n"); + return ENOMEM; + } + + if (strlen(str) != len) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Input contains additional data, only PIN expected.\n"); + talloc_free(str); + return EINVAL; + } + + /* see FIX no PIN in sss_cli.c */ + if ((strncasecmp(str, "null", len) == 0) || + (strncasecmp(str, "NULL", len) == 0)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "%s PIN is received - return EINVAL\n", str); + talloc_free(str); + return EINVAL; + } + + *_pin = str; + + return EOK; +} + +ssize_t +read_pin(char **pin) +{ + char *line_ptr = NULL; + struct termios old, new; + size_t line_len = 0; + ssize_t bytes_read; + ssize_t ret; + + ret = tcgetattr(STDIN_FILENO, &old); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to get the parameters associated with stdin [%d]: %s.\n", + errno, sss_strerror(errno)); + goto done; + } + new = old; + new.c_lflag &= ~ECHO; + ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &new); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to turn echoing off [%d]: %s.\n", + errno, sss_strerror(errno)); + goto done; + } + + PRINT("Enter PIN:\n"); + fflush(stdout); + bytes_read = getline(&line_ptr, &line_len, stdin); + if (bytes_read == -1) { + DEBUG(SSSDBG_OP_FAILURE, "getline failed [%d]: %s.\n", + errno, sss_strerror(errno)); + } else { + /* Remove the end of line '\n' character */ + line_ptr[--bytes_read] = '\0'; + } + PRINT("\n"); + + ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &old); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to restore parameters associated with stdin [%d]: %s.\n", + errno, sss_strerror(errno)); + goto done; + } + + ret = bytes_read; + *pin = line_ptr; + +done: + return ret; +} + +errno_t +generate_credentials(struct passkey_data *data, fido_dev_t *dev, + fido_cred_t *cred) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *pin = NULL; + char *tmp_pin = NULL; + bool has_pin; + ssize_t pin_len = 0; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + has_pin = fido_dev_has_pin(dev); + if (has_pin == true) { + if (data->quiet == true) { + ret = passkey_recv_pin(tmp_ctx, STDIN_FILENO, &pin); + if (ret != EOK) { + goto done; + } + } else { + pin_len = read_pin(&tmp_pin); + if (pin_len == -1) { + ret = ERR_INPUT_PARSE; + goto done; + } + pin = talloc_strdup(tmp_ctx, tmp_pin); + sss_erase_mem_securely(tmp_pin, pin_len); + free(tmp_pin); + } + } + + if (data->quiet == false) { + PRINT("Please touch the device.\n"); + fflush(stdout); + } + ret = fido_dev_make_cred(dev, cred, pin); + sss_erase_mem_securely(pin, pin_len); + + if (ret != FIDO_OK) { + if (ret == FIDO_ERR_PIN_INVALID) { + ERROR("Invalid PIN.\n"); + } + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_make_cred failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + if (has_pin == true) { + ret = fido_cred_set_uv(cred, FIDO_OPT_TRUE); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_uv failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +verify_credentials(const fido_cred_t *const cred) +{ + errno_t ret; + + if (fido_cred_x5c_ptr(cred) != NULL) { + ret = fido_cred_verify(cred); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_verify failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Attestation certificate missing. " + "Falling back to self attestation.\n"); + ret = fido_cred_verify_self(cred); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_cred_verify_self failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } + +done: + return ret; +} + +errno_t +print_credentials(const struct passkey_data *data, + const fido_cred_t *const cred) +{ + TALLOC_CTX *tmp_ctx = NULL; + const unsigned char *cred_id = NULL; + const unsigned char *public_key = NULL; + const char *b64_cred_id = NULL; + char *pem_key = NULL; + size_t cred_id_len; + size_t user_key_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + cred_id = fido_cred_id_ptr(cred); + if (cred_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_id_ptr failed.\n"); + ret = ERR_CREDS_INVALID; + goto done; + } + + cred_id_len = fido_cred_id_len(cred); + if (cred_id_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_id_len returned 0.\n"); + ret = ERR_CREDS_INVALID; + goto done; + } + + b64_cred_id = sss_base64_encode(tmp_ctx, cred_id, cred_id_len); + if (b64_cred_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to encode key handle.\n"); + ret = ENOMEM; + goto done; + } + + public_key = fido_cred_pubkey_ptr(cred); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_pubkey_ptr failed.\n"); + ret = ERR_CREDS_INVALID; + goto done; + } + + user_key_len = fido_cred_pubkey_len(cred); + if (user_key_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_pubkey_len returned 0.\n"); + ret = ERR_CREDS_INVALID; + goto done; + } + + ret = public_key_to_base64(tmp_ctx, data, public_key, user_key_len, + &pem_key); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "failed to format public key to b64 [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + PRINT("passkey:%s,%s\n", b64_cred_id, pem_key); + if (data->mapping_file != NULL) { + print_credentials_to_file(data, b64_cred_id, pem_key); + } + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +print_credentials_to_file(const struct passkey_data *data, + const char *b64_cred_id, + const char *pem_key) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *mapping_data = NULL; + int mapping_data_len = 0; + int fd = -1; + ssize_t written = 0; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + mapping_data = talloc_asprintf(tmp_ctx, "passkey:%s,%s", + b64_cred_id, pem_key); + if (mapping_data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + mapping_data_len = strlen(mapping_data); + + fd = open(data->mapping_file, O_WRONLY|O_CREAT, 0640); + if (fd == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "open() failed [%d][%s]\n", ret, strerror(ret)); + ret = EIO; + goto done; + } + + errno = 0; + written = sss_atomic_write_s(fd, mapping_data, mapping_data_len); + if (written == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "Write failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + if (written != mapping_data_len) { + DEBUG(SSSDBG_OP_FAILURE, + "Write error, wrote [%zd] bytes, expected [%d]\n", + written, mapping_data_len); + ret = EIO; + goto done; + } + + ret = EOK; + +done: + if (fd != -1) { + if (close(fd) == -1) { + DEBUG(SSSDBG_OP_FAILURE, + "Close failed [%s].\n", strerror(errno)); + } + } + talloc_free(tmp_ctx); + + return ret; +} + +int +es256_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *es256_key, + size_t es256_key_len, EVP_PKEY **_evp_pkey) +{ + EVP_PKEY *evp_pkey = NULL; + es256_pk_t *public_key = NULL; + errno_t ret; + + public_key = es256_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "es256_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = es256_pk_from_ptr(public_key, es256_key, es256_key_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "es256_pk_from_ptr failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + evp_pkey = es256_pk_to_EVP_PKEY(public_key); + if (evp_pkey == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "es256_pk_to_EVP_PKEY failed.\n"); + ret = ENOMEM; + goto done; + } + + *_evp_pkey = evp_pkey; + ret = EOK; + +done: + es256_pk_free(&public_key); + + return ret; +} + +int +rs256_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *rs256_key, + size_t rs256_key_len, EVP_PKEY **_evp_pkey) +{ + EVP_PKEY *evp_pkey = NULL; + rs256_pk_t *public_key = NULL; + errno_t ret; + + public_key = rs256_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = rs256_pk_from_ptr(public_key, rs256_key, rs256_key_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_from_ptr failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + evp_pkey = rs256_pk_to_EVP_PKEY(public_key); + if (evp_pkey == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_to_EVP_PKEY failed.\n"); + ret = ENOMEM; + goto done; + } + + *_evp_pkey = evp_pkey; + ret = EOK; + +done: + rs256_pk_free(&public_key); + + return ret; +} + +int +eddsa_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *eddsa_key, + size_t eddsa_key_len, EVP_PKEY **_evp_pkey) +{ + EVP_PKEY *evp_pkey = NULL; + eddsa_pk_t *public_key = NULL; + errno_t ret; + + public_key = eddsa_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = eddsa_pk_from_ptr(public_key, eddsa_key, eddsa_key_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_from_ptr failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + evp_pkey = eddsa_pk_to_EVP_PKEY(public_key); + if (evp_pkey == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_to_EVP_PKEY failed.\n"); + ret = ENOMEM; + goto done; + } + + *_evp_pkey = evp_pkey; + ret = EOK; + +done: + eddsa_pk_free(&public_key); + + return ret; +} + +errno_t +evp_pkey_to_es256_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) +{ + void *public_key = NULL; + errno_t ret; + + public_key = es256_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "es256_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = es256_pk_from_EVP_PKEY(public_key, evp_pkey); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "es256_pk_from_EVP_PKEY failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + _pk_data->public_key = public_key; + ret = EOK; + +done: + return ret; +} + +errno_t +evp_pkey_to_rs256_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) +{ + void *public_key = NULL; + errno_t ret; + + public_key = rs256_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = rs256_pk_from_EVP_PKEY(public_key, evp_pkey); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "rs256_pk_from_EVP_PKEY failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + _pk_data->public_key = public_key; + ret = EOK; + +done: + return ret; +} + +errno_t +evp_pkey_to_eddsa_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) +{ + void *public_key = NULL; + errno_t ret; + + public_key = eddsa_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = eddsa_pk_from_EVP_PKEY(public_key, evp_pkey); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "eddsa_pk_from_EVP_PKEY failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + _pk_data->public_key = public_key; + ret = EOK; + +done: + return ret; +} diff --git a/src/passkey_child/passkey_child_devices.c b/src/passkey_child/passkey_child_devices.c index 2011b12f661..74e0559e49a 100644 --- a/src/passkey_child/passkey_child_devices.c +++ b/src/passkey_child/passkey_child_devices.c @@ -1,246 +1,246 @@ -/* - SSSD - - Helper child to commmunicate with passkey devices - - Authors: - Iker Pedrosa - - Copyright (C) 2022 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 . -*/ - -#include "util/debug.h" -#include "util/util.h" - -#include "passkey_child.h" - -errno_t -list_devices(fido_dev_info_t *dev_list, size_t *dev_number) -{ - errno_t ret; - - for (int i = 0; i < TIMEOUT; i += FREQUENCY) { - ret = fido_dev_info_manifest(dev_list, DEVLIST_SIZE, dev_number); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "Unable to discover device(s) [%d]: %s.\n", - ret, fido_strerr(ret)); - } - - if ((*dev_number) != 0) { - DEBUG(SSSDBG_TRACE_FUNC, "Device found.\n"); - break; - } - - if (i < (TIMEOUT - 1)) { - DEBUG(SSSDBG_TRACE_FUNC, "No device available, retrying.\n"); - sleep(FREQUENCY); - } - } - - return ret; -} - -errno_t -select_device(enum action_opt action, fido_dev_info_t *dev_list, - size_t dev_list_len, fido_assert_t *assert, - fido_dev_t **_dev) -{ - fido_dev_t *dev = NULL; - const char *path; - const fido_dev_info_t *di = NULL; - errno_t ret; - - if (dev_list_len == 0) { - DEBUG(SSSDBG_OP_FAILURE, "No device found. Aborting.\n"); - ret = ENOENT; - goto done; - } else if (action == ACTION_REGISTER && dev_list_len == 1) { - dev = fido_dev_new(); - if (dev == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_new failed.\n"); - ret = ENOMEM; - goto done; - } - - di = fido_dev_info_ptr(dev_list, 0); - if (di == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_ptr failed.\n"); - ret = ENOMEM; - goto done; - } - - path = fido_dev_info_path(di); - if (path == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_path failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = fido_dev_open(dev, path); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_open failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - *_dev = dev; - } else if (action == ACTION_REGISTER && dev_list_len > 1) { - DEBUG(SSSDBG_OP_FAILURE, - "Only one device is supported at a time. Aborting.\n"); - fprintf(stderr, "Only one device is supported at a time. Aborting.\n"); - ret = EPERM; - goto done; - } else { - if (assert == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "assert cannot be NULL.\n"); - ret = EINVAL; - goto done; - } - - ret = select_from_multiple_devices(dev_list, dev_list_len, assert, _dev); - if (ret != FIDO_OK) { - goto done; - } - } - - ret = EOK; - -done: - if (ret != EOK) { - if (dev != NULL) { - fido_dev_close(dev); - } - fido_dev_free(&dev); - } - - return ret; -} - -errno_t -select_from_multiple_devices(fido_dev_info_t *dev_list, - size_t dev_list_len, - fido_assert_t *assert, - fido_dev_t **_dev) -{ - fido_dev_t *dev = NULL; - const fido_dev_info_t *di = NULL; - const char *path; - bool is_fido2; - errno_t ret; - - DEBUG(SSSDBG_TRACE_FUNC, - "Working with %ld authenticator(s).\n", dev_list_len); - - for (size_t i = 0; i < dev_list_len; i++) { - dev = fido_dev_new(); - if (dev == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_new failed.\n"); - ret = ENOMEM; - goto done; - } - - di = fido_dev_info_ptr(dev_list, i); - if (di == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_ptr failed.\n"); - ret = ENOMEM; - goto done; - } - - path = fido_dev_info_path(di); - if (path == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_path failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = fido_dev_open(dev, path); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_open failed [%d]: %s.\n", - ret, fido_strerr(ret)); - } - - is_fido2 = fido_dev_is_fido2(dev); - ret = fido_dev_get_assert(dev, assert, NULL); - if ((is_fido2 == false && ret == FIDO_ERR_USER_PRESENCE_REQUIRED) - || (is_fido2 == true && ret == FIDO_OK)) { - *_dev = dev; - DEBUG(SSSDBG_FUNC_DATA, "Assertion found in passkey %ld.\n", i); - ret = EOK; - goto done; - } - - DEBUG(SSSDBG_FUNC_DATA, "Assertion not found in passkey %ld.\n", i); - - fido_dev_close(dev); - fido_dev_free(&dev); - } - - ret = FIDO_ERR_NOTFOUND; - DEBUG(SSSDBG_OP_FAILURE, "Assertion not found.\n"); - -done: - return ret; -} - -errno_t -get_device_options(fido_dev_t *dev, struct passkey_data *_data) -{ - bool has_pin; - bool has_uv; - bool supports_uv; - errno_t ret; - - has_uv = fido_dev_has_uv(dev); - has_pin = fido_dev_has_pin(dev); - supports_uv = fido_dev_supports_uv(dev); - - if (_data->user_verification == FIDO_OPT_TRUE && has_pin != true - && has_uv != true) { - DEBUG(SSSDBG_OP_FAILURE, - "Policy enabled user-verification but there isn't any " - "verification method set.\n"); - ret = EINVAL; - goto done; - } - - if (_data->user_verification == FIDO_OPT_OMIT - && (has_uv == true || has_pin == true)) { - DEBUG(SSSDBG_CONF_SETTINGS, - "Policy didn't indicate any preference for user-verification " - "but the key settings are enforcing it. Thus, enforcing the " - "user-verification.\n"); - _data->user_verification = FIDO_OPT_TRUE; - ret = EOK; - goto done; - } - - if (_data->user_verification == FIDO_OPT_FALSE - && supports_uv == false) { - DEBUG(SSSDBG_CONF_SETTINGS, - "Policy disabled user-verification but the key doesn't support " - "it. Thus, omitting the user-verification.\n"); - _data->user_verification = FIDO_OPT_OMIT; - ret = EOK; - goto done; - } - - ret = EOK; - -done: - - return ret; -} +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa + + Copyright (C) 2022 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 . +*/ + +#include "util/debug.h" +#include "util/util.h" + +#include "passkey_child.h" + +errno_t +list_devices(fido_dev_info_t *dev_list, size_t *dev_number) +{ + errno_t ret; + + for (int i = 0; i < TIMEOUT; i += FREQUENCY) { + ret = fido_dev_info_manifest(dev_list, DEVLIST_SIZE, dev_number); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to discover device(s) [%d]: %s.\n", + ret, fido_strerr(ret)); + } + + if ((*dev_number) != 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Device found.\n"); + break; + } + + if (i < (TIMEOUT - 1)) { + DEBUG(SSSDBG_TRACE_FUNC, "No device available, retrying.\n"); + sleep(FREQUENCY); + } + } + + return ret; +} + +errno_t +select_device(enum action_opt action, fido_dev_info_t *dev_list, + size_t dev_list_len, fido_assert_t *assert, + fido_dev_t **_dev) +{ + fido_dev_t *dev = NULL; + const char *path; + const fido_dev_info_t *di = NULL; + errno_t ret; + + if (dev_list_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "No device found. Aborting.\n"); + ret = ENOENT; + goto done; + } else if (action == ACTION_REGISTER && dev_list_len == 1) { + dev = fido_dev_new(); + if (dev == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_new failed.\n"); + ret = ENOMEM; + goto done; + } + + di = fido_dev_info_ptr(dev_list, 0); + if (di == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_ptr failed.\n"); + ret = ENOMEM; + goto done; + } + + path = fido_dev_info_path(di); + if (path == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_path failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_dev_open(dev, path); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_open failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + *_dev = dev; + } else if (action == ACTION_REGISTER && dev_list_len > 1) { + DEBUG(SSSDBG_OP_FAILURE, + "Only one device is supported at a time. Aborting.\n"); + fprintf(stderr, "Only one device is supported at a time. Aborting.\n"); + ret = EPERM; + goto done; + } else { + if (assert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "assert cannot be NULL.\n"); + ret = EINVAL; + goto done; + } + + ret = select_from_multiple_devices(dev_list, dev_list_len, assert, _dev); + if (ret != FIDO_OK) { + goto done; + } + } + + ret = EOK; + +done: + if (ret != EOK) { + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + } + + return ret; +} + +errno_t +select_from_multiple_devices(fido_dev_info_t *dev_list, + size_t dev_list_len, + fido_assert_t *assert, + fido_dev_t **_dev) +{ + fido_dev_t *dev = NULL; + const fido_dev_info_t *di = NULL; + const char *path; + bool is_fido2; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, + "Working with %ld authenticator(s).\n", dev_list_len); + + for (size_t i = 0; i < dev_list_len; i++) { + dev = fido_dev_new(); + if (dev == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_new failed.\n"); + ret = ENOMEM; + goto done; + } + + di = fido_dev_info_ptr(dev_list, i); + if (di == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_ptr failed.\n"); + ret = ENOMEM; + goto done; + } + + path = fido_dev_info_path(di); + if (path == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_path failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_dev_open(dev, path); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_open failed [%d]: %s.\n", + ret, fido_strerr(ret)); + } + + is_fido2 = fido_dev_is_fido2(dev); + ret = fido_dev_get_assert(dev, assert, NULL); + if ((is_fido2 == false && ret == FIDO_ERR_USER_PRESENCE_REQUIRED) + || (is_fido2 == true && ret == FIDO_OK)) { + *_dev = dev; + DEBUG(SSSDBG_FUNC_DATA, "Assertion found in passkey %ld.\n", i); + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Assertion not found in passkey %ld.\n", i); + + fido_dev_close(dev); + fido_dev_free(&dev); + } + + ret = FIDO_ERR_NOTFOUND; + DEBUG(SSSDBG_OP_FAILURE, "Assertion not found.\n"); + +done: + return ret; +} + +errno_t +get_device_options(fido_dev_t *dev, struct passkey_data *_data) +{ + bool has_pin; + bool has_uv; + bool supports_uv; + errno_t ret; + + has_uv = fido_dev_has_uv(dev); + has_pin = fido_dev_has_pin(dev); + supports_uv = fido_dev_supports_uv(dev); + + if (_data->user_verification == FIDO_OPT_TRUE && has_pin != true + && has_uv != true) { + DEBUG(SSSDBG_OP_FAILURE, + "Policy enabled user-verification but there isn't any " + "verification method set.\n"); + ret = EINVAL; + goto done; + } + + if (_data->user_verification == FIDO_OPT_OMIT + && (has_uv == true || has_pin == true)) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Policy didn't indicate any preference for user-verification " + "but the key settings are enforcing it. Thus, enforcing the " + "user-verification.\n"); + _data->user_verification = FIDO_OPT_TRUE; + ret = EOK; + goto done; + } + + if (_data->user_verification == FIDO_OPT_FALSE + && supports_uv == false) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Policy disabled user-verification but the key doesn't support " + "it. Thus, omitting the user-verification.\n"); + _data->user_verification = FIDO_OPT_OMIT; + ret = EOK; + goto done; + } + + ret = EOK; + +done: + + return ret; +} diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c index 42834eeae0d..26699dbd3f8 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -1,3106 +1,3225 @@ -/* - SSSD - - PAM Responder - - Copyright (C) Simo Sorce 2009 - Copyright (C) Sumit Bose 2009 - - 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 . -*/ - -#define _GNU_SOURCE - -#include -#include -#include "util/util.h" -#include "util/auth_utils.h" -#include "util/find_uid.h" -#include "util/sss_ptr_hash.h" -#include "db/sysdb.h" -#include "confdb/confdb.h" -#include "responder/common/responder_packet.h" -#include "responder/common/responder.h" -#include "responder/common/negcache.h" -#include "providers/data_provider.h" -#include "responder/pam/pamsrv.h" -#include "responder/pam/pamsrv_passkey.h" -#include "responder/pam/pam_helpers.h" -#include "responder/common/cache_req/cache_req.h" - -enum pam_verbosity { - PAM_VERBOSITY_NO_MESSAGES = 0, - PAM_VERBOSITY_IMPORTANT, - PAM_VERBOSITY_INFO, - PAM_VERBOSITY_DEBUG -}; - -#define DEFAULT_PAM_VERBOSITY PAM_VERBOSITY_IMPORTANT - -struct pam_initgroup_enum_str { - enum pam_initgroups_scheme scheme; - const char *option; -}; - -struct pam_initgroup_enum_str pam_initgroup_enum_str[] = { - { PAM_INITGR_NEVER, "never" }, - { PAM_INITGR_NO_SESSION, "no_session" }, - { PAM_INITGR_ALWAYS, "always" }, - { PAM_INITGR_INVALID, NULL } -}; - -enum pam_initgroups_scheme pam_initgroups_string_to_enum(const char *str) -{ - size_t c; - - for (c = 0 ; pam_initgroup_enum_str[c].option != NULL; c++) { - if (strcasecmp(pam_initgroup_enum_str[c].option, str) == 0) { - return pam_initgroup_enum_str[c].scheme; - } - } - - return PAM_INITGR_INVALID; -} - -const char *pam_initgroup_enum_to_string(enum pam_initgroups_scheme scheme) -{ - size_t c; - - for (c = 0 ; pam_initgroup_enum_str[c].option != NULL; c++) { - if (pam_initgroup_enum_str[c].scheme == scheme) { - return pam_initgroup_enum_str[c].option; - } - } - - return "(NULL)"; -} - -static errno_t -pam_null_last_online_auth_with_curr_token(struct sss_domain_info *domain, - const char *username); -static errno_t -pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain, - const char *name, - uint64_t *_value); - -void pam_reply(struct pam_auth_req *preq); - -static errno_t check_cert(TALLOC_CTX *mctx, - struct tevent_context *ev, - struct pam_ctx *pctx, - struct pam_auth_req *preq, - struct pam_data *pd); - -int pam_check_user_done(struct pam_auth_req *preq, int ret); - -static errno_t pack_user_info_msg(TALLOC_CTX *mem_ctx, - const char *user_error_message, - size_t *resp_len, - uint8_t **_resp) -{ - uint32_t resp_type = SSS_PAM_USER_INFO_ACCOUNT_EXPIRED; - size_t err_len; - uint8_t *resp; - size_t p; - - err_len = strlen(user_error_message); - *resp_len = 2 * sizeof(uint32_t) + err_len; - resp = talloc_size(mem_ctx, *resp_len); - if (resp == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); - return ENOMEM; - } - - p = 0; - SAFEALIGN_SET_UINT32(&resp[p], resp_type, &p); - SAFEALIGN_SET_UINT32(&resp[p], err_len, &p); - safealign_memcpy(&resp[p], user_error_message, err_len, &p); - if (p != *resp_len) { - DEBUG(SSSDBG_FATAL_FAILURE, "Size mismatch\n"); - } - - *_resp = resp; - return EOK; -} - -static void inform_user(struct pam_data* pd, const char *pam_message) -{ - size_t msg_len; - uint8_t *msg; - errno_t ret; - - ret = pack_user_info_msg(pd, pam_message, &msg_len, &msg); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "pack_user_info_msg failed.\n"); - } else { - ret = pam_add_response(pd, SSS_PAM_USER_INFO, msg_len, msg); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); - } - } -} - -static bool is_domain_requested(struct pam_data *pd, const char *domain_name) -{ - int i; - - /* If none specific domains got requested via pam, all domains are allowed. - * Which mimics the default/original behaviour. - */ - if (!pd->requested_domains) { - return true; - } - - for (i = 0; pd->requested_domains[i]; i++) { - if (strcasecmp(domain_name, pd->requested_domains[i])) { - continue; - } - - return true; - } - - return false; -} - -static int extract_authtok_v2(struct sss_auth_token *tok, - size_t data_size, uint8_t *body, size_t blen, - size_t *c) -{ - uint32_t auth_token_type; - uint32_t auth_token_length; - uint8_t *auth_token_data; - int ret = EOK; - - if (data_size < sizeof(uint32_t) || *c+data_size > blen || - SIZE_T_OVERFLOW(*c, data_size)) return EINVAL; - - SAFEALIGN_COPY_UINT32_CHECK(&auth_token_type, &body[*c], blen, c); - auth_token_length = data_size - sizeof(uint32_t); - auth_token_data = body+(*c); - - switch (auth_token_type) { - case SSS_AUTHTOK_TYPE_EMPTY: - sss_authtok_set_empty(tok); - break; - case SSS_AUTHTOK_TYPE_PASSWORD: - if (auth_token_length == 0) { - sss_authtok_set_empty(tok); - } else { - ret = sss_authtok_set_password(tok, (const char *)auth_token_data, - auth_token_length); - } - break; - case SSS_AUTHTOK_TYPE_2FA: - case SSS_AUTHTOK_TYPE_2FA_SINGLE: - case SSS_AUTHTOK_TYPE_SC_PIN: - case SSS_AUTHTOK_TYPE_SC_KEYPAD: - case SSS_AUTHTOK_TYPE_OAUTH2: - case SSS_AUTHTOK_TYPE_PASSKEY: - case SSS_AUTHTOK_TYPE_PASSKEY_KRB: - case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: - case SSS_AUTHTOK_TYPE_PAM_STACKED: - ret = sss_authtok_set(tok, auth_token_type, - auth_token_data, auth_token_length); - break; - default: - return EINVAL; - } - - *c += auth_token_length; - - return ret; -} - -static int extract_string(char **var, size_t size, uint8_t *body, size_t blen, - size_t *c) { - uint8_t *str; - - if (*c+size > blen || SIZE_T_OVERFLOW(*c, size)) return EINVAL; - - str = body+(*c); - - if (str[size-1]!='\0') return EINVAL; - - /* If the string isn't valid UTF-8, fail */ - if (!sss_utf8_check(str, size-1)) { - return EINVAL; - } - - *c += size; - - *var = (char *) str; - - return EOK; -} - -static int extract_uint32_t(uint32_t *var, size_t size, uint8_t *body, - size_t blen, size_t *c) { - - if (size != sizeof(uint32_t) || *c+size > blen || SIZE_T_OVERFLOW(*c, size)) - return EINVAL; - - SAFEALIGN_COPY_UINT32_CHECK(var, &body[*c], blen, c); - - return EOK; -} - -static int pd_set_primary_name(const struct ldb_message *msg,struct pam_data *pd) -{ - const char *name; - - name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); - if (!name) { - DEBUG(SSSDBG_CRIT_FAILURE, "A user with no name?\n"); - return EIO; - } - - if (strcmp(pd->user, name)) { - DEBUG(SSSDBG_TRACE_FUNC, "User's primary name is %s\n", name); - talloc_free(pd->user); - pd->user = talloc_strdup(pd, name); - if (!pd->user) return ENOMEM; - } - - return EOK; -} - -static int pam_parse_in_data_v2(struct pam_data *pd, - uint8_t *body, size_t blen) -{ - size_t c; - uint32_t type; - uint32_t size; - int ret; - uint32_t start; - uint32_t terminator; - char *requested_domains; - - if (blen < 4*sizeof(uint32_t)+2) { - DEBUG(SSSDBG_CRIT_FAILURE, "Received data is invalid.\n"); - return EINVAL; - } - - SAFEALIGN_COPY_UINT32(&start, body, NULL); - SAFEALIGN_COPY_UINT32(&terminator, body + blen - sizeof(uint32_t), NULL); - - if (start != SSS_START_OF_PAM_REQUEST - || terminator != SSS_END_OF_PAM_REQUEST) { - DEBUG(SSSDBG_CRIT_FAILURE, "Received data is invalid.\n"); - return EINVAL; - } - - c = sizeof(uint32_t); - do { - SAFEALIGN_COPY_UINT32_CHECK(&type, &body[c], blen, &c); - - if (type == SSS_END_OF_PAM_REQUEST) { - if (c != blen) return EINVAL; - } else { - SAFEALIGN_COPY_UINT32_CHECK(&size, &body[c], blen, &c); - /* the uint32_t end maker SSS_END_OF_PAM_REQUEST does not count to - * the remaining buffer */ - if (size > (blen - c - sizeof(uint32_t))) { - DEBUG(SSSDBG_CRIT_FAILURE, "Invalid data size.\n"); - return EINVAL; - } - - switch(type) { - case SSS_PAM_ITEM_USER: - ret = extract_string(&pd->logon_name, size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_SERVICE: - ret = extract_string(&pd->service, size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_TTY: - ret = extract_string(&pd->tty, size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_RUSER: - ret = extract_string(&pd->ruser, size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_RHOST: - ret = extract_string(&pd->rhost, size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_REQUESTED_DOMAINS: - ret = extract_string(&requested_domains, size, body, blen, - &c); - if (ret != EOK) return ret; - - ret = split_on_separator(pd, requested_domains, ',', true, - true, &pd->requested_domains, - NULL); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to parse requested_domains list!\n"); - return ret; - } - break; - case SSS_PAM_ITEM_CLI_PID: - ret = extract_uint32_t(&pd->cli_pid, size, - body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_CHILD_PID: - /* This is optional. */ - ret = extract_uint32_t(&pd->child_pid, size, - body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_AUTHTOK: - ret = extract_authtok_v2(pd->authtok, - size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_NEWAUTHTOK: - ret = extract_authtok_v2(pd->newauthtok, - size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_FLAGS: - ret = extract_uint32_t(&pd->cli_flags, size, - body, blen, &c); - if (ret != EOK) return ret; - break; - default: - DEBUG(SSSDBG_CRIT_FAILURE, - "Ignoring unknown data type [%d].\n", type); - c += size; - } - } - - } while(c < blen); - - return EOK; - -} - -static int pam_parse_in_data_v3(struct pam_data *pd, - uint8_t *body, size_t blen) -{ - int ret; - - ret = pam_parse_in_data_v2(pd, body, blen); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_parse_in_data_v2 failed.\n"); - return ret; - } - - if (pd->cli_pid == 0) { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing client PID.\n"); - return EINVAL; - } - - return EOK; -} - -static int extract_authtok_v1(struct sss_auth_token *tok, - uint8_t *body, size_t blen, size_t *c) -{ - uint32_t auth_token_type; - uint32_t auth_token_length; - uint8_t *auth_token_data; - int ret = EOK; - - SAFEALIGN_COPY_UINT32_CHECK(&auth_token_type, &body[*c], blen, c); - SAFEALIGN_COPY_UINT32_CHECK(&auth_token_length, &body[*c], blen, c); - auth_token_data = body+(*c); - - switch (auth_token_type) { - case SSS_AUTHTOK_TYPE_EMPTY: - sss_authtok_set_empty(tok); - break; - case SSS_AUTHTOK_TYPE_PASSWORD: - ret = sss_authtok_set_password(tok, (const char *)auth_token_data, - auth_token_length); - break; - default: - return EINVAL; - } - - *c += auth_token_length; - - return ret; -} - -static int pam_parse_in_data(struct pam_data *pd, - uint8_t *body, size_t blen) -{ - size_t start; - size_t end; - size_t last; - int ret; - - last = blen - 1; - end = 0; - - /* user name */ - for (start = end; end < last; end++) if (body[end] == '\0') break; - if (body[end++] != '\0') return EINVAL; - pd->logon_name = (char *) &body[start]; - - for (start = end; end < last; end++) if (body[end] == '\0') break; - if (body[end++] != '\0') return EINVAL; - pd->service = (char *) &body[start]; - - for (start = end; end < last; end++) if (body[end] == '\0') break; - if (body[end++] != '\0') return EINVAL; - pd->tty = (char *) &body[start]; - - for (start = end; end < last; end++) if (body[end] == '\0') break; - if (body[end++] != '\0') return EINVAL; - pd->ruser = (char *) &body[start]; - - for (start = end; end < last; end++) if (body[end] == '\0') break; - if (body[end++] != '\0') return EINVAL; - pd->rhost = (char *) &body[start]; - - ret = extract_authtok_v1(pd->authtok, body, blen, &end); - if (ret) { - DEBUG(SSSDBG_CRIT_FAILURE, "Invalid auth token\n"); - return ret; - } - ret = extract_authtok_v1(pd->newauthtok, body, blen, &end); - if (ret) { - DEBUG(SSSDBG_CRIT_FAILURE, "Invalid new auth token\n"); - return ret; - } - - DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); - - return EOK; -} - -static errno_t -pam_get_local_auth_policy(struct sss_domain_info *domain, - const char *name, - bool *_sc_allow, - bool *_passkey_allow) -{ - TALLOC_CTX *tmp_ctx = NULL; - const char *attrs[] = { SYSDB_LOCAL_SMARTCARD_AUTH, SYSDB_LOCAL_PASSKEY_AUTH, NULL }; - struct ldb_message *ldb_msg; - bool sc_allow = false; - bool passkey_allow = false; - errno_t ret; - - if (name == NULL || *name == '\0') { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); - ret = EINVAL; - goto done; - } - - if (domain->sysdb == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing sysdb db context.\n"); - ret = EINVAL; - goto done; - } - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - ret = sysdb_search_user_by_name(tmp_ctx, domain, name, attrs, &ldb_msg); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "sysdb_search_user_by_name failed [%d][%s].\n", - ret, strerror(ret)); - goto done; - } - - sc_allow = ldb_msg_find_attr_as_bool(ldb_msg, SYSDB_LOCAL_SMARTCARD_AUTH, - false); - - passkey_allow = ldb_msg_find_attr_as_bool(ldb_msg, SYSDB_LOCAL_PASSKEY_AUTH, - true); - - ret = EOK; - -done: - if (ret == EOK) { - *_sc_allow = sc_allow; - *_passkey_allow = passkey_allow; - } - - talloc_free(tmp_ctx); - return ret; -} -static errno_t set_local_auth_type(struct pam_auth_req *preq, - bool sc_allow, - bool passkey_allow) -{ - struct sysdb_attrs *attrs; - errno_t ret; - - attrs = sysdb_new_attrs(preq); - if (!attrs) { - ret = ENOMEM; - goto fail; - } - - if (sc_allow) { - /* Only set SYSDB_LOCAL_SMARTCARD_AUTH to 'true' but never to - * 'false'. The krb5 backend will only returns that Smartcard - * authentication is available if a Smartcard is present. That means - * if the user authenticates with a different method and a Smartcard - * is not present at this time 'sc_allow' will be 'false' and might - * overwrite a 'true' value written during a previous authentication - * attempt where a Smartcard was present. To avoid this we only write - * 'true' values. Since the default if SYSDB_LOCAL_SMARTCARD_AUTH is - * missing is 'false' local Smartcard authentication (offline) will - * still only be enabled if online Smartcard authentication was - * detected. */ - ret = sysdb_attrs_add_bool(attrs, SYSDB_LOCAL_SMARTCARD_AUTH, sc_allow); - if (ret != EOK) { - goto fail; - } - } - - ret = sysdb_attrs_add_bool(attrs, SYSDB_LOCAL_PASSKEY_AUTH, passkey_allow); - if (ret != EOK) { - goto fail; - } - - ret = sysdb_set_user_attr(preq->domain, preq->pd->user, attrs, - SYSDB_MOD_REP); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "set_local_auth_type failed.\n"); - preq->pd->pam_status = PAM_SYSTEM_ERR; - goto fail; - } - - return EOK; - -fail: - return ret; -} -/*=Save-Last-Login-State===================================================*/ - -static errno_t set_last_login(struct pam_auth_req *preq) -{ - struct sysdb_attrs *attrs; - errno_t ret; - - attrs = sysdb_new_attrs(preq); - if (!attrs) { - ret = ENOMEM; - goto fail; - } - - ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_ONLINE_AUTH, time(NULL)); - if (ret != EOK) { - goto fail; - } - - ret = sysdb_attrs_add_time_t(attrs, - SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, - time(NULL)); - if (ret != EOK) { - goto fail; - } - - ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_LOGIN, time(NULL)); - if (ret != EOK) { - goto fail; - } - - ret = sysdb_set_user_attr(preq->domain, preq->pd->user, attrs, - SYSDB_MOD_REP); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "set_last_login failed.\n"); - preq->pd->pam_status = PAM_SYSTEM_ERR; - goto fail; - } else { - preq->pd->last_auth_saved = true; - } - preq->callback(preq); - - return EOK; - -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 ? var_name : "(NULL)"), - (service ? service : "(NULL)")); - - 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 pam_ctx *pctx, - struct response_data *resp_list, - struct pam_data *pd) -{ - int ret; - struct response_data *resp; - uint32_t user_info_type; - int64_t expire_date = 0; - int pam_verbosity = DEFAULT_PAM_VERBOSITY; - char **new_opts; - size_t c; - const char *default_pam_response_filter[] = { "ENV:KRB5CCNAME:sudo", - "ENV:KRB5CCNAME:sudo-i", - NULL }; - - ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_VERBOSITY, DEFAULT_PAM_VERBOSITY, - &pam_verbosity); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read PAM verbosity, not fatal.\n"); - pam_verbosity = DEFAULT_PAM_VERBOSITY; - } - - if (pctx->pam_filter_opts == NULL) { - ret = confdb_get_string_as_list(pctx->rctx->cdb, pctx, - CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_RESPONSE_FILTER, - &pctx->pam_filter_opts); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read values of [%s], not fatal.\n", - CONFDB_PAM_RESPONSE_FILTER); - pctx->pam_filter_opts = NULL; - } else { - if (pctx->pam_filter_opts == NULL - || *pctx->pam_filter_opts[0] == '+' - || *pctx->pam_filter_opts[0] == '-') { - ret = mod_defaults_list(pctx, default_pam_response_filter, - pctx->pam_filter_opts, &new_opts); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to modify [%s] defaults.\n", - CONFDB_PAM_RESPONSE_FILTER); - return ret; - } - talloc_free(pctx->pam_filter_opts); - pctx->pam_filter_opts = new_opts; - } - } - - if (pctx->pam_filter_opts == NULL) { - DEBUG(SSSDBG_CONF_SETTINGS, "No PAM response filter set.\n"); - } else { - /* Make sure there are no '+' or '-' prefixes anymore */ - for (c = 0; pctx->pam_filter_opts[c] != NULL; c++) { - if (*pctx->pam_filter_opts[0] == '+' - || *pctx->pam_filter_opts[0] == '-') { - DEBUG(SSSDBG_CRIT_FAILURE, - "Unsupport mix of prefixed and not prefixed " - "values of [%s].\n", CONFDB_PAM_RESPONSE_FILTER); - return EINVAL; - } - DEBUG(SSSDBG_CONF_SETTINGS, - "PAM response filter: [%s].\n", - pctx->pam_filter_opts[c]); - } - } - } - - 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"); - ret = EINVAL; - goto done; - } - - if (pam_verbosity == PAM_VERBOSITY_NO_MESSAGES) { - resp->do_not_send_to_client = true; - resp = resp->next; - continue; - } - - memcpy(&user_info_type, resp->data, sizeof(uint32_t)); - - resp->do_not_send_to_client = false; - switch (user_info_type) { - case SSS_PAM_USER_INFO_OFFLINE_AUTH: - if (resp->len != sizeof(uint32_t) + sizeof(int64_t)) { - DEBUG(SSSDBG_CRIT_FAILURE, - "User info offline auth entry is " - "too short.\n"); - ret = EINVAL; - goto done; - } - memcpy(&expire_date, resp->data + sizeof(uint32_t), - sizeof(int64_t)); - if ((expire_date == 0 && - pam_verbosity < PAM_VERBOSITY_INFO) || - (expire_date > 0 && - pam_verbosity < PAM_VERBOSITY_IMPORTANT)) { - resp->do_not_send_to_client = true; - } - - break; - default: - DEBUG(SSSDBG_TRACE_LIBS, - "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, pctx->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; - } - - resp = resp->next; - } - - ret = EOK; -done: - - return ret; -} - -static void do_not_send_cert_info(struct pam_data *pd) -{ - struct response_data *resp; - - resp = pd->resp_list; - while (resp != NULL) { - switch (resp->type) { - case SSS_PAM_CERT_INFO: - case SSS_PAM_CERT_INFO_WITH_HINT: - resp->do_not_send_to_client = true; - break; - default: - break; - } - resp = resp->next; - } -} - -static void evaluate_pam_resp_list(struct pam_data *pd, - struct pam_resp_auth_type *_auth_types, - bool *_found_cert_info) -{ - struct response_data *resp; - struct pam_resp_auth_type types = {0}; - bool found_cert_info = false; - - resp = pd->resp_list; - while (resp != NULL) { - switch (resp->type) { - case SSS_PAM_OTP_INFO: - types.otp_auth = true; - break; - case SSS_PAM_CERT_INFO: - case SSS_PAM_CERT_INFO_WITH_HINT: - found_cert_info = true; - break; - case SSS_PAM_PASSKEY_INFO: - case SSS_PAM_PASSKEY_KRB_INFO: - types.passkey_auth = true; - break; - case SSS_PASSWORD_PROMPTING: - types.password_auth = true; - break; - case SSS_CERT_AUTH_PROMPTING: - types.cert_auth = true; - break; - default: - break; - } - resp = resp->next; - } - - if (_auth_types != NULL) { - *_auth_types = types; - } - if (_found_cert_info != NULL) { - *_found_cert_info = found_cert_info; - } -} - -static void evalute_sending_cert_info(struct pam_data *pd) -{ - struct pam_resp_auth_type types = {0}; - bool found_cert_info = false; - - evaluate_pam_resp_list(pd, &types, &found_cert_info); - - if (found_cert_info && !types.cert_auth) { - do_not_send_cert_info(pd); - } -} - -errno_t pam_get_auth_types(struct pam_data *pd, - struct pam_resp_auth_type *_auth_types) -{ - int ret; - struct pam_resp_auth_type types = {0}; - - evaluate_pam_resp_list(pd, &types, NULL); - - if (!types.password_auth && !types.otp_auth && !types.cert_auth && !types.passkey_auth) { - /* If the backend cannot determine which authentication types are - * available the default would be to prompt for a password. */ - types.password_auth = true; - types.backend_returned_no_auth_type = true; - } - - DEBUG(SSSDBG_TRACE_ALL, "Authentication types for user [%s] and service " - "[%s]:%s%s%s%s\n", pd->user, pd->service, - types.password_auth ? " password": "", - types.otp_auth ? " two-factor" : "", - types.passkey_auth ? " passkey" : "", - types.cert_auth ? " smartcard" : ""); - - ret = EOK; - - *_auth_types = types; - - return ret; -} - -static errno_t pam_eval_local_auth_policy(TALLOC_CTX *mem_ctx, - struct pam_ctx *pctx, - struct pam_data *pd, - struct pam_auth_req *preq, - bool *_sc_allow, - bool *_passkey_allow, - char **_local_policy) { - - TALLOC_CTX *tmp_ctx; - errno_t ret; - const char *domain_cdb; - char *local_policy = NULL; - bool sc_allow = false; - bool passkey_allow = false; - struct pam_resp_auth_type auth_types; - char **opts; - size_t c; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - return ENOMEM; - } - - /* Check local auth policy */ - domain_cdb = talloc_asprintf(tmp_ctx, CONFDB_DOMAIN_PATH_TMPL, preq->domain->name); - if (domain_cdb == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = confdb_get_string(pctx->rctx->cdb, tmp_ctx, domain_cdb, - CONFDB_DOMAIN_LOCAL_AUTH_POLICY, - "match", &local_policy); - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get the confdb local_auth_policy\n"); - return ret; - } - - /* "only" ignores online methods and allows all local ones */ - if (strcasecmp(local_policy, "only") == 0) { - sc_allow = true; - passkey_allow = true; - /* Match what the KDC supports and provides */ - } else if (strcasecmp(local_policy, "match") == 0) { - /* Don't overwrite the local auth type when offline */ - if (pd->pam_status == PAM_SUCCESS && pd->cmd == SSS_PAM_PREAUTH && - !is_domain_provider(preq->domain, "ldap")) { - ret = pam_get_auth_types(pd, &auth_types); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to get authentication types\n"); - goto done; - } - - if (auth_types.cert_auth) { - sc_allow = true; - } else if (auth_types.passkey_auth) { - passkey_allow = true; - } - - /* Store the local auth types, in case we go offline */ - if (!auth_types.backend_returned_no_auth_type) { - ret = set_local_auth_type(preq, sc_allow, passkey_allow); - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, - "Failed to evaluate local auth policy\n"); - goto done; - } - } - } - - /* Read the latest auth types */ - ret = pam_get_local_auth_policy(preq->domain, preq->pd->user, - &sc_allow, &passkey_allow); - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, - "Failed to get PAM local auth policy\n"); - goto done; - } - /* Check for enable */ - } else { - ret = split_on_separator(tmp_ctx, local_policy, ',', true, true, &opts, - NULL); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d], %s.\n", - ret, sss_strerror(ret)); - goto done; - } - - for (c = 0; opts[c] != NULL; c++) { - if (strcasestr(opts[c], "passkey") != NULL) { - passkey_allow = strstr(opts[c], "enable") ? true : false; - } else if (strcasestr(opts[c], "smartcard") != NULL) { - sc_allow = strstr(opts[c], "enable") ? true : false; - } else { - DEBUG(SSSDBG_MINOR_FAILURE, - "Unexpected local auth policy option [%s], " \ - "skipping.\n", opts[c]); - } - } - } - - /* if passkey is enabled but local Smartcard authentication is not but - * possible, the cert info data has to be remove as well if only local - * Smartcard authentication is possible. If Smartcard authentication - * is possible on the server side we have to keep it because the - * 'enable' option should only add local methods but not reject remote - * ones. */ - if (!sc_allow) { - evalute_sending_cert_info(pd); - } - - *_sc_allow = sc_allow; - *_passkey_allow = passkey_allow; - *_local_policy = talloc_steal(mem_ctx, local_policy); - - ret = EOK; - -done: - talloc_free(tmp_ctx); - return ret; -} - -static void pam_reply_delay(struct tevent_context *ev, struct tevent_timer *te, - struct timeval tv, void *pvt) -{ - struct pam_auth_req *preq; - - DEBUG(SSSDBG_CONF_SETTINGS, "pam_reply_delay get called.\n"); - - preq = talloc_get_type(pvt, struct pam_auth_req); - - pam_reply(preq); -} - -static errno_t get_password_for_cache_auth(struct sss_auth_token *authtok, - const char **password) -{ - int ret; - size_t pw_len; - const char *fa2; - size_t fa2_len; - - switch (sss_authtok_get_type(authtok)) { - case SSS_AUTHTOK_TYPE_PASSWORD: - ret = sss_authtok_get_password(authtok, password, NULL); - break; - case SSS_AUTHTOK_TYPE_2FA: - ret = sss_authtok_get_2fa(authtok, password, &pw_len, &fa2, &fa2_len); - break; - default: - DEBUG(SSSDBG_FATAL_FAILURE, "Unsupported auth token type [%d].\n", - sss_authtok_get_type(authtok)); - ret = EINVAL; - } - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get password.\n"); - return ret; - } - - return EOK; -} - -static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd); -static void pam_handle_cached_login(struct pam_auth_req *preq, int ret, - time_t expire_date, time_t delayed_until, bool cached_auth); - -/* - * Add a request to add a variable to the PAM user environment, containing the - * actual (not overridden) user shell, in case session recording is enabled. - */ -static int pam_reply_sr_export_shell(struct pam_auth_req *preq, - const char *var_name) -{ - int ret; - TALLOC_CTX *ctx = NULL; - bool enabled; - const char *enabled_str; - const char *shell; - char *buf; - - /* Create temporary talloc context */ - ctx = talloc_new(NULL); - if (ctx == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); - ret = ENOMEM; - goto done; - } - - /* Check if session recording is enabled */ - if (preq->cctx->rctx->sr_conf.scope == - SESSION_RECORDING_SCOPE_NONE) { - enabled = false; - } else { - enabled_str = ldb_msg_find_attr_as_string(preq->user_obj, - SYSDB_SESSION_RECORDING, NULL); - if (enabled_str == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "%s attribute not found\n", SYSDB_SESSION_RECORDING); - ret = ENOENT; - goto done; - } else if (strcmp(enabled_str, "TRUE") == 0) { - enabled = true; - } else if (strcmp(enabled_str, "FALSE") == 0) { - enabled = false; - } else { - DEBUG(SSSDBG_CRIT_FAILURE, "invalid value of %s attribute: %s\n", - SYSDB_SESSION_RECORDING, enabled_str); - ret = ENOENT; - goto done; - } - } - - /* Export original shell if recording is enabled and so it's overridden */ - if (enabled) { - /* Extract the shell */ - shell = sss_resp_get_shell_override(preq->user_obj, - preq->cctx->rctx, preq->domain); - if (shell == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "user has no shell\n"); - ret = ENOENT; - goto done; - } - - /* Format environment entry */ - buf = talloc_asprintf(ctx, "%s=%s", var_name, shell); - if (buf == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); - ret = ENOMEM; - goto done; - } - - /* Add request to add the entry to user environment */ - ret = pam_add_response(preq->pd, SSS_PAM_ENV_ITEM, - strlen(buf) + 1, (uint8_t *)buf); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); - goto done; - } - } - - ret = EOK; - -done: - talloc_free(ctx); - return ret; -} - -void pam_reply(struct pam_auth_req *preq) -{ - struct cli_ctx *cctx; - struct cli_protocol *prctx; - uint8_t *body; - size_t blen; - int ret; - int32_t resp_c; - int32_t resp_size; - struct response_data *resp; - int p; - struct timeval tv; - struct tevent_timer *te; - struct pam_data *pd; - char *local_policy = NULL; - struct pam_ctx *pctx; - uint32_t user_info_type; - time_t exp_date = -1; - time_t delay_until = -1; - char* pam_account_expired_message; - char* pam_account_locked_message; - int pam_verbosity; - bool local_sc_auth_allow = false; - bool local_passkey_auth_allow = false; -#ifdef BUILD_PASSKEY - bool pk_preauth_done = false; -#endif /* BUILD_PASSKEY */ - - pd = preq->pd; - cctx = preq->cctx; - pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); - prctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); - - ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_VERBOSITY, DEFAULT_PAM_VERBOSITY, - &pam_verbosity); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read PAM verbosity, not fatal.\n"); - pam_verbosity = DEFAULT_PAM_VERBOSITY; - } - - DEBUG(SSSDBG_TRACE_ALL, - "pam_reply initially called with result [%d]: %s. " - "this result might be changed during processing\n", - pd->pam_status, pam_strerror(NULL, pd->pam_status)); - - if (preq->domain != NULL && preq->domain->name != NULL) { - ret = pam_eval_local_auth_policy(cctx, pctx, pd, preq, - &local_sc_auth_allow, - &local_passkey_auth_allow, - &local_policy); - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, - "Failed to evaluate local auth policy\n"); - goto done; - } - } - - /* Ignore local_auth_policy for the files provider, allow local - * smartcard auth (default behavior prior to local_auth_policy) */ - if (is_domain_provider(preq->domain, "files")) { - local_sc_auth_allow = true; - /* For the ldap auth provider we currently only support - * password based authentication */ - } else if (is_domain_provider(preq->domain, "ldap") && local_policy != NULL - && strcasecmp(local_policy, "match") == 0) { - local_passkey_auth_allow = false; - local_sc_auth_allow = false; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Local auth policy allowed: smartcard [%s], passkey [%s]\n", - local_sc_auth_allow ? "True" : "False", - local_passkey_auth_allow ? "True" : "False"); - - if (pd->cmd == SSS_PAM_AUTHENTICATE - && !preq->cert_auth_local - && (pd->pam_status == PAM_AUTHINFO_UNAVAIL - || pd->pam_status == PAM_NO_MODULE_DATA - || pd->pam_status == PAM_BAD_ITEM) - && may_do_cert_auth(pctx, pd)) { - /* We have Smartcard credentials and the backend indicates that it is - * offline (PAM_AUTHINFO_UNAVAIL) or cannot handle the credentials - * (PAM_BAD_ITEM), so let's try authentication against the Smartcard - * PAM_NO_MODULE_DATA is returned by the krb5 backend if no - * authentication method was found at all, this might happen if the - * user has a Smartcard assigned but the pkint plugin is not available - * on the client. */ - DEBUG(SSSDBG_IMPORTANT_INFO, - "Backend cannot handle Smartcard authentication, " - "trying local Smartcard authentication.\n"); - if (local_sc_auth_allow) { - preq->cert_auth_local = true; - ret = check_cert(cctx, cctx->ev, pctx, preq, pd); - pam_check_user_done(preq, ret); - return; - } else { - DEBUG(SSSDBG_IMPORTANT_INFO, - "Local smartcard auth not allowed by local_auth_policy"); - } - } - - if (pd->pam_status == PAM_AUTHINFO_UNAVAIL || preq->use_cached_auth) { - - switch(pd->cmd) { - case SSS_PAM_AUTHENTICATE: - if ((preq->domain != NULL) && - (preq->domain->cache_credentials == true) && - (pd->offline_auth == false)) { - const char *password = NULL; - bool use_cached_auth; - - /* backup value of preq->use_cached_auth*/ - use_cached_auth = preq->use_cached_auth; - /* set to false to avoid entering this branch when pam_reply() - * is recursively called from pam_handle_cached_login() */ - preq->use_cached_auth = false; - - /* do auth with offline credentials */ - pd->offline_auth = true; - - if (preq->domain->sysdb == NULL) { - DEBUG(SSSDBG_FATAL_FAILURE, - "Fatal: Sysdb CTX not found for domain" - " [%s]!\n", preq->domain->name); - goto done; - } - - ret = get_password_for_cache_auth(pd->authtok, &password); - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, - "get_password_and_type_for_cache_auth failed.\n"); - goto done; - } - - ret = sysdb_cache_auth(preq->domain, - pd->user, password, - pctx->rctx->cdb, false, - &exp_date, &delay_until); - - pam_handle_cached_login(preq, ret, exp_date, delay_until, - use_cached_auth); - return; - } - break; - case SSS_PAM_CHAUTHTOK_PRELIM: - case SSS_PAM_CHAUTHTOK: - DEBUG(SSSDBG_FUNC_DATA, - "Password change not possible while offline.\n"); - pd->pam_status = PAM_AUTHTOK_ERR; - user_info_type = SSS_PAM_USER_INFO_OFFLINE_CHPASS; - ret = pam_add_response(pd, SSS_PAM_USER_INFO, sizeof(uint32_t), - (const uint8_t *) &user_info_type); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); - goto done; - } - break; -/* TODO: we need the pam session cookie here to make sure that cached - * authentication was successful */ - case SSS_PAM_PREAUTH: - case SSS_PAM_SETCRED: - case SSS_PAM_ACCT_MGMT: - case SSS_PAM_OPEN_SESSION: - case SSS_PAM_CLOSE_SESSION: - DEBUG(SSSDBG_OP_FAILURE, - "Assuming offline authentication setting status for " - "pam call %d to PAM_SUCCESS.\n", pd->cmd); - pd->pam_status = PAM_SUCCESS; - break; - default: - DEBUG(SSSDBG_CRIT_FAILURE, "Unknown PAM call [%d].\n", pd->cmd); - pd->pam_status = PAM_MODULE_UNKNOWN; - } - } - - if (pd->pam_status == PAM_SUCCESS && pd->cmd == SSS_PAM_CHAUTHTOK) { - ret = pam_null_last_online_auth_with_curr_token(preq->domain, - pd->user); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "sysdb_null_last_online_auth_with_curr_token failed: " - "%s [%d].\n", sss_strerror(ret), ret); - goto done; - } - } - - if (pd->response_delay > 0) { - ret = gettimeofday(&tv, NULL); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "gettimeofday failed [%d][%s].\n", - errno, strerror(errno)); - goto done; - } - tv.tv_sec += pd->response_delay; - tv.tv_usec = 0; - pd->response_delay = 0; - - te = tevent_add_timer(cctx->ev, cctx, tv, pam_reply_delay, preq); - if (te == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to add event pam_reply_delay.\n"); - goto done; - } - - return; - } - - /* If this was a successful login, save the lastLogin time */ - if (pd->cmd == SSS_PAM_AUTHENTICATE && - pd->pam_status == PAM_SUCCESS && - preq->domain && - preq->domain->cache_credentials && - !pd->offline_auth && - !pd->last_auth_saved) { - ret = set_last_login(preq); - if (ret != EOK) { - goto done; - } - return; - } - - ret = sss_packet_new(prctx->creq, 0, sss_packet_get_cmd(prctx->creq->in), - &prctx->creq->out); - if (ret != EOK) { - goto done; - } - -#ifdef BUILD_PASSKEY - if(pd->cmd == SSS_PAM_AUTHENTICATE && - pd->pam_status == PAM_NEW_AUTHTOK_REQD && - sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_REPLY) { - DEBUG(SSSDBG_TRACE_FUNC, "Passkey authentication reply, ignoring " - "new authtok required status\n"); - pd->pam_status = PAM_SUCCESS; - } - - /* Passkey auth user notification if no TGT is granted */ - if (pd->cmd == SSS_PAM_AUTHENTICATE && - pd->pam_status == PAM_SUCCESS && - preq->pd->passkey_local_done) { - user_info_type = SSS_PAM_USER_INFO_NO_KRB_TGT; - pam_add_response(pd, SSS_PAM_USER_INFO, - sizeof(uint32_t), (const uint8_t *) &user_info_type); - DEBUG(SSSDBG_IMPORTANT_INFO, - "User [%s] logged in with local passkey authentication, single " - "sign on ticket is not obtained.\n", pd->user); - } -#endif /* BUILD_PASSKEY */ - - /* Account expiration warning is printed for sshd. If pam_verbosity - * is equal or above PAM_VERBOSITY_INFO then all services are informed - * about account expiration. - */ - if (pd->pam_status == PAM_ACCT_EXPIRED && - ((pd->service != NULL && strcasecmp(pd->service, "sshd") == 0) || - pam_verbosity >= PAM_VERBOSITY_INFO)) { - - ret = confdb_get_string(pctx->rctx->cdb, pd, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_ACCOUNT_EXPIRED_MESSAGE, "", - &pam_account_expired_message); - if (ret != EOK) { - DEBUG(SSSDBG_MINOR_FAILURE, - "Failed to get expiration message: %d:[%s].\n", - ret, sss_strerror(ret)); - goto done; - } - - inform_user(pd, pam_account_expired_message); - } - - if (pd->account_locked) { - - ret = confdb_get_string(pctx->rctx->cdb, pd, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_ACCOUNT_LOCKED_MESSAGE, "", - &pam_account_locked_message); - if (ret != EOK) { - DEBUG(SSSDBG_MINOR_FAILURE, - "Failed to get expiration message: %d:[%s].\n", - ret, sss_strerror(ret)); - goto done; - } - - inform_user(pd, pam_account_locked_message); - } - - ret = filter_responses(pctx, pd->resp_list, pd); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "filter_responses failed, not fatal.\n"); - } - - if (pd->domain != NULL) { - ret = pam_add_response(pd, SSS_PAM_DOMAIN_NAME, strlen(pd->domain)+1, - (uint8_t *) pd->domain); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); - goto done; - } - } - - 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"); - } - -#ifdef BUILD_PASSKEY - ret = pam_eval_passkey_response(pctx, pd, preq, &pk_preauth_done); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to eval passkey response\n"); - goto done; - } - - if (may_do_passkey_auth(pctx, pd) - && !pk_preauth_done - && preq->passkey_data_exists - && local_passkey_auth_allow) { - ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); - pam_check_user_done(preq, ret); - return; - } -#endif /* BUILD_PASSKEY */ - } - - /* - * Export non-overridden shell to tlog-rec-session when opening the session - */ - if (pd->cmd == SSS_PAM_OPEN_SESSION && pd->pam_status == PAM_SUCCESS) { - ret = pam_reply_sr_export_shell(preq, "TLOG_REC_SESSION_SHELL"); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "failed to export the shell to tlog-rec-session.\n"); - goto done; - } - } - - resp_c = 0; - resp_size = 0; - resp = pd->resp_list; - while(resp != NULL) { - if (!resp->do_not_send_to_client) { - resp_c++; - resp_size += resp->len; - } - resp = resp->next; - } - - ret = sss_packet_grow(prctx->creq->out, sizeof(int32_t) + - sizeof(int32_t) + - resp_c * 2* sizeof(int32_t) + - resp_size); - if (ret != EOK) { - goto done; - } - - sss_packet_get_body(prctx->creq->out, &body, &blen); - DEBUG(SSSDBG_FUNC_DATA, "blen: %zu\n", blen); - p = 0; - - memcpy(&body[p], &pd->pam_status, sizeof(int32_t)); - p += sizeof(int32_t); - - memcpy(&body[p], &resp_c, sizeof(int32_t)); - p += sizeof(int32_t); - - resp = pd->resp_list; - while(resp != NULL) { - if (!resp->do_not_send_to_client) { - memcpy(&body[p], &resp->type, sizeof(int32_t)); - p += sizeof(int32_t); - memcpy(&body[p], &resp->len, sizeof(int32_t)); - p += sizeof(int32_t); - memcpy(&body[p], resp->data, resp->len); - p += resp->len; - } - - resp = resp->next; - } - -done: - DEBUG(SSSDBG_FUNC_DATA, "Returning [%d]: %s to the client\n", - pd->pam_status, pam_strerror(NULL, pd->pam_status)); - sss_cmd_done(cctx, preq); -} - -static void pam_dom_forwarder(struct pam_auth_req *preq); - -static void pam_handle_cached_login(struct pam_auth_req *preq, int ret, - time_t expire_date, time_t delayed_until, - bool use_cached_auth) -{ - uint32_t resp_type; - size_t resp_len; - uint8_t *resp; - int64_t dummy; - - preq->pd->pam_status = cached_login_pam_status(ret); - - switch (preq->pd->pam_status) { - case PAM_SUCCESS: - resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH; - resp_len = sizeof(uint32_t) + sizeof(int64_t); - resp = talloc_size(preq->pd, resp_len); - if (resp == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "talloc_size failed, cannot prepare user info.\n"); - } else { - memcpy(resp, &resp_type, sizeof(uint32_t)); - dummy = (int64_t) expire_date; - memcpy(resp+sizeof(uint32_t), &dummy, sizeof(int64_t)); - ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, - (const uint8_t *) resp); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); - } - } - break; - case PAM_PERM_DENIED: - if (delayed_until >= 0) { - resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED; - resp_len = sizeof(uint32_t) + sizeof(int64_t); - resp = talloc_size(preq->pd, resp_len); - if (resp == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "talloc_size failed, cannot prepare user info.\n"); - } else { - memcpy(resp, &resp_type, sizeof(uint32_t)); - dummy = (int64_t) delayed_until; - memcpy(resp+sizeof(uint32_t), &dummy, sizeof(int64_t)); - ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, - (const uint8_t *) resp); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "pam_add_response failed.\n"); - } - } - } - break; - case PAM_AUTH_ERR: - /* Was this attempt to authenticate from cache? */ - if (use_cached_auth) { - /* Don't try cached authentication again, try online check. */ - DEBUG(SSSDBG_FUNC_DATA, - "Cached authentication failed for: %s\n", - preq->pd->user); - preq->cached_auth_failed = true; - pam_dom_forwarder(preq); - return; - } - break; - default: - DEBUG(SSSDBG_TRACE_LIBS, - "cached login returned: %d\n", preq->pd->pam_status); - } - - pam_reply(preq); - return; -} - -static void pam_forwarder_cb(struct tevent_req *req); -static void pam_forwarder_cert_cb(struct tevent_req *req); -int pam_check_user_search(struct pam_auth_req *preq); - - -/* TODO: we should probably return some sort of cookie that is set in the - * PAM_ENVIRONMENT, so that we can save performing some calls and cache - * data. */ - -static errno_t pam_forwarder_parse_data(struct cli_ctx *cctx, struct pam_data *pd) -{ - struct cli_protocol *prctx; - uint8_t *body; - size_t blen; - errno_t ret; - uint32_t terminator; - const char *key_id; - - prctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); - - sss_packet_get_body(prctx->creq->in, &body, &blen); - if (blen >= sizeof(uint32_t)) { - SAFEALIGN_COPY_UINT32(&terminator, - body + blen - sizeof(uint32_t), - NULL); - if (terminator != SSS_END_OF_PAM_REQUEST) { - DEBUG(SSSDBG_CRIT_FAILURE, "Received data not terminated.\n"); - ret = EINVAL; - goto done; - } - } - - switch (prctx->cli_protocol_version->version) { - case 1: - ret = pam_parse_in_data(pd, body, blen); - break; - case 2: - ret = pam_parse_in_data_v2(pd, body, blen); - break; - case 3: - ret = pam_parse_in_data_v3(pd, body, blen); - break; - default: - DEBUG(SSSDBG_CRIT_FAILURE, "Illegal protocol version [%d].\n", - prctx->cli_protocol_version->version); - ret = EINVAL; - } - if (ret != EOK) { - goto done; - } - - if (pd->logon_name != NULL) { - ret = sss_parse_name_for_domains(pd, cctx->rctx->domains, - cctx->rctx->default_domain, - pd->logon_name, - &pd->domain, &pd->user); - } else { - /* SSS_PAM_PREAUTH request may have a missing name, e.g. if the - * name is determined with the help of a certificate. During - * SSS_PAM_AUTHENTICATE at least a key ID is needed to identify the - * selected certificate. */ - if (pd->cmd == SSS_PAM_AUTHENTICATE - && may_do_cert_auth(talloc_get_type(cctx->rctx->pvt_ctx, - struct pam_ctx), pd) - && (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_SC_PIN - || sss_authtok_get_type(pd->authtok) - == SSS_AUTHTOK_TYPE_SC_KEYPAD)) { - ret = sss_authtok_get_sc(pd->authtok, NULL, NULL, NULL, NULL, NULL, - NULL, &key_id, NULL, NULL, NULL); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_sc failed.\n"); - goto done; - } - - if (key_id == NULL || *key_id == '\0') { - DEBUG(SSSDBG_CRIT_FAILURE, - "Missing logon and Smartcard key ID during " - "authentication.\n"); - ret = ERR_NO_CREDS; - goto done; - } - - ret = EOK; - } else if (pd->cmd == SSS_PAM_PREAUTH - && may_do_cert_auth(talloc_get_type(cctx->rctx->pvt_ctx, - struct pam_ctx), pd)) { - ret = EOK; - } else { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing logon name in PAM request.\n"); - ret = ERR_NO_CREDS; - goto done; - } - } - - DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); - -done: - return ret; -} - -static bool is_uid_trusted(struct cli_creds *creds, - size_t trusted_uids_count, - uid_t *trusted_uids) -{ - errno_t ret; - - /* root is always trusted */ - if (client_euid(creds) == 0) { - return true; - } - - /* All uids are allowed */ - if (trusted_uids_count == 0) { - return true; - } - - ret = check_allowed_uids(client_euid(creds), trusted_uids_count, trusted_uids); - if (ret == EOK) return true; - - return false; -} - -static bool is_domain_public(char *name, - char **public_dom_names, - size_t public_dom_names_count) -{ - size_t i; - - for(i=0; i < public_dom_names_count; i++) { - if (strcasecmp(name, public_dom_names[i]) == 0) { - return true; - } - } - return false; -} - -static enum cache_req_dom_type -get_domain_request_type(struct pam_auth_req *preq, - struct pam_ctx *pctx) -{ - enum cache_req_dom_type req_dom_type; - - /* By default, only POSIX domains are to be contacted */ - req_dom_type = CACHE_REQ_POSIX_DOM; - - for (int i = 0; pctx->app_services[i]; i++) { - if (strcmp(pctx->app_services[i], preq->pd->service) == 0) { - req_dom_type = CACHE_REQ_APPLICATION_DOM; - break; - } - } - - return req_dom_type; -} - -static errno_t check_cert(TALLOC_CTX *mctx, - struct tevent_context *ev, - struct pam_ctx *pctx, - struct pam_auth_req *preq, - struct pam_data *pd) -{ - int p11_child_timeout; - int wait_for_card_timeout; - char *cert_verification_opts; - errno_t ret; - struct tevent_req *req; - char *uri = NULL; - - ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_P11_CHILD_TIMEOUT, - P11_CHILD_TIMEOUT_DEFAULT, - &p11_child_timeout); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read p11_child_timeout from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - return ret; - } - if ((pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) && pd->priv == 1) { - ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_WAIT_FOR_CARD_TIMEOUT, - P11_WAIT_FOR_CARD_TIMEOUT_DEFAULT, - &wait_for_card_timeout); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read [%s] from confdb: [%d]: %s\n", - CONFDB_PAM_WAIT_FOR_CARD_TIMEOUT, ret, sss_strerror(ret)); - return ret; - } - - p11_child_timeout += wait_for_card_timeout; - } - - ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_CERT_VERIFICATION, - NULL, &cert_verification_opts); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read '"CONFDB_PAM_CERT_VERIFICATION"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - return ret; - } - - if (cert_verification_opts == NULL) { - ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_MONITOR_CONF_ENTRY, - CONFDB_MONITOR_CERT_VERIFICATION, NULL, - &cert_verification_opts); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read '"CONFDB_MONITOR_CERT_VERIFICATION"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - return ret; - } - } - - ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_P11_URI, NULL, &uri); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read '"CONFDB_PAM_P11_URI"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - return ret; - } - - req = pam_check_cert_send(mctx, ev, - pctx->ca_db, p11_child_timeout, - cert_verification_opts, pctx->sss_certmap_ctx, - uri, pd); - if (req == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "pam_check_cert_send failed.\n"); - return ENOMEM; - } - - tevent_req_set_callback(req, pam_forwarder_cert_cb, preq); - return EAGAIN; -} - - -static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd) -{ - struct pam_auth_req *preq; - struct pam_data *pd; - int ret; - struct pam_ctx *pctx = - talloc_get_type(cctx->rctx->pvt_ctx, struct pam_ctx); - struct tevent_req *req; - - preq = talloc_zero(cctx, struct pam_auth_req); - if (!preq) { - return ENOMEM; - } - preq->cctx = cctx; - preq->cert_auth_local = false; - preq->client_id_num = cctx->client_id_num; - - preq->pd = create_pam_data(preq); - if (!preq->pd) { - talloc_free(preq); - return ENOMEM; - } - pd = preq->pd; - - preq->is_uid_trusted = is_uid_trusted(cctx->creds, - pctx->trusted_uids_count, - pctx->trusted_uids); - - if (!preq->is_uid_trusted) { - DEBUG(SSSDBG_MINOR_FAILURE, "uid %"SPRIuid" is not trusted.\n", - client_euid(cctx->creds)); - } - - - pd->cmd = pam_cmd; - pd->priv = cctx->priv; - pd->client_id_num = cctx->client_id_num; - - ret = pam_forwarder_parse_data(cctx, pd); - if (ret == EAGAIN) { - req = sss_dp_get_domains_send(cctx, cctx->rctx, true, pd->domain); - if (req == NULL) { - ret = ENOMEM; - } else { - tevent_req_set_callback(req, pam_forwarder_cb, preq); - ret = EAGAIN; - } - goto done; - } else if (ret != EOK) { - goto done; - } - - /* Determine what domain type to contact */ - preq->req_dom_type = get_domain_request_type(preq, pctx); - - if (pd->cmd == SSS_PAM_AUTHENTICATE - && (pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) - && !IS_SC_AUTHTOK(pd->authtok)) { - DEBUG(SSSDBG_MINOR_FAILURE, - "Smartcard authentication required but authentication " - "token [%d][%s] is not suitable.\n", - sss_authtok_get_type(pd->authtok), - sss_authtok_type_to_str(sss_authtok_get_type(pd->authtok))); - ret = ERR_NO_CREDS; - goto done; - } - - /* Try backend first for authentication before doing local Smartcard - * authentication if a logon name is available. Otherwise try to derive - * the logon name from the certificate first. */ - if ((pd->cmd != SSS_PAM_AUTHENTICATE - || (pd->cmd == SSS_PAM_AUTHENTICATE && pd->logon_name == NULL)) - && may_do_cert_auth(pctx, pd)) { - ret = check_cert(cctx, cctx->ev, pctx, preq, pd); - /* Finish here */ - goto done; - } - - /* This is set to false inside passkey_local() if no passkey data is found. - * It is checked in pam_reply() to avoid an endless loop */ - preq->passkey_data_exists = true; - -#ifdef BUILD_PASSKEY - if ((pd->cmd == SSS_PAM_AUTHENTICATE)) { - if (may_do_passkey_auth(pctx, pd)) { - if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { - ret = passkey_kerberos(pctx, preq->pd, preq); - goto done; - } else if ((sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY) || - (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_EMPTY)) { - ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); - goto done; - } - } - } -#endif /* BUILD_PASSKEY */ - - ret = pam_check_user_search(preq); - -done: - return pam_check_user_done(preq, ret); -} - -static errno_t pam_user_by_cert_step(struct pam_auth_req *preq); -static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req); -static void pam_forwarder_cert_cb(struct tevent_req *req) -{ - struct pam_auth_req *preq = tevent_req_callback_data(req, - struct pam_auth_req); - struct pam_data *pd; - errno_t ret = EOK; - const char *cert; - - ret = pam_check_cert_recv(req, preq, &preq->cert_list); - talloc_free(req); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "get_cert request failed.\n"); - goto done; - } - - pd = preq->pd; - - cert = sss_cai_get_cert(preq->cert_list); - - if (cert == NULL) { - if (pd->logon_name == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "No certificate found and no logon name given, " \ - "authentication not possible.\n"); - ret = ENOENT; - } else if (pd->cmd == SSS_PAM_PREAUTH - && (pd->cli_flags & PAM_CLI_FLAGS_TRY_CERT_AUTH)) { - DEBUG(SSSDBG_TRACE_ALL, - "try_cert_auth flag set but no certificate available, " - "request finished.\n"); - preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; - pam_reply(preq); - return; - } else { - if (pd->cmd == SSS_PAM_AUTHENTICATE) { - DEBUG(SSSDBG_CRIT_FAILURE, - "No certificate returned, authentication failed.\n"); - preq->pd->pam_status = PAM_AUTH_ERR; - pam_reply(preq); - return; - } else { - ret = pam_check_user_search(preq); - } - - } - goto done; - } - - preq->current_cert = preq->cert_list; - ret = pam_user_by_cert_step(preq); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n"); - goto done; - } - - return; - -done: - pam_check_user_done(preq, ret); -} - -static errno_t pam_user_by_cert_step(struct pam_auth_req *preq) -{ - struct cli_ctx *cctx = preq->cctx; - struct tevent_req *req; - struct pam_ctx *pctx = - talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); - - if (preq->current_cert == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate data.\n"); - return EINVAL; - } - - req = cache_req_user_by_cert_send(preq, cctx->ev, cctx->rctx, - pctx->rctx->ncache, 0, - preq->req_dom_type, NULL, - sss_cai_get_cert(preq->current_cert)); - if (req == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert_send failed.\n"); - return ENOMEM; - } - - tevent_req_set_callback(req, pam_forwarder_lookup_by_cert_done, preq); - return EOK; -} - -static errno_t get_results_from_all_domains(TALLOC_CTX *mem_ctx, - struct cache_req_result **results, - struct ldb_result **ldb_results) -{ - int ret; - size_t count = 0; - size_t c; - size_t d; - size_t r = 0; - struct ldb_result *res; - - for (d = 0; results != NULL && results[d] != NULL; d++) { - count += results[d]->count; - } - - res = talloc_zero(mem_ctx, struct ldb_result); - if (res == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); - return ENOMEM; - } - - if (count == 0) { - *ldb_results = res; - return EOK; - } - - res->msgs = talloc_zero_array(res, struct ldb_message *, count); - if (res->msgs == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); - return ENOMEM; - } - res->count = count; - - for (d = 0; results != NULL && results[d] != NULL; d++) { - for (c = 0; c < results[d]->count; c++) { - if (r >= count) { - DEBUG(SSSDBG_CRIT_FAILURE, - "More results found then counted before.\n"); - ret = EINVAL; - goto done; - } - res->msgs[r++] = talloc_steal(res->msgs, results[d]->msgs[c]); - } - } - - *ldb_results = res; - ret = EOK; - -done: - if (ret != EOK) { - talloc_free(res); - } - - return ret; -} - -/* Return true if hint is set for at least one domain */ -static bool get_user_name_hint(struct sss_domain_info *domains) -{ - struct sss_domain_info *d; - - DLIST_FOR_EACH(d, domains) { - if (d->user_name_hint == true) { - return true; - } - } - - return false; -} - -static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req) -{ - int ret; - struct cache_req_result **results; - struct pam_auth_req *preq = tevent_req_callback_data(req, - struct pam_auth_req); - const char *cert_user = NULL; - size_t cert_count = 0; - size_t cert_user_count = 0; - struct ldb_result *cert_user_objs; - - ret = cache_req_recv(preq, req, &results); - talloc_zfree(req); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert request failed.\n"); - goto done; - } - - if (ret == EOK) { - ret = get_results_from_all_domains(preq, results, - &cert_user_objs); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "get_results_from_all_domains failed.\n"); - goto done; - } - - sss_cai_set_cert_user_objs(preq->current_cert, cert_user_objs); - } - - preq->current_cert = sss_cai_get_next(preq->current_cert); - if (preq->current_cert != NULL) { - ret = pam_user_by_cert_step(preq); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n"); - goto done; - } - return; - } - - sss_cai_check_users(&preq->cert_list, &cert_count, &cert_user_count); - DEBUG(SSSDBG_TRACE_ALL, - "Found [%zu] certificates and [%zu] related users.\n", - cert_count, cert_user_count); - - if (cert_user_count == 0) { - if (preq->pd->logon_name == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Missing logon name and no certificate user found.\n"); - ret = ENOENT; - goto done; - } - } else { - - if (preq->pd->logon_name == NULL) { - if (preq->pd->cmd != SSS_PAM_PREAUTH - && preq->pd->cmd != SSS_PAM_AUTHENTICATE) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Missing logon name only allowed during (pre-)auth.\n"); - ret = ENOENT; - goto done; - } - /* Multiple certificates are only expected during pre-auth */ - if (cert_count > 1 && preq->pd->cmd == SSS_PAM_PREAUTH) { - for (preq->current_cert = preq->cert_list; - preq->current_cert != NULL; - preq->current_cert = sss_cai_get_next(preq->current_cert)) { - - ret = add_pam_cert_response(preq->pd, - preq->cctx->rctx->domains, "", - preq->current_cert, - get_user_name_hint(preq->cctx->rctx->domains) - ? SSS_PAM_CERT_INFO_WITH_HINT - : SSS_PAM_CERT_INFO); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "add_pam_cert_response failed.\n"); - preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; - } - } - - ret = EOK; - preq->pd->pam_status = PAM_SUCCESS; - pam_reply(preq); - goto done; - } - - if (cert_user_count == 1) { - cert_user_objs = sss_cai_get_cert_user_objs(preq->cert_list); - if (cert_user_objs == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate user.\n"); - ret = ENOENT; - goto done; - } - - cert_user = ldb_msg_find_attr_as_string( - cert_user_objs->msgs[0], - SYSDB_NAME, NULL); - if (cert_user == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Certificate user object has not name.\n"); - ret = ENOENT; - goto done; - } - - DEBUG(SSSDBG_FUNC_DATA, - "Found certificate user [%s].\n", cert_user); - - ret = sss_parse_name_for_domains(preq->pd, - preq->cctx->rctx->domains, - preq->cctx->rctx->default_domain, - cert_user, - &preq->pd->domain, - &preq->pd->user); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "sss_parse_name_for_domains failed.\n"); - goto done; - } - } - - if (get_user_name_hint(preq->cctx->rctx->domains) - && preq->pd->cmd == SSS_PAM_PREAUTH) { - ret = add_pam_cert_response(preq->pd, - preq->cctx->rctx->domains, cert_user, - preq->cert_list, - SSS_PAM_CERT_INFO_WITH_HINT); - preq->pd->pam_status = PAM_SUCCESS; - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); - preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; - } - ret = EOK; - pam_reply(preq); - goto done; - } - - /* Without user name hints the certificate must map to single user - * if no login name was given */ - if (cert_user == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "More than one user mapped to certificate.\n"); - ret = ERR_NO_CREDS; - goto done; - } - - /* If logon_name was not given during authentication add a - * SSS_PAM_CERT_INFO message to send the name to the caller. - * Additionally initial_cert_auth_successful is set to - * indicate that the user is already authenticated. */ - if (preq->pd->cmd == SSS_PAM_AUTHENTICATE - && preq->pd->logon_name == NULL) { - ret = add_pam_cert_response(preq->pd, - preq->cctx->rctx->domains, cert_user, - preq->cert_list, - SSS_PAM_CERT_INFO); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); - preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; - goto done; - } - - preq->initial_cert_auth_successful = true; - } - - /* cert_user will be returned to the PAM client as user name, so - * we can use it here already e.g. to set in initgroups timeout */ - preq->pd->logon_name = talloc_strdup(preq->pd, cert_user); - if (preq->pd->logon_name == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); - ret = ENOMEM; - goto done; - } - } - } - - if (preq->user_obj == NULL) { - ret = pam_check_user_search(preq); - } else { - ret = EOK; - } - - if (ret == EOK) { - pam_dom_forwarder(preq); - } - -done: - pam_check_user_done(preq, ret); -} - -static void pam_forwarder_cb(struct tevent_req *req) -{ - struct pam_auth_req *preq = tevent_req_callback_data(req, - struct pam_auth_req); - struct cli_ctx *cctx = preq->cctx; - struct pam_data *pd; - errno_t ret = EOK; - struct pam_ctx *pctx = - talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); - - ret = sss_dp_get_domains_recv(req); - talloc_free(req); - if (ret != EOK) { - goto done; - } - - ret = p11_refresh_certmap_ctx(pctx, pctx->rctx->domains); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "p11_refresh_certmap_ctx failed, " - "certificate matching might not work as expected"); - } - - pd = preq->pd; - - ret = pam_forwarder_parse_data(cctx, pd); - if (ret == EAGAIN) { - DEBUG(SSSDBG_TRACE_FUNC, "Assuming %s is a UPN\n", pd->logon_name); - /* If not, cache_req will error out later */ - pd->user = talloc_strdup(pd, pd->logon_name); - if (pd->user == NULL) { - ret = ENOMEM; - goto done; - } - pd->domain = NULL; - } else if (ret != EOK) { - ret = EINVAL; - goto done; - } - - /* try backend first for authentication before doing local Smartcard - * authentication */ - if (pd->cmd != SSS_PAM_AUTHENTICATE && may_do_cert_auth(pctx, pd)) { - ret = check_cert(cctx, cctx->ev, pctx, preq, pd); - /* Finish here */ - goto done; - } - -#ifdef BUILD_PASSKEY - /* This is set to false inside passkey_local() if no passkey data is found. - * It is checked in pam_reply() to avoid an endless loop */ - preq->passkey_data_exists = true; - - if ((pd->cmd == SSS_PAM_AUTHENTICATE)) { - if (may_do_passkey_auth(pctx, pd)) { - if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { - ret = passkey_kerberos(pctx, preq->pd, preq); - goto done; - } else if ((sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY) || - (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_EMPTY)) { - ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); - goto done; - } - } - } -#endif /* BUILD_PASSKEY */ - - ret = pam_check_user_search(preq); - -done: - pam_check_user_done(preq, ret); -} - -static void pam_check_user_search_next(struct tevent_req *req); -static void pam_check_user_search_lookup(struct tevent_req *req); -static void pam_check_user_search_done(struct pam_auth_req *preq, int ret, - struct cache_req_result *result); - -/* lookup the user uid from the cache first, - * then we'll refresh initgroups if needed */ -int pam_check_user_search(struct pam_auth_req *preq) -{ - struct tevent_req *dpreq; - struct cache_req_data *data; - - data = cache_req_data_name(preq, - CACHE_REQ_INITGROUPS, - preq->pd->logon_name); - if (data == NULL) { - return ENOMEM; - } - - cache_req_data_set_bypass_cache(data, false); - cache_req_data_set_bypass_dp(data, true); - cache_req_data_set_requested_domains(data, preq->pd->requested_domains); - - dpreq = cache_req_send(preq, - preq->cctx->rctx->ev, - preq->cctx->rctx, - preq->cctx->rctx->ncache, - 0, - preq->req_dom_type, - NULL, - data); - if (!dpreq) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Out of memory sending data provider request\n"); - return ENOMEM; - } - - tevent_req_set_callback(dpreq, pam_check_user_search_next, preq); - - /* tell caller we are in an async call */ - return EAGAIN; -} - -static void pam_check_user_search_next(struct tevent_req *req) -{ - struct pam_auth_req *preq; - struct pam_ctx *pctx; - struct cache_req_result *result = NULL; - struct cache_req_data *data; - struct tevent_req *dpreq; - int ret; - - preq = tevent_req_callback_data(req, struct pam_auth_req); - pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); - - ret = cache_req_single_domain_recv(preq, req, &result); - talloc_zfree(req); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_OP_FAILURE, "Cache lookup failed, trying to get fresh " - "data from the backend.\n"); - } - - DEBUG(SSSDBG_TRACE_ALL, "PAM initgroups scheme [%s].\n", - pam_initgroup_enum_to_string(pctx->initgroups_scheme)); - - if (ret == EOK) { - bool user_has_session = false; - - if (pctx->initgroups_scheme == PAM_INITGR_NO_SESSION) { - uid_t uid = ldb_msg_find_attr_as_uint64(result->msgs[0], - SYSDB_UIDNUM, 0); - if (!uid) { - DEBUG(SSSDBG_CRIT_FAILURE, "A user with no UID?\n"); - talloc_zfree(preq->cctx); - return; - } - - /* If a user already has a session on the system, we take the - * cache for granted and do not force an online lookup. This is - * because in most cases the user is just trying to authenticate - * but not create a new session (sudo, lockscreen, polkit, etc.) - * An online refresh in this situation would just delay operations - * without providing any useful additional information. - */ - (void)check_if_uid_is_active(uid, &user_has_session); - - DEBUG(SSSDBG_TRACE_ALL, "Found %s session for uid %"SPRIuid".\n", - user_has_session ? "a" : "no", uid); - } - - /* The initgr cache is used to make sure that during a single PAM - * session (auth, acct_mgtm, ....) the backend is contacted only - * once. logon_name is the name provided by the PAM client and - * will not be modified during the request, so it makes sense to - * use it here instead od the pd->user. - */ - ret = pam_initgr_check_timeout(pctx->id_table, preq->pd->logon_name); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_OP_FAILURE, "Could not look up initgroup timeout\n"); - } - - if ((ret == EOK) || user_has_session - || pctx->initgroups_scheme == PAM_INITGR_NEVER) { - DEBUG(SSSDBG_TRACE_ALL, "No new initgroups needed because:\n"); - if (ret == EOK) { - DEBUG(SSSDBG_TRACE_ALL, "PAM initgr cache still valid.\n"); - } else if (user_has_session) { - DEBUG(SSSDBG_TRACE_ALL, "there is a active session for " - "user [%s].\n", preq->pd->logon_name); - } else if (pctx->initgroups_scheme == PAM_INITGR_NEVER) { - DEBUG(SSSDBG_TRACE_ALL, "initgroups scheme is 'never'.\n"); - } - pam_check_user_search_done(preq, EOK, result); - return; - } - } - - /* If we get here it means the user was not found or does not have a - * session, or initgr has not been cached before, so we force a new - * online lookup */ - data = cache_req_data_name(preq, - CACHE_REQ_INITGROUPS, - preq->pd->logon_name); - if (data == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory\n"); - talloc_zfree(preq->cctx); - return; - } - cache_req_data_set_bypass_cache(data, true); - cache_req_data_set_bypass_dp(data, false); - cache_req_data_set_requested_domains(data, preq->pd->requested_domains); - - dpreq = cache_req_send(preq, - preq->cctx->rctx->ev, - preq->cctx->rctx, - preq->cctx->rctx->ncache, - 0, - preq->req_dom_type, - NULL, - data); - if (!dpreq) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Out of memory sending data provider request\n"); - talloc_zfree(preq->cctx); - return; - } - - tevent_req_set_callback(dpreq, pam_check_user_search_lookup, preq); -} - -static void pam_check_user_search_lookup(struct tevent_req *req) -{ - struct cache_req_result *result; - struct pam_auth_req *preq; - int ret; - - preq = tevent_req_callback_data(req, struct pam_auth_req); - - ret = cache_req_single_domain_recv(preq, req, &result); - talloc_zfree(req); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Fatal error, killing connection!\n"); - talloc_zfree(preq->cctx); - return; - } - - pam_check_user_search_done(preq, ret, result); -} - -static void pam_check_user_search_done(struct pam_auth_req *preq, int ret, - struct cache_req_result *result) -{ - struct pam_ctx *pctx; - - pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); - - if (ret == EOK) { - preq->user_obj = result->msgs[0]; - pd_set_primary_name(preq->user_obj, preq->pd); - preq->domain = result->domain; - - ret = pam_initgr_cache_set(pctx->rctx->ev, - pctx->id_table, - preq->pd->logon_name, - pctx->id_timeout); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Could not save initgr timestamp." - "Proceeding with PAM actions\n"); - } - - pam_dom_forwarder(preq); - } - - ret = pam_check_user_done(preq, ret); - if (ret != EOK) { - preq->pd->pam_status = PAM_SYSTEM_ERR; - pam_reply(preq); - } -} - -int pam_check_user_done(struct pam_auth_req *preq, int ret) -{ - switch (ret) { - case EOK: - break; - - case EAGAIN: - /* performing async request, just return */ - break; - - case ENOENT: - preq->pd->pam_status = PAM_USER_UNKNOWN; - pam_reply(preq); - break; - - case ERR_P11_PIN_LOCKED: - preq->pd->pam_status = PAM_AUTH_ERR; - pam_reply(preq); - break; - - case ERR_NO_CREDS: - preq->pd->pam_status = PAM_CRED_INSUFFICIENT; - pam_reply(preq); - break; - - default: - preq->pd->pam_status = PAM_SYSTEM_ERR; - pam_reply(preq); - break; - } - - return EOK; -} - -static errno_t pam_is_last_online_login_fresh(struct sss_domain_info *domain, - const char* user, - int cached_auth_timeout, - bool *_result) -{ - errno_t ret; - bool result = true; - uint64_t last_login; - - ret = pam_get_last_online_auth_with_curr_token(domain, user, &last_login); - if (ret != EOK) { - DEBUG(SSSDBG_MINOR_FAILURE, - "sysdb_get_last_online_auth_with_curr_token failed: %s:[%d]\n", - sss_strerror(ret), ret); - goto done; - } - - result = time(NULL) < (last_login + cached_auth_timeout); - ret = EOK; - -done: - if (ret == EOK) { - *_result = result; - } - return ret; -} - -static bool pam_is_authtok_cachable(struct sss_auth_token *authtok) -{ - enum sss_authtok_type type; - bool cachable = false; - - type = sss_authtok_get_type(authtok); - if (type == SSS_AUTHTOK_TYPE_PASSWORD) { - cachable = true; - } else { - DEBUG(SSSDBG_TRACE_LIBS, "Authentication token can't be cached\n"); - } - - return cachable; -} - -static bool pam_can_user_cache_auth(struct sss_domain_info *domain, - int pam_cmd, - struct sss_auth_token *authtok, - const char* user, - bool cached_auth_failed) -{ - errno_t ret; - bool result = false; - - if (cached_auth_failed) { - /* Do not retry indefinitely */ - return false; - } - - if (!domain->cache_credentials || domain->cached_auth_timeout <= 0) { - return false; - } - - if (pam_cmd == SSS_PAM_PREAUTH - || (pam_cmd == SSS_PAM_AUTHENTICATE - && pam_is_authtok_cachable(authtok))) { - - ret = pam_is_last_online_login_fresh(domain, user, - domain->cached_auth_timeout, - &result); - if (ret != EOK) { - /* non-critical, consider fail as 'non-fresh value' */ - DEBUG(SSSDBG_MINOR_FAILURE, - "pam_is_last_online_login_fresh failed: %s:[%d]\n", - sss_strerror(ret), ret); - } - } - - return result; -} - -static void pam_dom_forwarder(struct pam_auth_req *preq) -{ - TALLOC_CTX *tmp_ctx = NULL; - int ret; - struct pam_ctx *pctx = - talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); - const char *cert_user; - struct ldb_result *cert_user_objs; - bool sc_auth; - bool passkey_auth; - size_t c; - char *local_policy = NULL; - bool found = false; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - return; - } - - if (!preq->pd->domain) { - preq->pd->domain = preq->domain->name; - } - - /* Untrusted users can access only public domains. */ - if (!preq->is_uid_trusted && - !is_domain_public(preq->pd->domain, pctx->public_domains, - pctx->public_domains_count)) { - DEBUG(SSSDBG_MINOR_FAILURE, - "Untrusted user %"SPRIuid" cannot access non-public domain %s.\n", - client_euid(preq->cctx->creds), preq->pd->domain); - preq->pd->pam_status = PAM_PERM_DENIED; - pam_reply(preq); - return; - } - - /* skip this domain if not requested and the user is trusted - * as untrusted users can't request a domain */ - if (preq->is_uid_trusted && - !is_domain_requested(preq->pd, preq->pd->domain)) { - preq->pd->pam_status = PAM_USER_UNKNOWN; - pam_reply(preq); - return; - } - - if (pam_can_user_cache_auth(preq->domain, - preq->pd->cmd, - preq->pd->authtok, - preq->pd->user, - preq->cached_auth_failed)) { - preq->use_cached_auth = true; - pam_reply(preq); - return; - } - - /* Skip online auth when local auth policy = only */ -#ifdef BUILD_PASSKEY - if (may_do_cert_auth(pctx, preq->pd) || may_do_passkey_auth(pctx, preq->pd)) { -#else - if (may_do_cert_auth(pctx, preq->pd)) { -#endif /* BUILD_PASSKEY */ - if (preq->domain->name != NULL) { - ret = pam_eval_local_auth_policy(preq->cctx, pctx, preq->pd, preq, - &sc_auth, - &passkey_auth, - &local_policy); - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, - "Failed to evaluate local auth policy\n"); - preq->pd->pam_status = PAM_AUTH_ERR; - pam_reply(preq); - return; - } - } - } - - if (may_do_cert_auth(pctx, preq->pd) && preq->cert_list != NULL) { - /* Check if user matches certificate user */ - found = false; - for (preq->current_cert = preq->cert_list; - preq->current_cert != NULL; - preq->current_cert = sss_cai_get_next(preq->current_cert)) { - - cert_user_objs = sss_cai_get_cert_user_objs(preq->current_cert); - if (cert_user_objs == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "Unexpected missing certificate user, " - "trying next certificate.\n"); - continue; - } - - for (c = 0; c < cert_user_objs->count; c++) { - cert_user = ldb_msg_find_attr_as_string(cert_user_objs->msgs[c], - SYSDB_NAME, NULL); - if (cert_user == NULL) { - /* Even if there might be other users mapped to the - * certificate a missing SYSDB_NAME indicates some critical - * condition which justifies that the whole request is aborted - * */ - DEBUG(SSSDBG_CRIT_FAILURE, - "Certificate user object has no name.\n"); - preq->pd->pam_status = PAM_USER_UNKNOWN; - pam_reply(preq); - return; - } - - if (ldb_dn_compare(cert_user_objs->msgs[c]->dn, - preq->user_obj->dn) == 0) { - found = true; - if (preq->pd->cmd == SSS_PAM_PREAUTH) { - ret = sss_authtok_set_sc(preq->pd->authtok, - SSS_AUTHTOK_TYPE_SC_PIN, NULL, 0, - sss_cai_get_token_name(preq->current_cert), 0, - sss_cai_get_module_name(preq->current_cert), 0, - sss_cai_get_key_id(preq->current_cert), 0, - sss_cai_get_label(preq->current_cert), 0); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "sss_authtok_set_sc failed, Smartcard " - "authentication detection might fail in " - "the backend.\n"); - } - - ret = add_pam_cert_response(preq->pd, - preq->cctx->rctx->domains, - cert_user, - preq->current_cert, - SSS_PAM_CERT_INFO); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); - preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; - } - } - - } - } - } - - if (found) { - if (local_policy != NULL && strcasecmp(local_policy, "only") == 0) { - talloc_free(tmp_ctx); - DEBUG(SSSDBG_IMPORTANT_INFO, - "Local auth only set and matching certificate was found, " - "skipping online auth\n"); - if (preq->pd->cmd == SSS_PAM_PREAUTH) { - preq->pd->pam_status = PAM_SUCCESS; - } else if (preq->pd->cmd == SSS_PAM_AUTHENTICATE - && IS_SC_AUTHTOK(preq->pd->authtok) - && (preq->cert_auth_local - || preq->initial_cert_auth_successful)) { - preq->pd->pam_status = PAM_SUCCESS; - preq->callback = pam_reply; - } - - pam_reply(preq); - return; - } - - /* We are done if we do not have to call the backend */ - if (preq->pd->cmd == SSS_PAM_AUTHENTICATE - && preq->cert_auth_local) { - preq->pd->pam_status = PAM_SUCCESS; - preq->callback = pam_reply; - pam_reply(preq); - return; - } - } else { - if (preq->pd->cmd == SSS_PAM_PREAUTH) { - DEBUG(SSSDBG_TRACE_FUNC, - "User and certificate user do not match, " - "continue with other authentication methods.\n"); - } else { - DEBUG(SSSDBG_CRIT_FAILURE, - "User and certificate user do not match.\n"); - preq->pd->pam_status = PAM_AUTH_ERR; - pam_reply(preq); - return; - } - } - } - - if (local_policy != NULL && strcasecmp(local_policy, "only") == 0) { - talloc_free(tmp_ctx); - DEBUG(SSSDBG_IMPORTANT_INFO, "Local auth only set, skipping online auth\n"); - if (preq->pd->cmd == SSS_PAM_PREAUTH) { - preq->pd->pam_status = PAM_SUCCESS; - } else if (preq->pd->cmd == SSS_PAM_AUTHENTICATE && IS_SC_AUTHTOK(preq->pd->authtok)) { - /* Trigger offline smartcardcard autheitcation */ - preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; - } - - pam_reply(preq); - return; - } - - preq->callback = pam_reply; - ret = pam_dp_send_req(preq); - DEBUG(SSSDBG_CONF_SETTINGS, "pam_dp_send_req returned %d\n", ret); - - talloc_free(tmp_ctx); - - if (ret != EOK) { - preq->pd->pam_status = PAM_SYSTEM_ERR; - pam_reply(preq); - } -} - -static int pam_cmd_authenticate(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_authenticate\n"); - return pam_forwarder(cctx, SSS_PAM_AUTHENTICATE); -} - -static int pam_cmd_setcred(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_setcred\n"); - return pam_forwarder(cctx, SSS_PAM_SETCRED); -} - -static int pam_cmd_acct_mgmt(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_acct_mgmt\n"); - return pam_forwarder(cctx, SSS_PAM_ACCT_MGMT); -} - -static int pam_cmd_open_session(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_open_session\n"); - return pam_forwarder(cctx, SSS_PAM_OPEN_SESSION); -} - -static int pam_cmd_close_session(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_close_session\n"); - return pam_forwarder(cctx, SSS_PAM_CLOSE_SESSION); -} - -static int pam_cmd_chauthtok(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_chauthtok\n"); - return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK); -} - -static int pam_cmd_chauthtok_prelim(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_chauthtok_prelim\n"); - return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK_PRELIM); -} - -static int pam_cmd_preauth(struct cli_ctx *cctx) -{ - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_preauth\n"); - return pam_forwarder(cctx, SSS_PAM_PREAUTH); -} - -struct cli_protocol_version *register_cli_protocol_version(void) -{ - static struct cli_protocol_version pam_cli_protocol_version[] = { - {3, "2009-09-14", "make cli_pid mandatory"}, - {2, "2009-05-12", "new format "}, - {1, "2008-09-05", "initial version, \\0 terminated strings"}, - {0, NULL, NULL} - }; - - return pam_cli_protocol_version; -} - -struct sss_cmd_table *get_pam_cmds(void) -{ - static struct sss_cmd_table sss_cmds[] = { - {SSS_GET_VERSION, sss_cmd_get_version}, - {SSS_PAM_AUTHENTICATE, pam_cmd_authenticate}, - {SSS_PAM_SETCRED, pam_cmd_setcred}, - {SSS_PAM_ACCT_MGMT, pam_cmd_acct_mgmt}, - {SSS_PAM_OPEN_SESSION, pam_cmd_open_session}, - {SSS_PAM_CLOSE_SESSION, pam_cmd_close_session}, - {SSS_PAM_CHAUTHTOK, pam_cmd_chauthtok}, - {SSS_PAM_CHAUTHTOK_PRELIM, pam_cmd_chauthtok_prelim}, - {SSS_PAM_PREAUTH, pam_cmd_preauth}, - {SSS_GSSAPI_INIT, pam_cmd_gssapi_init}, - {SSS_GSSAPI_SEC_CTX, pam_cmd_gssapi_sec_ctx}, - {SSS_CLI_NULL, NULL} - }; - - return sss_cmds; -} - -errno_t -pam_set_last_online_auth_with_curr_token(struct sss_domain_info *domain, - const char *username, - uint64_t value) -{ - TALLOC_CTX *tmp_ctx; - struct sysdb_attrs *attrs; - int ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - attrs = sysdb_new_attrs(tmp_ctx); - if (attrs == NULL) { - ret = ENOMEM; - goto done; - } - - ret = sysdb_attrs_add_time_t(attrs, - SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, - value); - if (ret != EOK) { goto done; } - - ret = sysdb_set_user_attr(domain, username, attrs, SYSDB_MOD_REP); - if (ret != EOK) { goto done; } - -done: - if (ret != EOK) { - DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, sss_strerror(ret)); - } - - talloc_zfree(tmp_ctx); - return ret; -} - -static errno_t -pam_null_last_online_auth_with_curr_token(struct sss_domain_info *domain, - const char *username) -{ - return pam_set_last_online_auth_with_curr_token(domain, username, 0); -} - -static errno_t -pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain, - const char *name, - uint64_t *_value) -{ - TALLOC_CTX *tmp_ctx = NULL; - const char *attrs[] = { SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, NULL }; - struct ldb_message *ldb_msg; - uint64_t value = 0; - errno_t ret; - - if (name == NULL || *name == '\0') { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); - ret = EINVAL; - goto done; - } - - if (domain->sysdb == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing sysdb db context.\n"); - ret = EINVAL; - goto done; - } - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - ret = sysdb_search_user_by_name(tmp_ctx, domain, name, attrs, &ldb_msg); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "sysdb_search_user_by_name failed [%d][%s].\n", - ret, strerror(ret)); - goto done; - } - - /* Check offline_auth_cache_timeout */ - value = ldb_msg_find_attr_as_uint64(ldb_msg, - SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, - 0); - ret = EOK; - -done: - if (ret == EOK) { - *_value = value; - } - - talloc_free(tmp_ctx); - return ret; -} +/* + SSSD + + PAM Responder + + Copyright (C) Simo Sorce 2009 + Copyright (C) Sumit Bose 2009 + + 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 . +*/ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include + +#include "util/util.h" +#include "util/auth_utils.h" +#include "util/find_uid.h" +#include "util/sss_ptr_hash.h" +#include "db/sysdb.h" + +#include "confdb/confdb.h" +#include "responder/common/responder_packet.h" +#include "responder/common/responder.h" +#include "responder/common/negcache.h" +#include "providers/data_provider.h" +#include "responder/pam/pamsrv.h" +#include "responder/pam/pamsrv_passkey.h" +#include "responder/pam/pam_helpers.h" +#include "responder/common/cache_req/cache_req.h" + +enum pam_verbosity { + PAM_VERBOSITY_NO_MESSAGES = 0, + PAM_VERBOSITY_IMPORTANT, + PAM_VERBOSITY_INFO, + PAM_VERBOSITY_DEBUG +}; + +#define DEFAULT_PAM_VERBOSITY PAM_VERBOSITY_IMPORTANT + +struct pam_initgroup_enum_str { + enum pam_initgroups_scheme scheme; + const char *option; +}; + +struct pam_initgroup_enum_str pam_initgroup_enum_str[] = { + { PAM_INITGR_NEVER, "never" }, + { PAM_INITGR_NO_SESSION, "no_session" }, + { PAM_INITGR_ALWAYS, "always" }, + { PAM_INITGR_INVALID, NULL } +}; + +enum pam_initgroups_scheme pam_initgroups_string_to_enum(const char *str) +{ + size_t c; + + for (c = 0 ; pam_initgroup_enum_str[c].option != NULL; c++) { + if (strcasecmp(pam_initgroup_enum_str[c].option, str) == 0) { + return pam_initgroup_enum_str[c].scheme; + } + } + + return PAM_INITGR_INVALID; +} + +const char *pam_initgroup_enum_to_string(enum pam_initgroups_scheme scheme) +{ + size_t c; + + for (c = 0 ; pam_initgroup_enum_str[c].option != NULL; c++) { + if (pam_initgroup_enum_str[c].scheme == scheme) { + return pam_initgroup_enum_str[c].option; + } + } + + return "(NULL)"; +} + +static errno_t +pam_null_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *username); +static errno_t +pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *name, + uint64_t *_value); + +void pam_reply(struct pam_auth_req *preq); + +static errno_t check_cert(TALLOC_CTX *mctx, + struct tevent_context *ev, + struct pam_ctx *pctx, + struct pam_auth_req *preq, + struct pam_data *pd); + +int pam_check_user_done(struct pam_auth_req *preq, int ret); + +static errno_t pack_user_info_msg(TALLOC_CTX *mem_ctx, + const char *user_error_message, + size_t *resp_len, + uint8_t **_resp) +{ + uint32_t resp_type = SSS_PAM_USER_INFO_ACCOUNT_EXPIRED; + size_t err_len; + uint8_t *resp; + size_t p; + + err_len = strlen(user_error_message); + *resp_len = 2 * sizeof(uint32_t) + err_len; + resp = talloc_size(mem_ctx, *resp_len); + if (resp == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + p = 0; + SAFEALIGN_SET_UINT32(&resp[p], resp_type, &p); + SAFEALIGN_SET_UINT32(&resp[p], err_len, &p); + safealign_memcpy(&resp[p], user_error_message, err_len, &p); + if (p != *resp_len) { + DEBUG(SSSDBG_FATAL_FAILURE, "Size mismatch\n"); + } + + *_resp = resp; + return EOK; +} + +static void inform_user(struct pam_data* pd, const char *pam_message) +{ + size_t msg_len; + uint8_t *msg; + errno_t ret; + + ret = pack_user_info_msg(pd, pam_message, &msg_len, &msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "pack_user_info_msg failed.\n"); + } else { + ret = pam_add_response(pd, SSS_PAM_USER_INFO, msg_len, msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + } +} + +static bool is_domain_requested(struct pam_data *pd, const char *domain_name) +{ + int i; + + /* If none specific domains got requested via pam, all domains are allowed. + * Which mimics the default/original behaviour. + */ + if (!pd->requested_domains) { + return true; + } + + for (i = 0; pd->requested_domains[i]; i++) { + if (strcasecmp(domain_name, pd->requested_domains[i])) { + continue; + } + + return true; + } + + return false; +} + +static int extract_authtok_v2(struct sss_auth_token *tok, + size_t data_size, uint8_t *body, size_t blen, + size_t *c) +{ + uint32_t auth_token_type; + uint32_t auth_token_length; + uint8_t *auth_token_data; + int ret = EOK; + + if (data_size < sizeof(uint32_t) || *c+data_size > blen || + SIZE_T_OVERFLOW(*c, data_size)) return EINVAL; + + SAFEALIGN_COPY_UINT32_CHECK(&auth_token_type, &body[*c], blen, c); + auth_token_length = data_size - sizeof(uint32_t); + auth_token_data = body+(*c); + + switch (auth_token_type) { + case SSS_AUTHTOK_TYPE_EMPTY: + sss_authtok_set_empty(tok); + break; + case SSS_AUTHTOK_TYPE_PASSWORD: + if (auth_token_length == 0) { + sss_authtok_set_empty(tok); + } else { + ret = sss_authtok_set_password(tok, (const char *)auth_token_data, + auth_token_length); + } + break; + case SSS_AUTHTOK_TYPE_2FA: + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + case SSS_AUTHTOK_TYPE_SC_PIN: + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_OAUTH2: + case SSS_AUTHTOK_TYPE_PASSKEY: + case SSS_AUTHTOK_TYPE_PASSKEY_KRB: + case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: + case SSS_AUTHTOK_TYPE_PAM_STACKED: + ret = sss_authtok_set(tok, auth_token_type, + auth_token_data, auth_token_length); + break; + default: + return EINVAL; + } + + *c += auth_token_length; + + return ret; +} + +static int extract_string(char **var, size_t size, uint8_t *body, size_t blen, + size_t *c) { + uint8_t *str; + + if (*c+size > blen || SIZE_T_OVERFLOW(*c, size)) return EINVAL; + + str = body+(*c); + + if (str[size-1]!='\0') return EINVAL; + + /* If the string isn't valid UTF-8, fail */ + if (!sss_utf8_check(str, size-1)) { + return EINVAL; + } + + *c += size; + + *var = (char *) str; + + return EOK; +} + +static int extract_uint32_t(uint32_t *var, size_t size, uint8_t *body, + size_t blen, size_t *c) { + + if (size != sizeof(uint32_t) || *c+size > blen || SIZE_T_OVERFLOW(*c, size)) + return EINVAL; + + SAFEALIGN_COPY_UINT32_CHECK(var, &body[*c], blen, c); + + return EOK; +} + +static int pd_set_primary_name(const struct ldb_message *msg,struct pam_data *pd) +{ + const char *name; + + name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + if (!name) { + DEBUG(SSSDBG_CRIT_FAILURE, "A user with no name?\n"); + return EIO; + } + + if (strcmp(pd->user, name)) { + DEBUG(SSSDBG_TRACE_FUNC, "User's primary name is %s\n", name); + talloc_free(pd->user); + pd->user = talloc_strdup(pd, name); + if (!pd->user) return ENOMEM; + } + + return EOK; +} + +static int pam_parse_in_data_v2(struct pam_data *pd, + uint8_t *body, size_t blen) +{ + size_t c; + uint32_t type; + uint32_t size; + int ret; + uint32_t start; + uint32_t terminator; + char *requested_domains; + + if (blen < 4*sizeof(uint32_t)+2) { + DEBUG(SSSDBG_CRIT_FAILURE, "Received data is invalid.\n"); + return EINVAL; + } + + SAFEALIGN_COPY_UINT32(&start, body, NULL); + SAFEALIGN_COPY_UINT32(&terminator, body + blen - sizeof(uint32_t), NULL); + + if (start != SSS_START_OF_PAM_REQUEST + || terminator != SSS_END_OF_PAM_REQUEST) { + DEBUG(SSSDBG_CRIT_FAILURE, "Received data is invalid.\n"); + return EINVAL; + } + + c = sizeof(uint32_t); + do { + SAFEALIGN_COPY_UINT32_CHECK(&type, &body[c], blen, &c); + + if (type == SSS_END_OF_PAM_REQUEST) { + if (c != blen) return EINVAL; + } else { + SAFEALIGN_COPY_UINT32_CHECK(&size, &body[c], blen, &c); + /* the uint32_t end maker SSS_END_OF_PAM_REQUEST does not count to + * the remaining buffer */ + if (size > (blen - c - sizeof(uint32_t))) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid data size.\n"); + return EINVAL; + } + + switch(type) { + case SSS_PAM_ITEM_USER: + ret = extract_string(&pd->logon_name, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_SERVICE: + ret = extract_string(&pd->service, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_TTY: + ret = extract_string(&pd->tty, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_RUSER: + ret = extract_string(&pd->ruser, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_RHOST: + ret = extract_string(&pd->rhost, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_REQUESTED_DOMAINS: + ret = extract_string(&requested_domains, size, body, blen, + &c); + if (ret != EOK) return ret; + + ret = split_on_separator(pd, requested_domains, ',', true, + true, &pd->requested_domains, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse requested_domains list!\n"); + return ret; + } + break; + case SSS_PAM_ITEM_CLI_PID: + ret = extract_uint32_t(&pd->cli_pid, size, + body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_CHILD_PID: + /* This is optional. */ + ret = extract_uint32_t(&pd->child_pid, size, + body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_AUTHTOK: + ret = extract_authtok_v2(pd->authtok, + size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_NEWAUTHTOK: + ret = extract_authtok_v2(pd->newauthtok, + size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_FLAGS: + ret = extract_uint32_t(&pd->cli_flags, size, + body, blen, &c); + if (ret != EOK) return ret; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Ignoring unknown data type [%d].\n", type); + c += size; + } + } + + } while(c < blen); + + return EOK; + +} + +static int pam_parse_in_data_v3(struct pam_data *pd, + uint8_t *body, size_t blen) +{ + int ret; + + ret = pam_parse_in_data_v2(pd, body, blen); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_parse_in_data_v2 failed.\n"); + return ret; + } + + if (pd->cli_pid == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing client PID.\n"); + return EINVAL; + } + + return EOK; +} + +static int extract_authtok_v1(struct sss_auth_token *tok, + uint8_t *body, size_t blen, size_t *c) +{ + uint32_t auth_token_type; + uint32_t auth_token_length; + uint8_t *auth_token_data; + int ret = EOK; + + SAFEALIGN_COPY_UINT32_CHECK(&auth_token_type, &body[*c], blen, c); + SAFEALIGN_COPY_UINT32_CHECK(&auth_token_length, &body[*c], blen, c); + auth_token_data = body+(*c); + + switch (auth_token_type) { + case SSS_AUTHTOK_TYPE_EMPTY: + sss_authtok_set_empty(tok); + break; + case SSS_AUTHTOK_TYPE_PASSWORD: + ret = sss_authtok_set_password(tok, (const char *)auth_token_data, + auth_token_length); + break; + default: + return EINVAL; + } + + *c += auth_token_length; + + return ret; +} + +static int pam_parse_in_data(struct pam_data *pd, + uint8_t *body, size_t blen) +{ + size_t start; + size_t end; + size_t last; + int ret; + + last = blen - 1; + end = 0; + + /* user name */ + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->logon_name = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->service = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->tty = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->ruser = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->rhost = (char *) &body[start]; + + ret = extract_authtok_v1(pd->authtok, body, blen, &end); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid auth token\n"); + return ret; + } + ret = extract_authtok_v1(pd->newauthtok, body, blen, &end); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid new auth token\n"); + return ret; + } + + DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); + + return EOK; +} + +static errno_t +pam_get_local_auth_policy(struct sss_domain_info *domain, + const char *name, + bool *_sc_allow, + bool *_passkey_allow) +{ + TALLOC_CTX *tmp_ctx = NULL; + const char *attrs[] = { SYSDB_LOCAL_SMARTCARD_AUTH, SYSDB_LOCAL_PASSKEY_AUTH, NULL }; + struct ldb_message *ldb_msg; + bool sc_allow = false; + bool passkey_allow = false; + errno_t ret; + + if (name == NULL || *name == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); + ret = EINVAL; + goto done; + } + + if (domain->sysdb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing sysdb db context.\n"); + ret = EINVAL; + goto done; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_user_by_name(tmp_ctx, domain, name, attrs, &ldb_msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_search_user_by_name failed [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + + sc_allow = ldb_msg_find_attr_as_bool(ldb_msg, SYSDB_LOCAL_SMARTCARD_AUTH, + false); + + passkey_allow = ldb_msg_find_attr_as_bool(ldb_msg, SYSDB_LOCAL_PASSKEY_AUTH, + true); + + ret = EOK; + +done: + if (ret == EOK) { + *_sc_allow = sc_allow; + *_passkey_allow = passkey_allow; + } + + talloc_free(tmp_ctx); + return ret; +} +static errno_t set_local_auth_type(struct pam_auth_req *preq, + bool sc_allow, + bool passkey_allow) +{ + struct sysdb_attrs *attrs; + errno_t ret; + + attrs = sysdb_new_attrs(preq); + if (!attrs) { + ret = ENOMEM; + goto fail; + } + + if (sc_allow) { + /* Only set SYSDB_LOCAL_SMARTCARD_AUTH to 'true' but never to + * 'false'. The krb5 backend will only returns that Smartcard + * authentication is available if a Smartcard is present. That means + * if the user authenticates with a different method and a Smartcard + * is not present at this time 'sc_allow' will be 'false' and might + * overwrite a 'true' value written during a previous authentication + * attempt where a Smartcard was present. To avoid this we only write + * 'true' values. Since the default if SYSDB_LOCAL_SMARTCARD_AUTH is + * missing is 'false' local Smartcard authentication (offline) will + * still only be enabled if online Smartcard authentication was + * detected. */ + ret = sysdb_attrs_add_bool(attrs, SYSDB_LOCAL_SMARTCARD_AUTH, sc_allow); + if (ret != EOK) { + goto fail; + } + } + + ret = sysdb_attrs_add_bool(attrs, SYSDB_LOCAL_PASSKEY_AUTH, passkey_allow); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_set_user_attr(preq->domain, preq->pd->user, attrs, + SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "set_local_auth_type failed.\n"); + preq->pd->pam_status = PAM_SYSTEM_ERR; + goto fail; + } + + return EOK; + +fail: + return ret; +} +/*=Save-Last-Login-State===================================================*/ + +static errno_t set_last_login(struct pam_auth_req *preq) +{ + struct sysdb_attrs *attrs; + errno_t ret; + + attrs = sysdb_new_attrs(preq); + if (!attrs) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_ONLINE_AUTH, time(NULL)); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_attrs_add_time_t(attrs, + SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, + time(NULL)); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_LOGIN, time(NULL)); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_set_user_attr(preq->domain, preq->pd->user, attrs, + SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "set_last_login failed.\n"); + preq->pd->pam_status = PAM_SYSTEM_ERR; + goto fail; + } else { + preq->pd->last_auth_saved = true; + } + preq->callback(preq); + + return EOK; + +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 ? var_name : "(NULL)"), + (service ? service : "(NULL)")); + + 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 pam_ctx *pctx, + struct response_data *resp_list, + struct pam_data *pd) +{ + int ret; + struct response_data *resp; + uint32_t user_info_type; + int64_t expire_date = 0; + int pam_verbosity = DEFAULT_PAM_VERBOSITY; + char **new_opts; + size_t c; + const char *default_pam_response_filter[] = { "ENV:KRB5CCNAME:sudo", + "ENV:KRB5CCNAME:sudo-i", + NULL }; + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_VERBOSITY, DEFAULT_PAM_VERBOSITY, + &pam_verbosity); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read PAM verbosity, not fatal.\n"); + pam_verbosity = DEFAULT_PAM_VERBOSITY; + } + + if (pctx->pam_filter_opts == NULL) { + ret = confdb_get_string_as_list(pctx->rctx->cdb, pctx, + CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_RESPONSE_FILTER, + &pctx->pam_filter_opts); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read values of [%s], not fatal.\n", + CONFDB_PAM_RESPONSE_FILTER); + pctx->pam_filter_opts = NULL; + } else { + if (pctx->pam_filter_opts == NULL + || *pctx->pam_filter_opts[0] == '+' + || *pctx->pam_filter_opts[0] == '-') { + ret = mod_defaults_list(pctx, default_pam_response_filter, + pctx->pam_filter_opts, &new_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to modify [%s] defaults.\n", + CONFDB_PAM_RESPONSE_FILTER); + return ret; + } + talloc_free(pctx->pam_filter_opts); + pctx->pam_filter_opts = new_opts; + } + } + + if (pctx->pam_filter_opts == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "No PAM response filter set.\n"); + } else { + /* Make sure there are no '+' or '-' prefixes anymore */ + for (c = 0; pctx->pam_filter_opts[c] != NULL; c++) { + if (*pctx->pam_filter_opts[0] == '+' + || *pctx->pam_filter_opts[0] == '-') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unsupport mix of prefixed and not prefixed " + "values of [%s].\n", CONFDB_PAM_RESPONSE_FILTER); + return EINVAL; + } + DEBUG(SSSDBG_CONF_SETTINGS, + "PAM response filter: [%s].\n", + pctx->pam_filter_opts[c]); + } + } + } + + 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"); + ret = EINVAL; + goto done; + } + + if (pam_verbosity == PAM_VERBOSITY_NO_MESSAGES) { + resp->do_not_send_to_client = true; + resp = resp->next; + continue; + } + + memcpy(&user_info_type, resp->data, sizeof(uint32_t)); + + resp->do_not_send_to_client = false; + switch (user_info_type) { + case SSS_PAM_USER_INFO_OFFLINE_AUTH: + if (resp->len != sizeof(uint32_t) + sizeof(int64_t)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "User info offline auth entry is " + "too short.\n"); + ret = EINVAL; + goto done; + } + memcpy(&expire_date, resp->data + sizeof(uint32_t), + sizeof(int64_t)); + if ((expire_date == 0 && + pam_verbosity < PAM_VERBOSITY_INFO) || + (expire_date > 0 && + pam_verbosity < PAM_VERBOSITY_IMPORTANT)) { + resp->do_not_send_to_client = true; + } + + break; + default: + DEBUG(SSSDBG_TRACE_LIBS, + "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, pctx->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; + } + + resp = resp->next; + } + + ret = EOK; +done: + + return ret; +} + +static void do_not_send_cert_info(struct pam_data *pd) +{ + struct response_data *resp; + + resp = pd->resp_list; + while (resp != NULL) { + switch (resp->type) { + case SSS_PAM_CERT_INFO: + case SSS_PAM_CERT_INFO_WITH_HINT: + resp->do_not_send_to_client = true; + break; + default: + break; + } + resp = resp->next; + } +} + +static void evaluate_pam_resp_list(struct pam_data *pd, + struct pam_resp_auth_type *_auth_types, + bool *_found_cert_info) +{ + struct response_data *resp; + struct pam_resp_auth_type types = {0}; + bool found_cert_info = false; + + resp = pd->resp_list; + while (resp != NULL) { + switch (resp->type) { + case SSS_PAM_OTP_INFO: + types.otp_auth = true; + break; + case SSS_PAM_CERT_INFO: + case SSS_PAM_CERT_INFO_WITH_HINT: + found_cert_info = true; + break; + case SSS_PAM_PASSKEY_INFO: + case SSS_PAM_PASSKEY_KRB_INFO: + types.passkey_auth = true; + break; + case SSS_PASSWORD_PROMPTING: + types.password_auth = true; + break; + case SSS_CERT_AUTH_PROMPTING: + types.cert_auth = true; + break; + default: + break; + } + resp = resp->next; + } + + if (_auth_types != NULL) { + *_auth_types = types; + } + if (_found_cert_info != NULL) { + *_found_cert_info = found_cert_info; + } +} + +static void evalute_sending_cert_info(struct pam_data *pd) +{ + struct pam_resp_auth_type types = {0}; + bool found_cert_info = false; + + evaluate_pam_resp_list(pd, &types, &found_cert_info); + + if (found_cert_info && !types.cert_auth) { + do_not_send_cert_info(pd); + } +} + +errno_t pam_get_auth_types(struct pam_data *pd, + struct pam_resp_auth_type *_auth_types) +{ + int ret; + struct pam_resp_auth_type types = {0}; + + evaluate_pam_resp_list(pd, &types, NULL); + + if (!types.password_auth && !types.otp_auth && !types.cert_auth && !types.passkey_auth) { + /* If the backend cannot determine which authentication types are + * available the default would be to prompt for a password. */ + types.password_auth = true; + types.backend_returned_no_auth_type = true; + } + + DEBUG(SSSDBG_TRACE_ALL, "Authentication types for user [%s] and service " + "[%s]:%s%s%s%s\n", pd->user, pd->service, + types.password_auth ? " password": "", + types.otp_auth ? " two-factor" : "", + types.passkey_auth ? " passkey" : "", + types.cert_auth ? " smartcard" : ""); + + ret = EOK; + + *_auth_types = types; + + return ret; +} + +static errno_t pam_eval_local_auth_policy(TALLOC_CTX *mem_ctx, + struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq, + bool *_sc_allow, + bool *_passkey_allow, + char **_local_policy) { + + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char *domain_cdb; + char *local_policy = NULL; + bool sc_allow = false; + bool passkey_allow = false; + struct pam_resp_auth_type auth_types; + char **opts; + size_t c; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Check local auth policy */ + domain_cdb = talloc_asprintf(tmp_ctx, CONFDB_DOMAIN_PATH_TMPL, preq->domain->name); + if (domain_cdb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = confdb_get_string(pctx->rctx->cdb, tmp_ctx, domain_cdb, + CONFDB_DOMAIN_LOCAL_AUTH_POLICY, + "match", &local_policy); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get the confdb local_auth_policy\n"); + return ret; + } + + /* "only" ignores online methods and allows all local ones */ + if (strcasecmp(local_policy, "only") == 0) { + sc_allow = true; + passkey_allow = true; + /* Match what the KDC supports and provides */ + } else if (strcasecmp(local_policy, "match") == 0) { + /* Don't overwrite the local auth type when offline */ + if (pd->pam_status == PAM_SUCCESS && pd->cmd == SSS_PAM_PREAUTH && + !is_domain_provider(preq->domain, "ldap")) { + ret = pam_get_auth_types(pd, &auth_types); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get authentication types\n"); + goto done; + } + + if (auth_types.cert_auth) { + sc_allow = true; + } else if (auth_types.passkey_auth) { + passkey_allow = true; + } + + /* Store the local auth types, in case we go offline */ + if (!auth_types.backend_returned_no_auth_type) { + ret = set_local_auth_type(preq, sc_allow, passkey_allow); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to evaluate local auth policy\n"); + goto done; + } + } + } + + /* Read the latest auth types */ + ret = pam_get_local_auth_policy(preq->domain, preq->pd->user, + &sc_allow, &passkey_allow); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get PAM local auth policy\n"); + goto done; + } + /* Check for enable */ + } else { + ret = split_on_separator(tmp_ctx, local_policy, ',', true, true, &opts, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d], %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + for (c = 0; opts[c] != NULL; c++) { + if (strcasestr(opts[c], "passkey") != NULL) { + passkey_allow = strstr(opts[c], "enable") ? true : false; + } else if (strcasestr(opts[c], "smartcard") != NULL) { + sc_allow = strstr(opts[c], "enable") ? true : false; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unexpected local auth policy option [%s], " \ + "skipping.\n", opts[c]); + } + } + } + + /* if passkey is enabled but local Smartcard authentication is not but + * possible, the cert info data has to be remove as well if only local + * Smartcard authentication is possible. If Smartcard authentication + * is possible on the server side we have to keep it because the + * 'enable' option should only add local methods but not reject remote + * ones. */ + if (!sc_allow) { + evalute_sending_cert_info(pd); + } + + *_sc_allow = sc_allow; + *_passkey_allow = passkey_allow; + *_local_policy = talloc_steal(mem_ctx, local_policy); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static void pam_reply_delay(struct tevent_context *ev, struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct pam_auth_req *preq; + + DEBUG(SSSDBG_CONF_SETTINGS, "pam_reply_delay get called.\n"); + + preq = talloc_get_type(pvt, struct pam_auth_req); + + pam_reply(preq); +} + +static errno_t get_password_for_cache_auth(struct sss_auth_token *authtok, + const char **password) +{ + int ret; + size_t pw_len; + const char *fa2; + size_t fa2_len; + + switch (sss_authtok_get_type(authtok)) { + case SSS_AUTHTOK_TYPE_PASSWORD: + ret = sss_authtok_get_password(authtok, password, NULL); + break; + case SSS_AUTHTOK_TYPE_2FA: + ret = sss_authtok_get_2fa(authtok, password, &pw_len, &fa2, &fa2_len); + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, "Unsupported auth token type [%d].\n", + sss_authtok_get_type(authtok)); + ret = EINVAL; + } + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get password.\n"); + return ret; + } + + return EOK; +} + +static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd); +static void pam_handle_cached_login(struct pam_auth_req *preq, int ret, + time_t expire_date, time_t delayed_until, bool cached_auth); + +/* + * Add a request to add a variable to the PAM user environment, containing the + * actual (not overridden) user shell, in case session recording is enabled. + */ +static int pam_reply_sr_export_shell(struct pam_auth_req *preq, + const char *var_name) +{ + int ret; + TALLOC_CTX *ctx = NULL; + bool enabled; + const char *enabled_str; + const char *shell; + char *buf; + + /* Create temporary talloc context */ + ctx = talloc_new(NULL); + if (ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + ret = ENOMEM; + goto done; + } + + /* Check if session recording is enabled */ + if (preq->cctx->rctx->sr_conf.scope == + SESSION_RECORDING_SCOPE_NONE) { + enabled = false; + } else { + enabled_str = ldb_msg_find_attr_as_string(preq->user_obj, + SYSDB_SESSION_RECORDING, NULL); + if (enabled_str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "%s attribute not found\n", SYSDB_SESSION_RECORDING); + ret = ENOENT; + goto done; + } else if (strcmp(enabled_str, "TRUE") == 0) { + enabled = true; + } else if (strcmp(enabled_str, "FALSE") == 0) { + enabled = false; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "invalid value of %s attribute: %s\n", + SYSDB_SESSION_RECORDING, enabled_str); + ret = ENOENT; + goto done; + } + } + + /* Export original shell if recording is enabled and so it's overridden */ + if (enabled) { + /* Extract the shell */ + shell = sss_resp_get_shell_override(preq->user_obj, + preq->cctx->rctx, preq->domain); + if (shell == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "user has no shell\n"); + ret = ENOENT; + goto done; + } + + /* Format environment entry */ + buf = talloc_asprintf(ctx, "%s=%s", var_name, shell); + if (buf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + /* Add request to add the entry to user environment */ + ret = pam_add_response(preq->pd, SSS_PAM_ENV_ITEM, + strlen(buf) + 1, (uint8_t *)buf); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(ctx); + return ret; +} + +void pam_reply(struct pam_auth_req *preq) +{ + struct cli_ctx *cctx; + struct cli_protocol *prctx; + uint8_t *body; + size_t blen; + int ret; + int32_t resp_c; + int32_t resp_size; + struct response_data *resp; + int p; + struct timeval tv; + struct tevent_timer *te; + struct pam_data *pd; + char *local_policy = NULL; + struct pam_ctx *pctx; + uint32_t user_info_type; + time_t exp_date = -1; + time_t delay_until = -1; + char* pam_account_expired_message; + char* pam_account_locked_message; + int pam_verbosity; + bool local_sc_auth_allow = false; + bool local_passkey_auth_allow = false; +#ifdef BUILD_PASSKEY + bool pk_preauth_done = false; +#endif /* BUILD_PASSKEY */ + + pd = preq->pd; + cctx = preq->cctx; + pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + prctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_VERBOSITY, DEFAULT_PAM_VERBOSITY, + &pam_verbosity); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read PAM verbosity, not fatal.\n"); + pam_verbosity = DEFAULT_PAM_VERBOSITY; + } + + DEBUG(SSSDBG_TRACE_ALL, + "pam_reply initially called with result [%d]: %s. " + "this result might be changed during processing\n", + pd->pam_status, pam_strerror(NULL, pd->pam_status)); + + if (preq->domain != NULL && preq->domain->name != NULL) { + ret = pam_eval_local_auth_policy(cctx, pctx, pd, preq, + &local_sc_auth_allow, + &local_passkey_auth_allow, + &local_policy); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to evaluate local auth policy\n"); + goto done; + } + } + + /* Ignore local_auth_policy for the files provider, allow local + * smartcard auth (default behavior prior to local_auth_policy) */ + if (is_domain_provider(preq->domain, "files")) { + local_sc_auth_allow = true; + /* For the ldap auth provider we currently only support + * password based authentication */ + } else if (is_domain_provider(preq->domain, "ldap") && local_policy != NULL + && strcasecmp(local_policy, "match") == 0) { + local_passkey_auth_allow = false; + local_sc_auth_allow = false; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Local auth policy allowed: smartcard [%s], passkey [%s]\n", + local_sc_auth_allow ? "True" : "False", + local_passkey_auth_allow ? "True" : "False"); + + if (pd->cmd == SSS_PAM_AUTHENTICATE + && !preq->cert_auth_local + && (pd->pam_status == PAM_AUTHINFO_UNAVAIL + || pd->pam_status == PAM_NO_MODULE_DATA + || pd->pam_status == PAM_BAD_ITEM) + && may_do_cert_auth(pctx, pd)) { + /* We have Smartcard credentials and the backend indicates that it is + * offline (PAM_AUTHINFO_UNAVAIL) or cannot handle the credentials + * (PAM_BAD_ITEM), so let's try authentication against the Smartcard + * PAM_NO_MODULE_DATA is returned by the krb5 backend if no + * authentication method was found at all, this might happen if the + * user has a Smartcard assigned but the pkint plugin is not available + * on the client. */ + DEBUG(SSSDBG_IMPORTANT_INFO, + "Backend cannot handle Smartcard authentication, " + "trying local Smartcard authentication.\n"); + if (local_sc_auth_allow) { + preq->cert_auth_local = true; + ret = check_cert(cctx, cctx->ev, pctx, preq, pd); + pam_check_user_done(preq, ret); + return; + } else { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Local smartcard auth not allowed by local_auth_policy"); + } + } + + if (pd->pam_status == PAM_AUTHINFO_UNAVAIL || preq->use_cached_auth) { + + switch(pd->cmd) { + case SSS_PAM_AUTHENTICATE: + if ((preq->domain != NULL) && + (preq->domain->cache_credentials == true) && + (pd->offline_auth == false)) { + const char *password = NULL; + bool use_cached_auth; + + /* backup value of preq->use_cached_auth*/ + use_cached_auth = preq->use_cached_auth; + /* set to false to avoid entering this branch when pam_reply() + * is recursively called from pam_handle_cached_login() */ + preq->use_cached_auth = false; + + /* do auth with offline credentials */ + pd->offline_auth = true; + + if (preq->domain->sysdb == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Fatal: Sysdb CTX not found for domain" + " [%s]!\n", preq->domain->name); + goto done; + } + + ret = get_password_for_cache_auth(pd->authtok, &password); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "get_password_and_type_for_cache_auth failed.\n"); + goto done; + } + + ret = sysdb_cache_auth(preq->domain, + pd->user, password, + pctx->rctx->cdb, false, + &exp_date, &delay_until); + + pam_handle_cached_login(preq, ret, exp_date, delay_until, + use_cached_auth); + return; + } + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + case SSS_PAM_CHAUTHTOK: + DEBUG(SSSDBG_FUNC_DATA, + "Password change not possible while offline.\n"); + pd->pam_status = PAM_AUTHTOK_ERR; + user_info_type = SSS_PAM_USER_INFO_OFFLINE_CHPASS; + ret = pam_add_response(pd, SSS_PAM_USER_INFO, sizeof(uint32_t), + (const uint8_t *) &user_info_type); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + break; +/* TODO: we need the pam session cookie here to make sure that cached + * authentication was successful */ + case SSS_PAM_PREAUTH: + case SSS_PAM_SETCRED: + case SSS_PAM_ACCT_MGMT: + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_CLOSE_SESSION: + DEBUG(SSSDBG_OP_FAILURE, + "Assuming offline authentication setting status for " + "pam call %d to PAM_SUCCESS.\n", pd->cmd); + pd->pam_status = PAM_SUCCESS; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown PAM call [%d].\n", pd->cmd); + pd->pam_status = PAM_MODULE_UNKNOWN; + } + } + + if (pd->pam_status == PAM_SUCCESS && pd->cmd == SSS_PAM_CHAUTHTOK) { + ret = pam_null_last_online_auth_with_curr_token(preq->domain, + pd->user); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_null_last_online_auth_with_curr_token failed: " + "%s [%d].\n", sss_strerror(ret), ret); + goto done; + } + } + + if (pd->response_delay > 0) { + ret = gettimeofday(&tv, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "gettimeofday failed [%d][%s].\n", + errno, strerror(errno)); + goto done; + } + tv.tv_sec += pd->response_delay; + tv.tv_usec = 0; + pd->response_delay = 0; + + te = tevent_add_timer(cctx->ev, cctx, tv, pam_reply_delay, preq); + if (te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add event pam_reply_delay.\n"); + goto done; + } + + return; + } + + /* If this was a successful login, save the lastLogin time */ + if (pd->cmd == SSS_PAM_AUTHENTICATE && + pd->pam_status == PAM_SUCCESS && + preq->domain && + preq->domain->cache_credentials && + !pd->offline_auth && + !pd->last_auth_saved) { + ret = set_last_login(preq); + if (ret != EOK) { + goto done; + } + return; + } + + ret = sss_packet_new(prctx->creq, 0, sss_packet_get_cmd(prctx->creq->in), + &prctx->creq->out); + if (ret != EOK) { + goto done; + } + +#ifdef BUILD_PASSKEY + if(pd->cmd == SSS_PAM_AUTHENTICATE && + pd->pam_status == PAM_NEW_AUTHTOK_REQD && + sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_REPLY) { + DEBUG(SSSDBG_TRACE_FUNC, "Passkey authentication reply, ignoring " + "new authtok required status\n"); + pd->pam_status = PAM_SUCCESS; + } + + /* Passkey auth user notification if no TGT is granted */ + if (pd->cmd == SSS_PAM_AUTHENTICATE && + pd->pam_status == PAM_SUCCESS && + preq->pd->passkey_local_done) { + user_info_type = SSS_PAM_USER_INFO_NO_KRB_TGT; + pam_add_response(pd, SSS_PAM_USER_INFO, + sizeof(uint32_t), (const uint8_t *) &user_info_type); + DEBUG(SSSDBG_IMPORTANT_INFO, + "User [%s] logged in with local passkey authentication, single " + "sign on ticket is not obtained.\n", pd->user); + } +#endif /* BUILD_PASSKEY */ + + /* Account expiration warning is printed for sshd. If pam_verbosity + * is equal or above PAM_VERBOSITY_INFO then all services are informed + * about account expiration. + */ + if (pd->pam_status == PAM_ACCT_EXPIRED && + ((pd->service != NULL && strcasecmp(pd->service, "sshd") == 0) || + pam_verbosity >= PAM_VERBOSITY_INFO)) { + + ret = confdb_get_string(pctx->rctx->cdb, pd, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_ACCOUNT_EXPIRED_MESSAGE, "", + &pam_account_expired_message); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to get expiration message: %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + inform_user(pd, pam_account_expired_message); + } + + if (pd->account_locked) { + + ret = confdb_get_string(pctx->rctx->cdb, pd, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_ACCOUNT_LOCKED_MESSAGE, "", + &pam_account_locked_message); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to get expiration message: %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + inform_user(pd, pam_account_locked_message); + } + + ret = filter_responses(pctx, pd->resp_list, pd); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "filter_responses failed, not fatal.\n"); + } + + if (pd->domain != NULL) { + ret = pam_add_response(pd, SSS_PAM_DOMAIN_NAME, strlen(pd->domain)+1, + (uint8_t *) pd->domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + } + + 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"); + } + +#ifdef BUILD_PASSKEY + ret = pam_eval_passkey_response(pctx, pd, preq, &pk_preauth_done); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to eval passkey response\n"); + goto done; + } + + if (may_do_passkey_auth(pctx, pd) + && !pk_preauth_done + && preq->passkey_data_exists + && local_passkey_auth_allow) { + ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); + pam_check_user_done(preq, ret); + return; + } +#endif /* BUILD_PASSKEY */ + } + + /* + * Export non-overridden shell to tlog-rec-session when opening the session + */ + if (pd->cmd == SSS_PAM_OPEN_SESSION && pd->pam_status == PAM_SUCCESS) { + ret = pam_reply_sr_export_shell(preq, "TLOG_REC_SESSION_SHELL"); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "failed to export the shell to tlog-rec-session.\n"); + goto done; + } + } + + resp_c = 0; + resp_size = 0; + resp = pd->resp_list; + while(resp != NULL) { + if (!resp->do_not_send_to_client) { + resp_c++; + resp_size += resp->len; + } + resp = resp->next; + } + + ret = sss_packet_grow(prctx->creq->out, sizeof(int32_t) + + sizeof(int32_t) + + resp_c * 2* sizeof(int32_t) + + resp_size); + if (ret != EOK) { + goto done; + } + + sss_packet_get_body(prctx->creq->out, &body, &blen); + DEBUG(SSSDBG_FUNC_DATA, "blen: %zu\n", blen); + p = 0; + + memcpy(&body[p], &pd->pam_status, sizeof(int32_t)); + p += sizeof(int32_t); + + memcpy(&body[p], &resp_c, sizeof(int32_t)); + p += sizeof(int32_t); + + resp = pd->resp_list; + while(resp != NULL) { + if (!resp->do_not_send_to_client) { + memcpy(&body[p], &resp->type, sizeof(int32_t)); + p += sizeof(int32_t); + memcpy(&body[p], &resp->len, sizeof(int32_t)); + p += sizeof(int32_t); + memcpy(&body[p], resp->data, resp->len); + p += resp->len; + } + + resp = resp->next; + } + +done: + DEBUG(SSSDBG_FUNC_DATA, "Returning [%d]: %s to the client\n", + pd->pam_status, pam_strerror(NULL, pd->pam_status)); + sss_cmd_done(cctx, preq); +} + +static void pam_dom_forwarder(struct pam_auth_req *preq); + +static void pam_handle_cached_login(struct pam_auth_req *preq, int ret, + time_t expire_date, time_t delayed_until, + bool use_cached_auth) +{ + uint32_t resp_type; + size_t resp_len; + uint8_t *resp; + int64_t dummy; + + preq->pd->pam_status = cached_login_pam_status(ret); + + switch (preq->pd->pam_status) { + case PAM_SUCCESS: + resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH; + resp_len = sizeof(uint32_t) + sizeof(int64_t); + resp = talloc_size(preq->pd, resp_len); + if (resp == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_size failed, cannot prepare user info.\n"); + } else { + memcpy(resp, &resp_type, sizeof(uint32_t)); + dummy = (int64_t) expire_date; + memcpy(resp+sizeof(uint32_t), &dummy, sizeof(int64_t)); + ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, + (const uint8_t *) resp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + } + break; + case PAM_PERM_DENIED: + if (delayed_until >= 0) { + resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED; + resp_len = sizeof(uint32_t) + sizeof(int64_t); + resp = talloc_size(preq->pd, resp_len); + if (resp == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_size failed, cannot prepare user info.\n"); + } else { + memcpy(resp, &resp_type, sizeof(uint32_t)); + dummy = (int64_t) delayed_until; + memcpy(resp+sizeof(uint32_t), &dummy, sizeof(int64_t)); + ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, + (const uint8_t *) resp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "pam_add_response failed.\n"); + } + } + } + break; + case PAM_AUTH_ERR: + /* Was this attempt to authenticate from cache? */ + if (use_cached_auth) { + /* Don't try cached authentication again, try online check. */ + DEBUG(SSSDBG_FUNC_DATA, + "Cached authentication failed for: %s\n", + preq->pd->user); + preq->cached_auth_failed = true; + pam_dom_forwarder(preq); + return; + } + break; + default: + DEBUG(SSSDBG_TRACE_LIBS, + "cached login returned: %d\n", preq->pd->pam_status); + } + + pam_reply(preq); + return; +} + +static void pam_forwarder_cb(struct tevent_req *req); +static void pam_forwarder_cert_cb(struct tevent_req *req); +int pam_check_user_search(struct pam_auth_req *preq); + + +/* TODO: we should probably return some sort of cookie that is set in the + * PAM_ENVIRONMENT, so that we can save performing some calls and cache + * data. */ + +static errno_t pam_forwarder_parse_data(struct cli_ctx *cctx, struct pam_data *pd) +{ + struct cli_protocol *prctx; + uint8_t *body; + size_t blen; + errno_t ret; + uint32_t terminator; + const char *key_id; + + prctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + + sss_packet_get_body(prctx->creq->in, &body, &blen); + if (blen >= sizeof(uint32_t)) { + SAFEALIGN_COPY_UINT32(&terminator, + body + blen - sizeof(uint32_t), + NULL); + if (terminator != SSS_END_OF_PAM_REQUEST) { + DEBUG(SSSDBG_CRIT_FAILURE, "Received data not terminated.\n"); + ret = EINVAL; + goto done; + } + } + + switch (prctx->cli_protocol_version->version) { + case 1: + ret = pam_parse_in_data(pd, body, blen); + break; + case 2: + ret = pam_parse_in_data_v2(pd, body, blen); + break; + case 3: + ret = pam_parse_in_data_v3(pd, body, blen); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Illegal protocol version [%d].\n", + prctx->cli_protocol_version->version); + ret = EINVAL; + } + if (ret != EOK) { + goto done; + } + + if (pd->logon_name != NULL) { + ret = sss_parse_name_for_domains(pd, cctx->rctx->domains, + cctx->rctx->default_domain, + pd->logon_name, + &pd->domain, &pd->user); + } else { + /* SSS_PAM_PREAUTH request may have a missing name, e.g. if the + * name is determined with the help of a certificate. During + * SSS_PAM_AUTHENTICATE at least a key ID is needed to identify the + * selected certificate. */ + if (pd->cmd == SSS_PAM_AUTHENTICATE + && may_do_cert_auth(talloc_get_type(cctx->rctx->pvt_ctx, + struct pam_ctx), pd) + && (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_SC_PIN + || sss_authtok_get_type(pd->authtok) + == SSS_AUTHTOK_TYPE_SC_KEYPAD)) { + ret = sss_authtok_get_sc(pd->authtok, NULL, NULL, NULL, NULL, NULL, + NULL, &key_id, NULL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_sc failed.\n"); + goto done; + } + + if (key_id == NULL || *key_id == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing logon and Smartcard key ID during " + "authentication.\n"); + ret = ERR_NO_CREDS; + goto done; + } + + ret = EOK; + } else if (pd->cmd == SSS_PAM_PREAUTH + && may_do_cert_auth(talloc_get_type(cctx->rctx->pvt_ctx, + struct pam_ctx), pd)) { + ret = EOK; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing logon name in PAM request.\n"); + ret = ERR_NO_CREDS; + goto done; + } + } + + DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); + +done: + return ret; +} + +static bool is_uid_trusted(struct cli_creds *creds, + size_t trusted_uids_count, + uid_t *trusted_uids) +{ + errno_t ret; + + /* root is always trusted */ + if (client_euid(creds) == 0) { + return true; + } + + /* All uids are allowed */ + if (trusted_uids_count == 0) { + return true; + } + + ret = check_allowed_uids(client_euid(creds), trusted_uids_count, trusted_uids); + if (ret == EOK) return true; + + return false; +} + +static bool is_domain_public(char *name, + char **public_dom_names, + size_t public_dom_names_count) +{ + size_t i; + + for(i=0; i < public_dom_names_count; i++) { + if (strcasecmp(name, public_dom_names[i]) == 0) { + return true; + } + } + return false; +} + +static enum cache_req_dom_type +get_domain_request_type(struct pam_auth_req *preq, + struct pam_ctx *pctx) +{ + enum cache_req_dom_type req_dom_type; + + /* By default, only POSIX domains are to be contacted */ + req_dom_type = CACHE_REQ_POSIX_DOM; + + for (int i = 0; pctx->app_services[i]; i++) { + if (strcmp(pctx->app_services[i], preq->pd->service) == 0) { + req_dom_type = CACHE_REQ_APPLICATION_DOM; + break; + } + } + + return req_dom_type; +} + +static errno_t check_cert(TALLOC_CTX *mctx, + struct tevent_context *ev, + struct pam_ctx *pctx, + struct pam_auth_req *preq, + struct pam_data *pd) +{ + int p11_child_timeout; + int wait_for_card_timeout; + char *cert_verification_opts; + errno_t ret; + struct tevent_req *req; + char *uri = NULL; + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_P11_CHILD_TIMEOUT, + P11_CHILD_TIMEOUT_DEFAULT, + &p11_child_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read p11_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + if ((pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) && pd->priv == 1) { + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_WAIT_FOR_CARD_TIMEOUT, + P11_WAIT_FOR_CARD_TIMEOUT_DEFAULT, + &wait_for_card_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read [%s] from confdb: [%d]: %s\n", + CONFDB_PAM_WAIT_FOR_CARD_TIMEOUT, ret, sss_strerror(ret)); + return ret; + } + + p11_child_timeout += wait_for_card_timeout; + } + + ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_CERT_VERIFICATION, + NULL, &cert_verification_opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read '"CONFDB_PAM_CERT_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (cert_verification_opts == NULL) { + ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_CERT_VERIFICATION, NULL, + &cert_verification_opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read '"CONFDB_MONITOR_CERT_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + } + + ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_P11_URI, NULL, &uri); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read '"CONFDB_PAM_P11_URI"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + req = pam_check_cert_send(mctx, ev, + pctx->ca_db, p11_child_timeout, + cert_verification_opts, pctx->sss_certmap_ctx, + uri, pd); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_check_cert_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(req, pam_forwarder_cert_cb, preq); + return EAGAIN; +} + + +static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd) +{ + struct pam_auth_req *preq; + struct pam_data *pd; + int ret; + struct pam_ctx *pctx = + talloc_get_type(cctx->rctx->pvt_ctx, struct pam_ctx); + struct tevent_req *req; + + preq = talloc_zero(cctx, struct pam_auth_req); + if (!preq) { + return ENOMEM; + } + preq->cctx = cctx; + preq->cert_auth_local = false; + preq->client_id_num = cctx->client_id_num; + + preq->pd = create_pam_data(preq); + if (!preq->pd) { + talloc_free(preq); + return ENOMEM; + } + pd = preq->pd; + + preq->is_uid_trusted = is_uid_trusted(cctx->creds, + pctx->trusted_uids_count, + pctx->trusted_uids); + + if (!preq->is_uid_trusted) { + DEBUG(SSSDBG_MINOR_FAILURE, "uid %"SPRIuid" is not trusted.\n", + client_euid(cctx->creds)); + } + + + pd->cmd = pam_cmd; + pd->priv = cctx->priv; + pd->client_id_num = cctx->client_id_num; + + ret = pam_forwarder_parse_data(cctx, pd); + if (ret == EAGAIN) { + req = sss_dp_get_domains_send(cctx, cctx->rctx, true, pd->domain); + if (req == NULL) { + ret = ENOMEM; + } else { + tevent_req_set_callback(req, pam_forwarder_cb, preq); + ret = EAGAIN; + } + goto done; + } else if (ret != EOK) { + goto done; + } + + /* Determine what domain type to contact */ + preq->req_dom_type = get_domain_request_type(preq, pctx); + + if (pd->cmd == SSS_PAM_AUTHENTICATE + && (pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) + && !IS_SC_AUTHTOK(pd->authtok)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Smartcard authentication required but authentication " + "token [%d][%s] is not suitable.\n", + sss_authtok_get_type(pd->authtok), + sss_authtok_type_to_str(sss_authtok_get_type(pd->authtok))); + ret = ERR_NO_CREDS; + goto done; + } + + /* Try backend first for authentication before doing local Smartcard + * authentication if a logon name is available. Otherwise try to derive + * the logon name from the certificate first. */ + if ((pd->cmd != SSS_PAM_AUTHENTICATE + || (pd->cmd == SSS_PAM_AUTHENTICATE && pd->logon_name == NULL)) + && may_do_cert_auth(pctx, pd)) { + ret = check_cert(cctx, cctx->ev, pctx, preq, pd); + /* Finish here */ + goto done; + } + + /* This is set to false inside passkey_local() if no passkey data is found. + * It is checked in pam_reply() to avoid an endless loop */ + preq->passkey_data_exists = true; + +#ifdef BUILD_PASSKEY + if ((pd->cmd == SSS_PAM_AUTHENTICATE)) { + if (may_do_passkey_auth(pctx, pd)) { + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + ret = passkey_kerberos(pctx, preq->pd, preq); + goto done; + } else if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY) { + /* TYPE_EMPTY is not a valid */ + ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); + goto done; + } + } + } + + if (pd->cmd == SSS_PAM_PASSKEY_PREAUTH) { + +#define PASSKEY_DOPIN "/var/run/passkey-dopin" +#define PASSKEY_PINUV "/var/run/passkey-pinuv" +#define PASSKEY_PINONLY "/var/run/passkey-pinonly" +#define PASSKEY_NODEV "/var/run/passkey-nodev" + + DEBUG(SSSDBG_TRACE_FUNC, "required passkey device information .\n"); + if (may_do_passkey_auth(pctx, pd)) { + const char *devinfo = "nocacheddevinfo"; + + if (pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_PASSKEY_CACHED_DEVINFO) { + struct stat st_nodev; + int e; + DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] requests cached devinfo .\n", getpid()); + + e = stat (PASSKEY_NODEV, &st_nodev); + if (e == 0) { + /* indicator exists */ + if (time(NULL) < st_nodev.st_ctime + 100 /*seconds : configurable ??? */) { + /* the info is still valid */ + devinfo = "nodev"; + } else { + /* but the info is obsolete, we shall retest device existence */ + devinfo = "nodevobsolete"; + (void)remove (PASSKEY_NODEV); + } + } else { + DEBUG(SSSDBG_TRACE_FUNC, "stat nodev: errno=%d\n", errno); + if (access(PASSKEY_DOPIN, F_OK) == 0) { + /* the device requires a PIN to perform next authentication; */ + devinfo = "dopin"; + } else if (access(PASSKEY_PINONLY, F_OK) == 0) { + /* the device ALWAYS requires a PIN to perform authentication; */ + devinfo = "pinonly"; + } else if (access(PASSKEY_PINUV, F_OK) == 0) { + /* the cached device supports UV and does not fallback to PIN using + * PASSKEY_DOPIN indicator. + */ + devinfo = "pinuv"; + } + } + /* remove cache indicators. + * They will be rebuild according the device capabilities + * (see passkey_child) + * during authentication or fresh devinfo request. + * do not remove DOPIN that is updated only during authentication + */ + (void)remove (PASSKEY_PINUV); + (void)remove (PASSKEY_PINONLY); + + } else { + /* remove cache indicators. + * They will be rebuild according the device capabilities + * (see passkey_child) + * during this devinfo request + * do not remove DOPIN that is updated only during authentication + */ + (void)remove (PASSKEY_PINUV); + (void)remove (PASSKEY_PINONLY); + + DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] requests fresh devinfo .\n", getpid()); + /* + * ask the device capabilities + * This assumes the device is connected. + */ + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] calls passkey_kerberos_get_devinfo() \n", getpid()); + ret = passkey_kerberos_get_devinfo(pctx, preq->pd, preq); + DEBUG(SSSDBG_TRACE_FUNC, "passkey_kerberos_get_devinfo() returns %d\n", ret); + goto done; + } else if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_EMPTY) { + + DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] calls passkey_local_get_devinfo() .\n", getpid()); + ret = passkey_local_get_devinfo(cctx, cctx->ev, pctx, preq, pd); + DEBUG(SSSDBG_TRACE_FUNC, "passkey_local_get_devinfo() returns %d\n", ret); + goto done; + } else { + /* note that AUTHTOK_TYPE_PASSKEY is not a valid authtok + * see get_device_info in pam_sss.c + */ + DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] requests fresh with invalid authtok type: [%d]\n", + getpid(),sss_authtok_get_type(pd->authtok) ); + ret = PAM_AUTHTOK_ERR; + goto done; + } + } + + DEBUG(SSSDBG_TRACE_FUNC, "cached devinfo request replies: [%s}\n", devinfo); + + ret = pam_add_response(pd, SSS_PAM_PASSKEY_DEVINFO, strlen(devinfo) + 1, + (const uint8_t *) devinfo); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed. [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + pd->pam_status = PAM_SUCCESS; + pam_reply(preq); + + /* finish */ + return EOK; + } + DEBUG(SSSDBG_TRACE_FUNC, "passkey_get_devinfo requires OK from may_do_passkey_auth .\n"); + } +#endif /* BUILD_PASSKEY */ + + ret = pam_check_user_search(preq); + +done: + return pam_check_user_done(preq, ret); +} + +static errno_t pam_user_by_cert_step(struct pam_auth_req *preq); +static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req); +static void pam_forwarder_cert_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + struct pam_data *pd; + errno_t ret = EOK; + const char *cert; + + ret = pam_check_cert_recv(req, preq, &preq->cert_list); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_cert request failed.\n"); + goto done; + } + + pd = preq->pd; + + cert = sss_cai_get_cert(preq->cert_list); + + if (cert == NULL) { + if (pd->logon_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No certificate found and no logon name given, " \ + "authentication not possible.\n"); + ret = ENOENT; + } else if (pd->cmd == SSS_PAM_PREAUTH + && (pd->cli_flags & PAM_CLI_FLAGS_TRY_CERT_AUTH)) { + DEBUG(SSSDBG_TRACE_ALL, + "try_cert_auth flag set but no certificate available, " + "request finished.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + pam_reply(preq); + return; + } else { + if (pd->cmd == SSS_PAM_AUTHENTICATE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No certificate returned, authentication failed.\n"); + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + return; + } else { + ret = pam_check_user_search(preq); + } + + } + goto done; + } + + preq->current_cert = preq->cert_list; + ret = pam_user_by_cert_step(preq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n"); + goto done; + } + + return; + +done: + pam_check_user_done(preq, ret); +} + +static errno_t pam_user_by_cert_step(struct pam_auth_req *preq) +{ + struct cli_ctx *cctx = preq->cctx; + struct tevent_req *req; + struct pam_ctx *pctx = + talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + if (preq->current_cert == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate data.\n"); + return EINVAL; + } + + req = cache_req_user_by_cert_send(preq, cctx->ev, cctx->rctx, + pctx->rctx->ncache, 0, + preq->req_dom_type, NULL, + sss_cai_get_cert(preq->current_cert)); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(req, pam_forwarder_lookup_by_cert_done, preq); + return EOK; +} + +static errno_t get_results_from_all_domains(TALLOC_CTX *mem_ctx, + struct cache_req_result **results, + struct ldb_result **ldb_results) +{ + int ret; + size_t count = 0; + size_t c; + size_t d; + size_t r = 0; + struct ldb_result *res; + + for (d = 0; results != NULL && results[d] != NULL; d++) { + count += results[d]->count; + } + + res = talloc_zero(mem_ctx, struct ldb_result); + if (res == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + + if (count == 0) { + *ldb_results = res; + return EOK; + } + + res->msgs = talloc_zero_array(res, struct ldb_message *, count); + if (res->msgs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + return ENOMEM; + } + res->count = count; + + for (d = 0; results != NULL && results[d] != NULL; d++) { + for (c = 0; c < results[d]->count; c++) { + if (r >= count) { + DEBUG(SSSDBG_CRIT_FAILURE, + "More results found then counted before.\n"); + ret = EINVAL; + goto done; + } + res->msgs[r++] = talloc_steal(res->msgs, results[d]->msgs[c]); + } + } + + *ldb_results = res; + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(res); + } + + return ret; +} + +/* Return true if hint is set for at least one domain */ +static bool get_user_name_hint(struct sss_domain_info *domains) +{ + struct sss_domain_info *d; + + DLIST_FOR_EACH(d, domains) { + if (d->user_name_hint == true) { + return true; + } + } + + return false; +} + +static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req) +{ + int ret; + struct cache_req_result **results; + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + const char *cert_user = NULL; + size_t cert_count = 0; + size_t cert_user_count = 0; + struct ldb_result *cert_user_objs; + + ret = cache_req_recv(preq, req, &results); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert request failed.\n"); + goto done; + } + + if (ret == EOK) { + ret = get_results_from_all_domains(preq, results, + &cert_user_objs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_results_from_all_domains failed.\n"); + goto done; + } + + sss_cai_set_cert_user_objs(preq->current_cert, cert_user_objs); + } + + preq->current_cert = sss_cai_get_next(preq->current_cert); + if (preq->current_cert != NULL) { + ret = pam_user_by_cert_step(preq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n"); + goto done; + } + return; + } + + sss_cai_check_users(&preq->cert_list, &cert_count, &cert_user_count); + DEBUG(SSSDBG_TRACE_ALL, + "Found [%zu] certificates and [%zu] related users.\n", + cert_count, cert_user_count); + + if (cert_user_count == 0) { + if (preq->pd->logon_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing logon name and no certificate user found.\n"); + ret = ENOENT; + goto done; + } + } else { + + if (preq->pd->logon_name == NULL) { + if (preq->pd->cmd != SSS_PAM_PREAUTH + && preq->pd->cmd != SSS_PAM_AUTHENTICATE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing logon name only allowed during (pre-)auth.\n"); + ret = ENOENT; + goto done; + } + /* Multiple certificates are only expected during pre-auth */ + if (cert_count > 1 && preq->pd->cmd == SSS_PAM_PREAUTH) { + for (preq->current_cert = preq->cert_list; + preq->current_cert != NULL; + preq->current_cert = sss_cai_get_next(preq->current_cert)) { + + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, "", + preq->current_cert, + get_user_name_hint(preq->cctx->rctx->domains) + ? SSS_PAM_CERT_INFO_WITH_HINT + : SSS_PAM_CERT_INFO); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + } + + ret = EOK; + preq->pd->pam_status = PAM_SUCCESS; + pam_reply(preq); + goto done; + } + + if (cert_user_count == 1) { + cert_user_objs = sss_cai_get_cert_user_objs(preq->cert_list); + if (cert_user_objs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate user.\n"); + ret = ENOENT; + goto done; + } + + cert_user = ldb_msg_find_attr_as_string( + cert_user_objs->msgs[0], + SYSDB_NAME, NULL); + if (cert_user == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Certificate user object has not name.\n"); + ret = ENOENT; + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, + "Found certificate user [%s].\n", cert_user); + + ret = sss_parse_name_for_domains(preq->pd, + preq->cctx->rctx->domains, + preq->cctx->rctx->default_domain, + cert_user, + &preq->pd->domain, + &preq->pd->user); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_parse_name_for_domains failed.\n"); + goto done; + } + } + + if (get_user_name_hint(preq->cctx->rctx->domains) + && preq->pd->cmd == SSS_PAM_PREAUTH) { + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, cert_user, + preq->cert_list, + SSS_PAM_CERT_INFO_WITH_HINT); + preq->pd->pam_status = PAM_SUCCESS; + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + ret = EOK; + pam_reply(preq); + goto done; + } + + /* Without user name hints the certificate must map to single user + * if no login name was given */ + if (cert_user == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "More than one user mapped to certificate.\n"); + ret = ERR_NO_CREDS; + goto done; + } + + /* If logon_name was not given during authentication add a + * SSS_PAM_CERT_INFO message to send the name to the caller. + * Additionally initial_cert_auth_successful is set to + * indicate that the user is already authenticated. */ + if (preq->pd->cmd == SSS_PAM_AUTHENTICATE + && preq->pd->logon_name == NULL) { + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, cert_user, + preq->cert_list, + SSS_PAM_CERT_INFO); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + goto done; + } + + preq->initial_cert_auth_successful = true; + } + + /* cert_user will be returned to the PAM client as user name, so + * we can use it here already e.g. to set in initgroups timeout */ + preq->pd->logon_name = talloc_strdup(preq->pd, cert_user); + if (preq->pd->logon_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } + } + + if (preq->user_obj == NULL) { + ret = pam_check_user_search(preq); + } else { + ret = EOK; + } + + if (ret == EOK) { + pam_dom_forwarder(preq); + } + +done: + pam_check_user_done(preq, ret); +} + +static void pam_forwarder_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + struct cli_ctx *cctx = preq->cctx; + struct pam_data *pd; + errno_t ret = EOK; + struct pam_ctx *pctx = + talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + ret = sss_dp_get_domains_recv(req); + talloc_free(req); + if (ret != EOK) { + goto done; + } + + ret = p11_refresh_certmap_ctx(pctx, pctx->rctx->domains); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "p11_refresh_certmap_ctx failed, " + "certificate matching might not work as expected"); + } + + pd = preq->pd; + + ret = pam_forwarder_parse_data(cctx, pd); + if (ret == EAGAIN) { + DEBUG(SSSDBG_TRACE_FUNC, "Assuming %s is a UPN\n", pd->logon_name); + /* If not, cache_req will error out later */ + pd->user = talloc_strdup(pd, pd->logon_name); + if (pd->user == NULL) { + ret = ENOMEM; + goto done; + } + pd->domain = NULL; + } else if (ret != EOK) { + ret = EINVAL; + goto done; + } + + /* try backend first for authentication before doing local Smartcard + * authentication */ + if (pd->cmd != SSS_PAM_AUTHENTICATE && may_do_cert_auth(pctx, pd)) { + ret = check_cert(cctx, cctx->ev, pctx, preq, pd); + /* Finish here */ + goto done; + } + +#ifdef BUILD_PASSKEY + /* This is set to false inside passkey_local() if no passkey data is found. + * It is checked in pam_reply() to avoid an endless loop */ + preq->passkey_data_exists = true; + + if ((pd->cmd == SSS_PAM_AUTHENTICATE)) { + if (may_do_passkey_auth(pctx, pd)) { + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + ret = passkey_kerberos(pctx, preq->pd, preq); + goto done; + } else if ((sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY) || + (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_EMPTY)) { + ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); + goto done; + } + } + } +#endif /* BUILD_PASSKEY */ + + ret = pam_check_user_search(preq); + +done: + pam_check_user_done(preq, ret); +} + +static void pam_check_user_search_next(struct tevent_req *req); +static void pam_check_user_search_lookup(struct tevent_req *req); +static void pam_check_user_search_done(struct pam_auth_req *preq, int ret, + struct cache_req_result *result); + +/* lookup the user uid from the cache first, + * then we'll refresh initgroups if needed */ +int pam_check_user_search(struct pam_auth_req *preq) +{ + struct tevent_req *dpreq; + struct cache_req_data *data; + + data = cache_req_data_name(preq, + CACHE_REQ_INITGROUPS, + preq->pd->logon_name); + if (data == NULL) { + return ENOMEM; + } + + cache_req_data_set_bypass_cache(data, false); + cache_req_data_set_bypass_dp(data, true); + cache_req_data_set_requested_domains(data, preq->pd->requested_domains); + + dpreq = cache_req_send(preq, + preq->cctx->rctx->ev, + preq->cctx->rctx, + preq->cctx->rctx->ncache, + 0, + preq->req_dom_type, + NULL, + data); + if (!dpreq) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Out of memory sending data provider request\n"); + return ENOMEM; + } + + tevent_req_set_callback(dpreq, pam_check_user_search_next, preq); + + /* tell caller we are in an async call */ + return EAGAIN; +} + +static void pam_check_user_search_next(struct tevent_req *req) +{ + struct pam_auth_req *preq; + struct pam_ctx *pctx; + struct cache_req_result *result = NULL; + struct cache_req_data *data; + struct tevent_req *dpreq; + int ret; + + preq = tevent_req_callback_data(req, struct pam_auth_req); + pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + ret = cache_req_single_domain_recv(preq, req, &result); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Cache lookup failed, trying to get fresh " + "data from the backend.\n"); + } + + DEBUG(SSSDBG_TRACE_ALL, "PAM initgroups scheme [%s].\n", + pam_initgroup_enum_to_string(pctx->initgroups_scheme)); + + if (ret == EOK) { + bool user_has_session = false; + + if (pctx->initgroups_scheme == PAM_INITGR_NO_SESSION) { + uid_t uid = ldb_msg_find_attr_as_uint64(result->msgs[0], + SYSDB_UIDNUM, 0); + if (!uid) { + DEBUG(SSSDBG_CRIT_FAILURE, "A user with no UID?\n"); + talloc_zfree(preq->cctx); + return; + } + + /* If a user already has a session on the system, we take the + * cache for granted and do not force an online lookup. This is + * because in most cases the user is just trying to authenticate + * but not create a new session (sudo, lockscreen, polkit, etc.) + * An online refresh in this situation would just delay operations + * without providing any useful additional information. + */ + (void)check_if_uid_is_active(uid, &user_has_session); + + DEBUG(SSSDBG_TRACE_ALL, "Found %s session for uid %"SPRIuid".\n", + user_has_session ? "a" : "no", uid); + } + + /* The initgr cache is used to make sure that during a single PAM + * session (auth, acct_mgtm, ....) the backend is contacted only + * once. logon_name is the name provided by the PAM client and + * will not be modified during the request, so it makes sense to + * use it here instead od the pd->user. + */ + ret = pam_initgr_check_timeout(pctx->id_table, preq->pd->logon_name); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Could not look up initgroup timeout\n"); + } + + if ((ret == EOK) || user_has_session + || pctx->initgroups_scheme == PAM_INITGR_NEVER) { + DEBUG(SSSDBG_TRACE_ALL, "No new initgroups needed because:\n"); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_ALL, "PAM initgr cache still valid.\n"); + } else if (user_has_session) { + DEBUG(SSSDBG_TRACE_ALL, "there is a active session for " + "user [%s].\n", preq->pd->logon_name); + } else if (pctx->initgroups_scheme == PAM_INITGR_NEVER) { + DEBUG(SSSDBG_TRACE_ALL, "initgroups scheme is 'never'.\n"); + } + pam_check_user_search_done(preq, EOK, result); + return; + } + } + + /* If we get here it means the user was not found or does not have a + * session, or initgr has not been cached before, so we force a new + * online lookup */ + data = cache_req_data_name(preq, + CACHE_REQ_INITGROUPS, + preq->pd->logon_name); + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory\n"); + talloc_zfree(preq->cctx); + return; + } + cache_req_data_set_bypass_cache(data, true); + cache_req_data_set_bypass_dp(data, false); + cache_req_data_set_requested_domains(data, preq->pd->requested_domains); + + dpreq = cache_req_send(preq, + preq->cctx->rctx->ev, + preq->cctx->rctx, + preq->cctx->rctx->ncache, + 0, + preq->req_dom_type, + NULL, + data); + if (!dpreq) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Out of memory sending data provider request\n"); + talloc_zfree(preq->cctx); + return; + } + + tevent_req_set_callback(dpreq, pam_check_user_search_lookup, preq); +} + +static void pam_check_user_search_lookup(struct tevent_req *req) +{ + struct cache_req_result *result; + struct pam_auth_req *preq; + int ret; + + preq = tevent_req_callback_data(req, struct pam_auth_req); + + ret = cache_req_single_domain_recv(preq, req, &result); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Fatal error, killing connection!\n"); + talloc_zfree(preq->cctx); + return; + } + + pam_check_user_search_done(preq, ret, result); +} + +static void pam_check_user_search_done(struct pam_auth_req *preq, int ret, + struct cache_req_result *result) +{ + struct pam_ctx *pctx; + + pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + if (ret == EOK) { + preq->user_obj = result->msgs[0]; + pd_set_primary_name(preq->user_obj, preq->pd); + preq->domain = result->domain; + + ret = pam_initgr_cache_set(pctx->rctx->ev, + pctx->id_table, + preq->pd->logon_name, + pctx->id_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not save initgr timestamp." + "Proceeding with PAM actions\n"); + } + + pam_dom_forwarder(preq); + } + + ret = pam_check_user_done(preq, ret); + if (ret != EOK) { + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } +} + +int pam_check_user_done(struct pam_auth_req *preq, int ret) +{ + switch (ret) { + case EOK: + break; + + case EAGAIN: + /* performing async request, just return */ + break; + + case ENOENT: + preq->pd->pam_status = PAM_USER_UNKNOWN; + pam_reply(preq); + break; + + case ERR_P11_PIN_LOCKED: + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + break; + + case ERR_NO_CREDS: + preq->pd->pam_status = PAM_CRED_INSUFFICIENT; + pam_reply(preq); + break; + + default: + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + break; + } + + return EOK; +} + +static errno_t pam_is_last_online_login_fresh(struct sss_domain_info *domain, + const char* user, + int cached_auth_timeout, + bool *_result) +{ + errno_t ret; + bool result = true; + uint64_t last_login; + + ret = pam_get_last_online_auth_with_curr_token(domain, user, &last_login); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sysdb_get_last_online_auth_with_curr_token failed: %s:[%d]\n", + sss_strerror(ret), ret); + goto done; + } + + result = time(NULL) < (last_login + cached_auth_timeout); + ret = EOK; + +done: + if (ret == EOK) { + *_result = result; + } + return ret; +} + +static bool pam_is_authtok_cachable(struct sss_auth_token *authtok) +{ + enum sss_authtok_type type; + bool cachable = false; + + type = sss_authtok_get_type(authtok); + if (type == SSS_AUTHTOK_TYPE_PASSWORD) { + cachable = true; + } else { + DEBUG(SSSDBG_TRACE_LIBS, "Authentication token can't be cached\n"); + } + + return cachable; +} + +static bool pam_can_user_cache_auth(struct sss_domain_info *domain, + int pam_cmd, + struct sss_auth_token *authtok, + const char* user, + bool cached_auth_failed) +{ + errno_t ret; + bool result = false; + + if (cached_auth_failed) { + /* Do not retry indefinitely */ + return false; + } + + if (!domain->cache_credentials || domain->cached_auth_timeout <= 0) { + return false; + } + + if (pam_cmd == SSS_PAM_PREAUTH + || (pam_cmd == SSS_PAM_AUTHENTICATE + && pam_is_authtok_cachable(authtok))) { + + ret = pam_is_last_online_login_fresh(domain, user, + domain->cached_auth_timeout, + &result); + if (ret != EOK) { + /* non-critical, consider fail as 'non-fresh value' */ + DEBUG(SSSDBG_MINOR_FAILURE, + "pam_is_last_online_login_fresh failed: %s:[%d]\n", + sss_strerror(ret), ret); + } + } + + return result; +} + +static void pam_dom_forwarder(struct pam_auth_req *preq) +{ + TALLOC_CTX *tmp_ctx = NULL; + int ret; + struct pam_ctx *pctx = + talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + const char *cert_user; + struct ldb_result *cert_user_objs; + bool sc_auth; + bool passkey_auth; + size_t c; + char *local_policy = NULL; + bool found = false; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return; + } + + if (!preq->pd->domain) { + preq->pd->domain = preq->domain->name; + } + + /* Untrusted users can access only public domains. */ + if (!preq->is_uid_trusted && + !is_domain_public(preq->pd->domain, pctx->public_domains, + pctx->public_domains_count)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Untrusted user %"SPRIuid" cannot access non-public domain %s.\n", + client_euid(preq->cctx->creds), preq->pd->domain); + preq->pd->pam_status = PAM_PERM_DENIED; + pam_reply(preq); + return; + } + + /* skip this domain if not requested and the user is trusted + * as untrusted users can't request a domain */ + if (preq->is_uid_trusted && + !is_domain_requested(preq->pd, preq->pd->domain)) { + preq->pd->pam_status = PAM_USER_UNKNOWN; + pam_reply(preq); + return; + } + + if (pam_can_user_cache_auth(preq->domain, + preq->pd->cmd, + preq->pd->authtok, + preq->pd->user, + preq->cached_auth_failed)) { + preq->use_cached_auth = true; + pam_reply(preq); + return; + } + + /* Skip online auth when local auth policy = only */ +#ifdef BUILD_PASSKEY + if (may_do_cert_auth(pctx, preq->pd) || may_do_passkey_auth(pctx, preq->pd)) { +#else + if (may_do_cert_auth(pctx, preq->pd)) { +#endif /* BUILD_PASSKEY */ + if (preq->domain->name != NULL) { + ret = pam_eval_local_auth_policy(preq->cctx, pctx, preq->pd, preq, + &sc_auth, + &passkey_auth, + &local_policy); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to evaluate local auth policy\n"); + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + return; + } + } + } + + if (may_do_cert_auth(pctx, preq->pd) && preq->cert_list != NULL) { + /* Check if user matches certificate user */ + found = false; + for (preq->current_cert = preq->cert_list; + preq->current_cert != NULL; + preq->current_cert = sss_cai_get_next(preq->current_cert)) { + + cert_user_objs = sss_cai_get_cert_user_objs(preq->current_cert); + if (cert_user_objs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Unexpected missing certificate user, " + "trying next certificate.\n"); + continue; + } + + for (c = 0; c < cert_user_objs->count; c++) { + cert_user = ldb_msg_find_attr_as_string(cert_user_objs->msgs[c], + SYSDB_NAME, NULL); + if (cert_user == NULL) { + /* Even if there might be other users mapped to the + * certificate a missing SYSDB_NAME indicates some critical + * condition which justifies that the whole request is aborted + * */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Certificate user object has no name.\n"); + preq->pd->pam_status = PAM_USER_UNKNOWN; + pam_reply(preq); + return; + } + + if (ldb_dn_compare(cert_user_objs->msgs[c]->dn, + preq->user_obj->dn) == 0) { + found = true; + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + ret = sss_authtok_set_sc(preq->pd->authtok, + SSS_AUTHTOK_TYPE_SC_PIN, NULL, 0, + sss_cai_get_token_name(preq->current_cert), 0, + sss_cai_get_module_name(preq->current_cert), 0, + sss_cai_get_key_id(preq->current_cert), 0, + sss_cai_get_label(preq->current_cert), 0); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_authtok_set_sc failed, Smartcard " + "authentication detection might fail in " + "the backend.\n"); + } + + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, + cert_user, + preq->current_cert, + SSS_PAM_CERT_INFO); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + } + + } + } + } + + if (found) { + if (local_policy != NULL && strcasecmp(local_policy, "only") == 0) { + talloc_free(tmp_ctx); + DEBUG(SSSDBG_IMPORTANT_INFO, + "Local auth only set and matching certificate was found, " + "skipping online auth\n"); + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + preq->pd->pam_status = PAM_SUCCESS; + } else if (preq->pd->cmd == SSS_PAM_AUTHENTICATE + && IS_SC_AUTHTOK(preq->pd->authtok) + && (preq->cert_auth_local + || preq->initial_cert_auth_successful)) { + preq->pd->pam_status = PAM_SUCCESS; + preq->callback = pam_reply; + } + + pam_reply(preq); + return; + } + + /* We are done if we do not have to call the backend */ + if (preq->pd->cmd == SSS_PAM_AUTHENTICATE + && preq->cert_auth_local) { + preq->pd->pam_status = PAM_SUCCESS; + preq->callback = pam_reply; + pam_reply(preq); + return; + } + } else { + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + DEBUG(SSSDBG_TRACE_FUNC, + "User and certificate user do not match, " + "continue with other authentication methods.\n"); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "User and certificate user do not match.\n"); + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + return; + } + } + } + + if (local_policy != NULL && strcasecmp(local_policy, "only") == 0) { + talloc_free(tmp_ctx); + DEBUG(SSSDBG_IMPORTANT_INFO, "Local auth only set, skipping online auth\n"); + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + preq->pd->pam_status = PAM_SUCCESS; + } else if (preq->pd->cmd == SSS_PAM_AUTHENTICATE && IS_SC_AUTHTOK(preq->pd->authtok)) { + /* Trigger offline smartcardcard autheitcation */ + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + + pam_reply(preq); + return; + } + + preq->callback = pam_reply; + ret = pam_dp_send_req(preq); + DEBUG(SSSDBG_CONF_SETTINGS, "pam_dp_send_req returned %d\n", ret); + + talloc_free(tmp_ctx); + + if (ret != EOK) { + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } +} + +static int pam_cmd_authenticate(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_authenticate\n"); + return pam_forwarder(cctx, SSS_PAM_AUTHENTICATE); +} + +static int pam_cmd_setcred(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_setcred\n"); + return pam_forwarder(cctx, SSS_PAM_SETCRED); +} + +static int pam_cmd_acct_mgmt(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_acct_mgmt\n"); + return pam_forwarder(cctx, SSS_PAM_ACCT_MGMT); +} + +static int pam_cmd_open_session(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_open_session\n"); + return pam_forwarder(cctx, SSS_PAM_OPEN_SESSION); +} + +static int pam_cmd_close_session(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_close_session\n"); + return pam_forwarder(cctx, SSS_PAM_CLOSE_SESSION); +} + +static int pam_cmd_chauthtok(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_chauthtok\n"); + return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK); +} + +static int pam_cmd_chauthtok_prelim(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_chauthtok_prelim\n"); + return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK_PRELIM); +} + +static int pam_cmd_preauth(struct cli_ctx *cctx) +{ + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_preauth\n"); + return pam_forwarder(cctx, SSS_PAM_PREAUTH); +} + +static int pam_cmd_passkey_preauth(struct cli_ctx *cctx) +{ + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_passkey_preauth\n"); + return pam_forwarder(cctx, SSS_PAM_PASSKEY_PREAUTH); +} + +struct cli_protocol_version *register_cli_protocol_version(void) +{ + static struct cli_protocol_version pam_cli_protocol_version[] = { + {3, "2009-09-14", "make cli_pid mandatory"}, + {2, "2009-05-12", "new format "}, + {1, "2008-09-05", "initial version, \\0 terminated strings"}, + {0, NULL, NULL} + }; + + return pam_cli_protocol_version; +} + +struct sss_cmd_table *get_pam_cmds(void) +{ + static struct sss_cmd_table sss_cmds[] = { + {SSS_GET_VERSION, sss_cmd_get_version}, + {SSS_PAM_AUTHENTICATE, pam_cmd_authenticate}, + {SSS_PAM_SETCRED, pam_cmd_setcred}, + {SSS_PAM_ACCT_MGMT, pam_cmd_acct_mgmt}, + {SSS_PAM_OPEN_SESSION, pam_cmd_open_session}, + {SSS_PAM_CLOSE_SESSION, pam_cmd_close_session}, + {SSS_PAM_CHAUTHTOK, pam_cmd_chauthtok}, + {SSS_PAM_CHAUTHTOK_PRELIM, pam_cmd_chauthtok_prelim}, + {SSS_PAM_PREAUTH, pam_cmd_preauth}, + {SSS_PAM_PASSKEY_PREAUTH, pam_cmd_passkey_preauth}, + {SSS_GSSAPI_INIT, pam_cmd_gssapi_init}, + {SSS_GSSAPI_SEC_CTX, pam_cmd_gssapi_sec_ctx}, + {SSS_CLI_NULL, NULL} + }; + + return sss_cmds; +} + +errno_t +pam_set_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *username, + uint64_t value) +{ + TALLOC_CTX *tmp_ctx; + struct sysdb_attrs *attrs; + int ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + attrs = sysdb_new_attrs(tmp_ctx); + if (attrs == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_time_t(attrs, + SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, + value); + if (ret != EOK) { goto done; } + + ret = sysdb_set_user_attr(domain, username, attrs, SYSDB_MOD_REP); + if (ret != EOK) { goto done; } + +done: + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, sss_strerror(ret)); + } + + talloc_zfree(tmp_ctx); + return ret; +} + +static errno_t +pam_null_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *username) +{ + return pam_set_last_online_auth_with_curr_token(domain, username, 0); +} + +static errno_t +pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *name, + uint64_t *_value) +{ + TALLOC_CTX *tmp_ctx = NULL; + const char *attrs[] = { SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, NULL }; + struct ldb_message *ldb_msg; + uint64_t value = 0; + errno_t ret; + + if (name == NULL || *name == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); + ret = EINVAL; + goto done; + } + + if (domain->sysdb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing sysdb db context.\n"); + ret = EINVAL; + goto done; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_user_by_name(tmp_ctx, domain, name, attrs, &ldb_msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_search_user_by_name failed [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + + /* Check offline_auth_cache_timeout */ + value = ldb_msg_find_attr_as_uint64(ldb_msg, + SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, + 0); + ret = EOK; + +done: + if (ret == EOK) { + *_value = value; + } + + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/responder/pam/pamsrv_passkey.c b/src/responder/pam/pamsrv_passkey.c index 83f36793fe0..c40515633b3 100644 --- a/src/responder/pam/pamsrv_passkey.c +++ b/src/responder/pam/pamsrv_passkey.c @@ -1,1463 +1,1879 @@ -/* - SSSD - - PAM Responder - passkey related requests - - Copyright (C) Justin Stephenson 2022 - - 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 . -*/ - -#include "util/child_common.h" -#include "util/authtok.h" -#include "db/sysdb.h" -#include "db/sysdb_passkey_user_verification.h" -#include "responder/pam/pamsrv.h" - -#include "responder/pam/pamsrv_passkey.h" - -struct pam_passkey_verification_enum_str { - enum passkey_user_verification verification; - const char *option; -}; - -struct pam_passkey_table_data { - hash_table_t *table; - char *key; - struct pk_child_user_data *data; -}; - -struct pam_passkey_verification_enum_str pam_passkey_verification_enum_str[] = { - { PAM_PASSKEY_VERIFICATION_ON, "on" }, - { PAM_PASSKEY_VERIFICATION_OFF, "off" }, - { PAM_PASSKEY_VERIFICATION_OMIT, "unset" }, - { PAM_PASSKEY_VERIFICATION_INVALID, NULL } -}; - -#define PASSKEY_PREFIX "passkey:" -#define USER_VERIFICATION "user_verification=" -#define USER_VERIFICATION_LEN (sizeof(USER_VERIFICATION) -1) - -const char *pam_passkey_verification_enum_to_string(enum passkey_user_verification verification) -{ - size_t c; - - for (c = 0 ; pam_passkey_verification_enum_str[c].option != NULL; c++) { - if (pam_passkey_verification_enum_str[c].verification == verification) { - return pam_passkey_verification_enum_str[c].option; - } - } - - return "(NULL)"; -} - -struct passkey_ctx { - struct pam_ctx *pam_ctx; - struct tevent_context *ev; - struct pam_data *pd; - struct pam_auth_req *preq; -}; - -void pam_forwarder_passkey_cb(struct tevent_req *req); - -errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, - struct pk_child_user_data *pk_data, - bool kerberos_pa, - char **_result_kh, - char **_result_ph); - -struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - struct passkey_ctx *pctx); -void pam_passkey_get_user_done(struct tevent_req *req); -void pam_passkey_get_mapping_done(struct tevent_req *req); -errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, - struct tevent_req *req, - struct cache_req_result **_result); - -struct passkey_get_mapping_state { - struct pam_data *pd; - struct cache_req_result *result; -}; - -void passkey_kerberos_cb(struct tevent_req *req) -{ - struct pam_auth_req *preq = tevent_req_callback_data(req, - struct pam_auth_req); - errno_t ret = EOK; - int child_status; - - ret = pam_passkey_auth_recv(req, &child_status); - talloc_free(req); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); - - pam_check_user_search(preq); - -done: - pam_check_user_done(preq, ret); -} - -errno_t passkey_kerberos(struct pam_ctx *pctx, - struct pam_data *pd, - struct pam_auth_req *preq) -{ - errno_t ret; - const char *prompt; - const char *key; - const char *pin; - size_t pin_len; - struct pk_child_user_data *data; - struct tevent_req *req; - int timeout; - char *verify_opts; - bool debug_libfido2; - enum passkey_user_verification verification; - - ret = sss_authtok_get_passkey(preq, preq->pd->authtok, - &prompt, &key, &pin, &pin_len); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failure to get passkey authtok\n"); - return EIO; - } - - if (prompt == NULL || key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "Passkey prompt and key are missing or invalid.\n"); - return EIO; - } - - data = sss_ptr_hash_lookup(pctx->pk_table_data->table, key, - struct pk_child_user_data); - if (data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to lookup passkey authtok\n"); - return EIO; - } - - ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, - &timeout); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - ret = confdb_get_string(pctx->rctx->cdb, preq, CONFDB_MONITOR_CONF_ENTRY, - CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, - &verify_opts); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - /* Always use verification sent from passkey krb5 plugin */ - if (strcasecmp(data->user_verification, "false") == 0) { - verification = PAM_PASSKEY_VERIFICATION_OFF; - } else { - verification = PAM_PASSKEY_VERIFICATION_ON; - } - - ret = confdb_get_bool(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, - &debug_libfido2); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - req = pam_passkey_auth_send(preq->cctx, preq->cctx->ev, timeout, debug_libfido2, - verification, pd, data, true); - if (req == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "passkey auth send failed [%d]: [%s]\n", - ret, sss_strerror(ret)); - goto done; - } - - tevent_req_set_callback(req, passkey_kerberos_cb, preq); - - ret = EAGAIN; - -done: - - return ret; - -} - - -errno_t passkey_local(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - struct pam_ctx *pam_ctx, - struct pam_auth_req *preq, - struct pam_data *pd) -{ - struct tevent_req *req; - struct passkey_ctx *pctx; - errno_t ret; - - pctx = talloc_zero(mem_ctx, struct passkey_ctx); - if (pctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "pctx == NULL\n"); - return ENOMEM; - } - - pctx->pd = pd; - pctx->pam_ctx = pam_ctx; - pctx->ev = ev; - pctx->preq = preq; - - DEBUG(SSSDBG_TRACE_FUNC, "Checking for passkey authentication data\n"); - - req = pam_passkey_get_mapping_send(mem_ctx, pctx->ev, pctx); - if (req == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_get_mapping_send failed.\n"); - ret = ENOMEM; - goto done; - } - - tevent_req_set_callback(req, pam_passkey_get_user_done, pctx); - - ret = EAGAIN; - -done: - if (ret != EAGAIN) { - talloc_free(pctx); - } - - return ret; -} - -struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - struct passkey_ctx *pk_ctx) -{ - - struct passkey_get_mapping_state *state; - struct tevent_req *req; - struct tevent_req *subreq; - int ret; - static const char *attrs[] = { SYSDB_NAME, SYSDB_USER_PASSKEY, NULL }; - - req = tevent_req_create(mem_ctx, &state, struct passkey_get_mapping_state); - if (req == NULL) { - ret = ENOMEM; - goto done; - } - - subreq = cache_req_user_by_name_attrs_send(state, pk_ctx->ev, - pk_ctx->pam_ctx->rctx, - pk_ctx->pam_ctx->rctx->ncache, 0, - pk_ctx->pd->domain, - pk_ctx->pd->user, attrs); - if (subreq == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); - ret = ENOMEM; - goto done; - } - - tevent_req_set_callback(subreq, pam_passkey_get_mapping_done, req); - - return req; - -done: - tevent_req_error(req, ret); - tevent_req_post(req, ev); - - return req; -} - -void pam_passkey_get_mapping_done(struct tevent_req *subreq) -{ - struct cache_req_result *result; - struct tevent_req *req; - struct passkey_get_mapping_state *state; - - errno_t ret; - - req = tevent_req_callback_data(subreq, struct tevent_req); - state = tevent_req_data(req, struct passkey_get_mapping_state); - - ret = cache_req_user_by_name_attrs_recv(state, subreq, &result); - state->result = result; - - talloc_zfree(subreq); - if (ret != EOK) { - tevent_req_error(req, ret); - return; - } - - tevent_req_done(req); - return; -} - -errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, - struct tevent_req *req, - struct cache_req_result **_result) -{ - struct passkey_get_mapping_state *state = NULL; - - state = tevent_req_data(req, struct passkey_get_mapping_state); - - TEVENT_REQ_RETURN_ON_ERROR(req); - - *_result = talloc_steal(mem_ctx, state->result); - return EOK; -} - -errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, - const char *verify_opts, - enum passkey_user_verification *_user_verification) -{ - int ret; - TALLOC_CTX *tmp_ctx; - char **opts; - size_t c; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); - return ENOMEM; - } - - if (verify_opts == NULL) { - ret = EOK; - goto done; - } - - ret = split_on_separator(tmp_ctx, verify_opts, ',', true, true, &opts, - NULL); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d], %s.\n", - ret, sss_strerror(ret)); - goto done; - } - - for (c = 0; opts[c] != NULL; c++) { - if (strncasecmp(opts[c], USER_VERIFICATION, USER_VERIFICATION_LEN) == 0) { - if (strcasecmp("true", &opts[c][USER_VERIFICATION_LEN]) == 0) { - DEBUG(SSSDBG_TRACE_FUNC, "user_verification set to true.\n"); - *_user_verification = PAM_PASSKEY_VERIFICATION_ON; - } else if (strcasecmp("false", &opts[c][USER_VERIFICATION_LEN]) == 0) { - DEBUG(SSSDBG_TRACE_FUNC, "user_verification set to false.\n"); - *_user_verification = PAM_PASSKEY_VERIFICATION_OFF; - } - } else { - DEBUG(SSSDBG_MINOR_FAILURE, - "Unsupported passkey verification option [%s], " \ - "skipping.\n", opts[c]); - } - } - - ret = EOK; - -done: - talloc_free(tmp_ctx); - - return ret; -} - -static errno_t passkey_local_verification(TALLOC_CTX *mem_ctx, - struct passkey_ctx *pctx, - struct confdb_ctx *cdb, - struct sysdb_ctx *sysdb, - const char *domain_name, - struct pk_child_user_data *pk_data, - enum passkey_user_verification *_user_verification, - bool *_debug_libfido2) -{ - TALLOC_CTX *tmp_ctx; - errno_t ret; - const char *verification_from_ldap; - char *verify_opts = NULL; - bool debug_libfido2 = false; - enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - return ENOMEM; - } - - ret = sysdb_domain_get_passkey_user_verification(tmp_ctx, sysdb, domain_name, - &verification_from_ldap); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read passkeyUserVerification from sysdb: [%d]: %s\n", - ret, sss_strerror(ret)); - /* This is expected for AD and LDAP */ - ret = EOK; - goto done; - } - - ret = confdb_get_bool(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, - &debug_libfido2); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - /* If require user verification setting is set in LDAP, use it */ - if (verification_from_ldap != NULL) { - if (strcasecmp(verification_from_ldap, "true") == 0) { - verification = PAM_PASSKEY_VERIFICATION_ON; - } else if (strcasecmp(verification_from_ldap, "false") == 0) { - verification = PAM_PASSKEY_VERIFICATION_OFF; - } - DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification is being enforced from LDAP\n"); - } else { - /* No verification set in LDAP, fallback to local sssd.conf setting */ - ret = confdb_get_string(pctx->pam_ctx->rctx->cdb, tmp_ctx, CONFDB_MONITOR_CONF_ENTRY, - CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, - &verify_opts); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - - ret = read_passkey_conf_verification(tmp_ctx, verify_opts, &verification); - if (ret != EOK) { - DEBUG(SSSDBG_MINOR_FAILURE, "Unable to parse passkey verificaton options.\n"); - /* Continue anyway */ - } - DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification is being enforced from local configuration\n"); - } - DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification setting [%s]\n", - pam_passkey_verification_enum_to_string(verification)); - - *_user_verification = verification; - *_debug_libfido2 = debug_libfido2; - - ret = EOK; - -done: - talloc_free(tmp_ctx); - - return ret; -} - -static bool mapping_is_passkey(TALLOC_CTX *tmp_ctx, - const char *mapping_str) -{ - int ret; - char **mappings; - - if (mapping_str == NULL) { - return false; - } - - ret = split_on_separator(tmp_ctx, (const char *) mapping_str, ':', true, true, - &mappings, NULL); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Incorrectly formatted passkey data [%d]: %s\n", - ret, sss_strerror(ret)); - return false; - } - - if (strcasecmp(mappings[0], "passkey") != 0) { - DEBUG(SSSDBG_TRACE_FUNC, "Mapping data found is not passkey related\n"); - return false; - } - - return true; -} - -errno_t process_passkey_data(TALLOC_CTX *mem_ctx, - struct ldb_message *user_mesg, - const char *domain, - struct pk_child_user_data *_data) -{ - struct ldb_message_element *el; - TALLOC_CTX *tmp_ctx; - int ret; - int num_creds = 0; - char **mappings; - const char **kh_mappings; - const char **public_keys; - const char *domain_name; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ERROR("talloc_new() failed\n"); - return ENOMEM; - } - - el = ldb_msg_find_element(user_mesg, SYSDB_USER_PASSKEY); - if (el == NULL) { - DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found\n"); - ret = ENOENT; - goto done; - } - - kh_mappings = talloc_zero_array(tmp_ctx, const char *, el->num_values + 1); - if (kh_mappings == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); - ret = ENOMEM; - goto done; - } - - public_keys = talloc_zero_array(tmp_ctx, const char *, el->num_values + 1); - if (public_keys == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); - ret = ENOMEM; - goto done; - } - - for (int i = 0; i < el->num_values; i++) { - /* This attribute may contain other mapping data unrelated to passkey. In that case - * let's skip it. For example, AD user altSecurityIdentities may store ssh public key - * or smart card mapping data */ - if ((mapping_is_passkey(tmp_ctx, (const char *)el->values[i].data)) == false) { - continue; - } - ret = split_on_separator(tmp_ctx, (const char *) el->values[i].data, ',', true, true, - &mappings, NULL); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Incorrectly formatted passkey data [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - kh_mappings[num_creds] = talloc_strdup(kh_mappings, mappings[0] + strlen(PASSKEY_PREFIX)); - if (kh_mappings[num_creds] == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup key handle failed.\n"); - ret = ENOMEM; - goto done; - } - - public_keys[num_creds] = talloc_strdup(public_keys, mappings[1]); - if (public_keys[num_creds] == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup public key failed.\n"); - ret = ENOMEM; - goto done; - } - - num_creds++; - } - - if (num_creds == 0) { - ret = ENOENT; - goto done; - } - - domain_name = talloc_strdup(tmp_ctx, domain); - if (domain_name == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup domain failed.\n"); - ret = ENOMEM; - goto done; - } - - _data->domain = talloc_steal(mem_ctx, domain_name); - _data->key_handles = talloc_steal(mem_ctx, kh_mappings); - _data->public_keys = talloc_steal(mem_ctx, public_keys); - _data->num_credentials = num_creds; - - ret = EOK; -done: - talloc_free(tmp_ctx); - - return ret; -} - -void pam_forwarder_passkey_cb(struct tevent_req *req) -{ - struct pam_auth_req *preq = tevent_req_callback_data(req, - struct pam_auth_req); - errno_t ret = EOK; - int child_status; - - ret = pam_passkey_auth_recv(req, &child_status); - talloc_free(req); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - preq->pd->passkey_local_done = true; - - DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); - preq->pd->pam_status = PAM_SUCCESS; - pam_reply(preq); - - return; - -done: - pam_check_user_done(preq, ret); -} - -void pam_passkey_get_user_done(struct tevent_req *req) -{ - int ret; - struct passkey_ctx *pctx; - bool debug_libfido2 = false; - char *domain_name = NULL; - int timeout; - struct cache_req_result *result = NULL; - struct pk_child_user_data *pk_data = NULL; - enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; - - pctx = tevent_req_callback_data(req, struct passkey_ctx); - - ret = pam_passkey_get_mapping_recv(pctx, req, &result); - talloc_zfree(req); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_name_attrs_recv failed [%d]: %s.\n", - ret, sss_strerror(ret)); - goto done; - } - - if (result == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "cache req result == NULL\n"); - ret = ENOMEM; - goto done; - } - - pk_data = talloc_zero(pctx, struct pk_child_user_data); - if (!pk_data) { - DEBUG(SSSDBG_CRIT_FAILURE, "pk_data == NULL\n"); - ret = ENOMEM; - goto done; - } - - /* Use dns_name for AD/IPA - for LDAP fallback to domain->name */ - if (result->domain != NULL) { - domain_name = result->domain->dns_name; - if (domain_name == NULL) { - domain_name = result->domain->name; - } - } - - if (domain_name == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Invalid or missing domain name\n"); - ret = EIO; - goto done; - } - - /* Get passkey data */ - DEBUG(SSSDBG_TRACE_ALL, "Processing passkey data\n"); - ret = process_passkey_data(pk_data, result->msgs[0], domain_name, pk_data); - if (ret != EOK) { - DEBUG(SSSDBG_TRACE_FUNC, - "process_passkey_data failed: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - /* timeout */ - ret = confdb_get_int(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, - &timeout); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - ret = passkey_local_verification(pctx, pctx, pctx->pam_ctx->rctx->cdb, - result->domain->sysdb, result->domain->dns_name, - pk_data, &verification, &debug_libfido2); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to check passkey verification [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - /* Preauth respond with prompt_pin true or false based on user verification */ - if (pctx->pd->cmd == SSS_PAM_PREAUTH) { - const char *prompt_pin = verification == PAM_PASSKEY_VERIFICATION_OFF ? "false" : "true"; - - ret = pam_add_response(pctx->pd, SSS_PAM_PASSKEY_INFO, strlen(prompt_pin) + 1, - (const uint8_t *) prompt_pin); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed. [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - pctx->pd->pam_status = PAM_SUCCESS; - pam_reply(pctx->preq); - talloc_free(pk_data); - return; - } - - req = pam_passkey_auth_send(pctx, pctx->ev, timeout, debug_libfido2, - verification, pctx->pd, pk_data, false); - if (req == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_send failed [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - tevent_req_set_callback(req, pam_forwarder_passkey_cb, pctx->preq); - -done: - if (pk_data != NULL) { - talloc_free(pk_data); - } - - if (ret == ENOENT) { - /* No passkey data, continue through to typical auth flow */ - DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found, skipping passkey auth\n"); - pctx->preq->passkey_data_exists = false; - pam_check_user_search(pctx->preq); - } else if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Unexpected passkey error [%d]: %s.\n", - ret, sss_strerror(ret)); - pctx->preq->passkey_data_exists = false; - pctx->preq->pd->pam_status = PAM_SYSTEM_ERR; - pam_reply(pctx->preq); - } - - return; -} - - -struct pam_passkey_auth_send_state { - struct pam_data *pd; - struct tevent_context *ev; - struct tevent_timer *timeout_handler; - struct sss_child_ctx_old *child_ctx; - struct child_io_fds *io; - const char *logfile; - const char **extra_args; - char *verify_opts; - int timeout; - int child_status; - bool kerberos_pa; -}; - -static errno_t passkey_child_exec(struct tevent_req *req); -static void pam_passkey_auth_done(int child_status, - struct tevent_signal *sige, - void *pvt); - -static int pin_destructor(void *ptr) -{ - uint8_t *pin = talloc_get_type(ptr, uint8_t); - if (pin == NULL) return EOK; - - sss_erase_talloc_mem_securely(pin); - - return EOK; -} - -errno_t get_passkey_child_write_buffer(TALLOC_CTX *mem_ctx, - struct pam_data *pd, - uint8_t **_buf, size_t *_len) -{ - int ret; - uint8_t *buf; - size_t len; - const char *pin = NULL; - - if (pd == NULL || pd->authtok == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing authtok.\n"); - return EINVAL; - } - - if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY || - sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { - ret = sss_authtok_get_passkey_pin(pd->authtok, &pin, &len); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_passkey_pin failed [%d]: %s\n", - ret, sss_strerror(ret)); - return ret; - } - - if (pin == NULL || len == 0) { - DEBUG(SSSDBG_OP_FAILURE, "Missing PIN.\n"); - return EINVAL; - } - - buf = talloc_size(mem_ctx, len); - if (buf == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); - return ENOMEM; - } - - talloc_set_destructor((void *) buf, pin_destructor); - - safealign_memcpy(buf, pin, len, NULL); - } else { - DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported authtok type [%d].\n", - sss_authtok_get_type(pd->authtok)); - return EINVAL; - } - - *_len = len; - *_buf = buf; - - return EOK; -} - -static void pam_passkey_child_read_data(struct tevent_req *subreq) -{ - uint8_t *buf; - ssize_t buf_len; - char *str; - struct tevent_req *req = tevent_req_callback_data(subreq, - struct tevent_req); - struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); - int ret; - - ret = read_pipe_recv(subreq, state, &buf, &buf_len); - talloc_zfree(subreq); - if (ret != EOK) { - tevent_req_error(req, ret); - return; - } - - str = malloc(sizeof(char) * buf_len); - if (str == NULL) { - return; - } - - snprintf(str, buf_len, "%s", buf); - - sss_authtok_set_passkey_reply(state->pd->authtok, str, 0); - - free(str); - - tevent_req_done(req); - return; -} - -static void passkey_child_write_done(struct tevent_req *subreq) -{ - struct tevent_req *req = tevent_req_callback_data(subreq, - struct tevent_req); - struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); - - int ret; - - DEBUG(SSSDBG_TRACE_LIBS, "Sending passkey data complete\n"); - - ret = write_pipe_recv(subreq); - talloc_zfree(subreq); - if (ret != EOK) { - tevent_req_error(req, ret); - return; - } - - PIPE_FD_CLOSE(state->io->write_to_child_fd); - - if (state->kerberos_pa) { - /* Read data back from passkey child */ - subreq = read_pipe_send(state, state->ev, state->io->read_from_child_fd); - if (subreq == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "read_pipe_send failed.\n"); - return; - } - - tevent_req_set_callback(subreq, pam_passkey_child_read_data, req); - } -} - -errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, - struct pk_child_user_data *pk_data, - bool kerberos_pa, - char **_result_kh, - char **_result_pk) -{ - errno_t ret; - char *result_kh = NULL; - char *result_pk = NULL; - - result_kh = talloc_strdup(mem_ctx, pk_data->key_handles[0]); - if (!kerberos_pa) { - result_pk = talloc_strdup(mem_ctx, pk_data->public_keys[0]); - } - - for (int i = 1; i < pk_data->num_credentials; i++) { - result_kh = talloc_strdup_append(result_kh, ","); - if (result_kh == NULL) { - ret = ENOMEM; - goto done; - } - - result_kh = talloc_strdup_append(result_kh, pk_data->key_handles[i]); - if (result_kh == NULL) { - ret = ENOMEM; - goto done; - } - - if (!kerberos_pa) { - result_pk = talloc_strdup_append(result_pk, ","); - if (result_pk == NULL) { - ret = ENOMEM; - goto done; - } - - result_pk = talloc_strdup_append(result_pk, pk_data->public_keys[i]); - if (result_kh == NULL || result_pk == NULL) { - ret = ENOMEM; - goto done; - } - } - } - - *_result_kh = result_kh; - *_result_pk = result_pk; - - ret = EOK; -done: - return ret; -} - -struct tevent_req * -pam_passkey_auth_send(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - int timeout, - bool debug_libfido2, - enum passkey_user_verification verification, - struct pam_data *pd, - struct pk_child_user_data *pk_data, - bool kerberos_pa) -{ - struct tevent_req *req; - struct pam_passkey_auth_send_state *state; - size_t arg_c = 0; - char *result_kh; - char *result_pk; - int num_args; - int ret; - - req = tevent_req_create(mem_ctx, &state, struct pam_passkey_auth_send_state); - if (req == NULL) { - return NULL; - } - - state->pd = pd; - state->ev = ev; - - state->timeout = timeout; - state->kerberos_pa = kerberos_pa; - state->logfile = PASSKEY_CHILD_LOG_FILE; - state->io = talloc(state, struct child_io_fds); - if (state->io == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc child fds failed.\n"); - ret = ENOMEM; - goto done; - } - state->io->write_to_child_fd = -1; - state->io->read_from_child_fd = -1; - talloc_set_destructor((void *) state->io, child_io_destructor); - - num_args = 11; - state->extra_args = talloc_zero_array(state, const char *, num_args + 1); - if (state->extra_args == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); - ret = ENOMEM; - goto done; - } - - switch (verification) { - case PAM_PASSKEY_VERIFICATION_ON: - state->extra_args[arg_c++] = "--user-verification=true"; - DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification true\n"); - break; - case PAM_PASSKEY_VERIFICATION_OFF: - state->extra_args[arg_c++] = "--user-verification=false"; - DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification false\n"); - break; - default: - DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification unset\n"); - break; - } - - ret = pam_passkey_concatenate_keys(state, pk_data, state->kerberos_pa, - &result_kh, &result_pk); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_concatenate keys failed - [%d]: [%s]\n", - ret, sss_strerror(ret)); - goto done; - } - - - if (state->kerberos_pa) { - state->extra_args[arg_c++] = pk_data->crypto_challenge; - state->extra_args[arg_c++] = "--cryptographic-challenge"; - state->extra_args[arg_c++] = result_kh; - state->extra_args[arg_c++] = "--key-handle"; - state->extra_args[arg_c++] = pk_data->domain; - state->extra_args[arg_c++] = "--domain"; - state->extra_args[arg_c++] = "--get-assert"; - } else { - state->extra_args[arg_c++] = result_pk; - state->extra_args[arg_c++] = "--public-key"; - state->extra_args[arg_c++] = result_kh; - state->extra_args[arg_c++] = "--key-handle"; - state->extra_args[arg_c++] = pk_data->domain; - state->extra_args[arg_c++] = "--domain"; - state->extra_args[arg_c++] = state->pd->user; - state->extra_args[arg_c++] = "--username"; - state->extra_args[arg_c++] = "--authenticate"; - } - - ret = passkey_child_exec(req); - -done: - if (ret == EOK) { - tevent_req_done(req); - tevent_req_post(req, ev); - } else if (ret != EAGAIN) { - tevent_req_error(req, ret); - tevent_req_post(req, ev); - } - - return req; -} - -static void -passkey_child_timeout(struct tevent_context *ev, - struct tevent_timer *te, - struct timeval tv, void *pvt) -{ - struct tevent_req *req = - talloc_get_type(pvt, struct tevent_req); - struct pam_passkey_auth_send_state *state = - tevent_req_data(req, struct pam_passkey_auth_send_state); - - DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for passkey child, " - "consider increasing passkey_child_timeout\n"); - child_handler_destroy(state->child_ctx); - state->child_ctx = NULL; - state->child_status = ETIMEDOUT; - tevent_req_error(req, ERR_PASSKEY_CHILD_TIMEOUT); -} - -static errno_t passkey_child_exec(struct tevent_req *req) -{ - struct pam_passkey_auth_send_state *state; - struct tevent_req *subreq; - int pipefd_from_child[2] = PIPE_INIT; - int pipefd_to_child[2] = PIPE_INIT; - pid_t child_pid; - uint8_t *write_buf = NULL; - size_t write_buf_len = 0; - struct timeval tv; - int ret; - - state = tevent_req_data(req, struct pam_passkey_auth_send_state); - - ret = pipe(pipefd_from_child); - if (ret == -1) { - ret = errno; - DEBUG(SSSDBG_CRIT_FAILURE, - "pipe failed [%d][%s].\n", ret, strerror(ret)); - goto done; - } - ret = pipe(pipefd_to_child); - if (ret == -1) { - ret = errno; - DEBUG(SSSDBG_CRIT_FAILURE, - "pipe failed [%d][%s].\n", ret, strerror(ret)); - goto done; - } - - child_pid = fork(); - if (child_pid == 0) { /* child */ - exec_child_ex(state, pipefd_to_child, pipefd_from_child, - PASSKEY_CHILD_PATH, state->logfile, state->extra_args, - false, STDIN_FILENO, STDOUT_FILENO); - /* We should never get here */ - ret = errno; - DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec passkey child\n"); - goto done; - } else if (child_pid > 0) { /* parent */ - state->io->read_from_child_fd = pipefd_from_child[0]; - PIPE_FD_CLOSE(pipefd_from_child[1]); - sss_fd_nonblocking(state->io->read_from_child_fd); - - state->io->write_to_child_fd = pipefd_to_child[1]; - PIPE_FD_CLOSE(pipefd_to_child[0]); - sss_fd_nonblocking(state->io->write_to_child_fd); - - /* Set up SIGCHLD handler */ - if (state->kerberos_pa) { - ret = child_handler_setup(state->ev, child_pid, NULL, req, &state->child_ctx); - } else { - ret = child_handler_setup(state->ev, child_pid, - pam_passkey_auth_done, req, - &state->child_ctx); - } - - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", - ret, sss_strerror(ret)); - ret = ERR_PASSKEY_CHILD; - goto done; - } - - /* Set up timeout handler */ - tv = tevent_timeval_current_ofs(state->timeout, 0); - state->timeout_handler = tevent_add_timer(state->ev, req, tv, - passkey_child_timeout, req); - if (state->timeout_handler == NULL) { - ret = ERR_PASSKEY_CHILD; - goto done; - } - - /* PIN is needed */ - if (sss_authtok_get_type(state->pd->authtok) != SSS_AUTHTOK_TYPE_EMPTY) { - ret = get_passkey_child_write_buffer(state, state->pd, &write_buf, - &write_buf_len); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "get_passkey_child_write_buffer failed [%d]: %s.\n", - ret, sss_strerror(ret)); - goto done; - } - } - - if (write_buf_len != 0) { - subreq = write_pipe_send(state, state->ev, write_buf, write_buf_len, - state->io->write_to_child_fd); - if (subreq == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "write_pipe_send failed.\n"); - ret = ERR_PASSKEY_CHILD; - goto done; - } - tevent_req_set_callback(subreq, passkey_child_write_done, req); - } - /* Now either wait for the timeout to fire or the child to finish */ - } else { /* error */ - ret = errno; - DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d][%s].\n", - ret, sss_strerror(ret)); - goto done; - } - - return EAGAIN; - -done: - if (ret != EOK) { - PIPE_CLOSE(pipefd_from_child); - PIPE_CLOSE(pipefd_to_child); - } - - return ret; -} - -errno_t pam_passkey_auth_recv(struct tevent_req *req, - int *child_status) -{ - struct pam_passkey_auth_send_state *state = - tevent_req_data(req, struct pam_passkey_auth_send_state); - - *child_status = state->child_status; - - TEVENT_REQ_RETURN_ON_ERROR(req); - - return EOK; -} - -errno_t decode_pam_passkey_msg(TALLOC_CTX *mem_ctx, - uint8_t *buf, - size_t len, - struct pk_child_user_data **_data) -{ - - size_t p = 0; - size_t pctr = 0; - errno_t ret; - size_t offset; - struct pk_child_user_data *data = NULL; - TALLOC_CTX *tmp_ctx; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - return ENOMEM; - } - - data = talloc_zero(tmp_ctx, struct pk_child_user_data); - if (data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to talloc passkey data.\n"); - ret = ENOMEM; - goto done; - } - - data->user_verification = talloc_strdup(data, (char *) &buf[p]); - if (data->user_verification == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey prompt.\n"); - ret = ENOMEM; - goto done; - } - - offset = strlen(data->user_verification) + 1; - if (offset >= len) { - DEBUG(SSSDBG_OP_FAILURE, "passkey prompt offset failure.\n"); - ret = EIO; - goto done; - } - - data->crypto_challenge = talloc_strdup(data, (char *) &buf[p + offset]); - if (data->crypto_challenge == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey challenge.\n"); - ret = ENOMEM; - goto done; - } - - offset += strlen(data->crypto_challenge) + 1; - if (offset >= len) { - DEBUG(SSSDBG_OP_FAILURE, "passkey challenge offset failure.\n"); - ret = EIO; - goto done; - } - - - data->domain = talloc_strdup(data, (char *) &buf[p] + offset); - if (data->domain == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey domain.\n"); - ret = ENOMEM; - goto done; - } - - offset += strlen(data->domain) + 1; - if (offset >= len) { - DEBUG(SSSDBG_OP_FAILURE, "passkey domain offset failure.\n"); - ret = EIO; - goto done; - } - - SAFEALIGN_COPY_UINT32(&data->num_credentials, &buf[p + offset], &pctr); - size_t list_sz = (size_t) data->num_credentials; - - offset += sizeof(uint32_t); - - data->key_handles = talloc_zero_array(data, const char *, list_sz); - - for (int i = 0; i < list_sz; i++) { - data->key_handles[i] = talloc_strdup(data->key_handles, (char *) &buf[p + offset]); - if (data->key_handles[i] == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey list.\n"); - ret = ENOMEM; - goto done; - } - - offset += strlen(data->key_handles[i]) + 1; - } - - *_data = talloc_steal(mem_ctx, data); - - ret = EOK; -done: - talloc_free(tmp_ctx); - return ret; -} - -errno_t save_passkey_data(TALLOC_CTX *mem_ctx, - struct pam_ctx *pctx, - struct pk_child_user_data *data, - struct pam_auth_req *preq) -{ - char *pk_key; - errno_t ret; - TALLOC_CTX *tmp_ctx; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - return ENOMEM; - } - - /* Passkey data (pk_table_data) is stolen onto client ctx, it will - * be freed when the client closes, and the sss_ptr_hash interface - * takes care of automatically removing it from the hash table then */ - pctx->pk_table_data = talloc_zero(tmp_ctx, struct pam_passkey_table_data); - if (pctx->pk_table_data == NULL) { - return ENOMEM; - } - - if (pctx->pk_table_data->table == NULL) { - pctx->pk_table_data->table = sss_ptr_hash_create(pctx->pk_table_data, - NULL, NULL); - if (pctx->pk_table_data->table == NULL) { - ret = ENOMEM; - goto done; - } - } - - pk_key = talloc_asprintf(tmp_ctx, "%s", data->crypto_challenge); - if (pk_key == NULL) { - ret = ENOMEM; - goto done; - } - - pctx->pk_table_data->key = talloc_strdup(pctx->pk_table_data, pk_key); - if (pctx->pk_table_data->key == NULL) { - ret = ENOMEM; - goto done; - } - - ret = sss_ptr_hash_add(pctx->pk_table_data->table, pk_key, data, - struct pk_child_user_data); - if (ret == EEXIST) { - DEBUG(SSSDBG_TRACE_FUNC, "pk_table key [%s] already exists\n", - pk_key); - goto done; - } else if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Unable to add pk data to hash table " - "[%d]: %s\n", ret, sss_strerror(ret)); - goto done; - } - - talloc_steal(mem_ctx, pctx->pk_table_data); - pctx->pk_table_data->data = talloc_steal(mem_ctx, data); - - ret = EOK; - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t pam_eval_passkey_response(struct pam_ctx *pctx, - struct pam_data *pd, - struct pam_auth_req *preq, - bool *_pk_preauth_done) -{ - struct response_data *pk_resp; - struct pk_child_user_data *pk_data; - errno_t ret; - TALLOC_CTX *tmp_ctx; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - return ENOMEM; - } - - pk_resp = pd->resp_list; - - while (pk_resp != NULL) { - switch (pk_resp->type) { - case SSS_PAM_PASSKEY_KRB_INFO: - if (!pctx->passkey_auth) { - /* Passkey auth is disabled. To avoid passkey prompts appearing, - * don't send SSS_PAM_PASSKEY_KRB_INFO to the client and - * add a dummy response to fallback to normal auth */ - pk_resp->do_not_send_to_client = true; - ret = pam_add_response(pd, SSS_OTP, 0, NULL); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); - goto done; - } - break; - } - ret = decode_pam_passkey_msg(tmp_ctx, pk_resp->data, pk_resp->len, &pk_data); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to decode passkey msg\n"); - ret = EIO; - goto done; - } - - ret = save_passkey_data(preq->cctx, pctx, pk_data, preq); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to save passkey msg\n"); - ret = EIO; - goto done; - } - break; - /* Passkey non-kerberos preauth has already run */ - case SSS_PAM_PASSKEY_INFO: - *_pk_preauth_done = true; - default: - break; - } - pk_resp = pk_resp->next; - } - - ret = EOK; -done: - talloc_free(tmp_ctx); - - return ret; -} - - -static void -pam_passkey_auth_done(int child_status, - struct tevent_signal *sige, - void *pvt) -{ - struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); - - struct pam_passkey_auth_send_state *state = - tevent_req_data(req, struct pam_passkey_auth_send_state); - state->child_status = WEXITSTATUS(child_status); - if (WIFEXITED(child_status)) { - if (WEXITSTATUS(child_status) != 0) { - DEBUG(SSSDBG_OP_FAILURE, - PASSKEY_CHILD_PATH " failed with status [%d]. Check passkey_child" - " logs for more information.\n", - WEXITSTATUS(child_status)); - tevent_req_error(req, ERR_PASSKEY_CHILD); - return; - } - } else if (WIFSIGNALED(child_status)) { - DEBUG(SSSDBG_OP_FAILURE, - PASSKEY_CHILD_PATH " was terminated by signal [%d]. Check passkey_child" - " logs for more information.\n", - WTERMSIG(child_status)); - tevent_req_error(req, ECHILD); - return; - } - - DEBUG(SSSDBG_TRACE_FUNC, "passkey data is valid. Mark done\n"); - - tevent_req_done(req); - return; -} - -bool may_do_passkey_auth(struct pam_ctx *pctx, - struct pam_data *pd) -{ -#ifndef BUILD_PASSKEY - DEBUG(SSSDBG_TRACE_FUNC, "Passkey auth not possible, SSSD built without passkey support!\n"); - return false; -#else - if (!pctx->passkey_auth) { - return false; - } - - if (pd->cmd != SSS_PAM_PREAUTH && pd->cmd != SSS_PAM_AUTHENTICATE) { - return false; - } - - if (pd->service == NULL || *pd->service == '\0') { - return false; - } - - return true; -#endif /* BUILD_PASSKEY */ -} +/* + SSSD + + PAM Responder - passkey related requests + + Copyright (C) Justin Stephenson 2022 + + 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 . +*/ + +#include +#include "util/child_common.h" +#include "util/authtok.h" +#include "db/sysdb.h" +#include "db/sysdb_passkey_user_verification.h" +#include "responder/pam/pamsrv.h" + +#include "responder/pam/pamsrv_passkey.h" + +struct pam_passkey_verification_enum_str { + enum passkey_user_verification verification; + const char *option; +}; + +struct pam_passkey_table_data { + hash_table_t *table; + char *key; + struct pk_child_user_data *data; +}; + +struct pam_passkey_verification_enum_str pam_passkey_verification_enum_str[] = { + { PAM_PASSKEY_VERIFICATION_ON, "on" }, + { PAM_PASSKEY_VERIFICATION_OFF, "off" }, + { PAM_PASSKEY_VERIFICATION_OMIT, "unset" }, + { PAM_PASSKEY_VERIFICATION_INVALID, NULL } +}; + +#define PASSKEY_PREFIX "passkey:" +#define USER_VERIFICATION "user_verification=" +#define USER_VERIFICATION_LEN (sizeof(USER_VERIFICATION) -1) + +const char *pam_passkey_verification_enum_to_string(enum passkey_user_verification verification) +{ + size_t c; + + for (c = 0 ; pam_passkey_verification_enum_str[c].option != NULL; c++) { + if (pam_passkey_verification_enum_str[c].verification == verification) { + return pam_passkey_verification_enum_str[c].option; + } + } + + return "(NULL)"; +} + +struct passkey_ctx { + struct pam_ctx *pam_ctx; + struct tevent_context *ev; + struct pam_data *pd; + struct pam_auth_req *preq; +}; + +void pam_forwarder_passkey_cb(struct tevent_req *req); + +void pam_forwarder_get_devinfo_cb(struct tevent_req *req); +void pam_passkey_get_devinfo(struct tevent_req *req); + +errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, + struct pk_child_user_data *pk_data, + enum passkey_auth_action auth_action, + char **_result_kh, + char **_result_ph); + +struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct passkey_ctx *pctx); +void pam_passkey_get_user_done(struct tevent_req *req); +void pam_passkey_get_mapping_done(struct tevent_req *req); +errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req_result **_result); + +struct passkey_get_mapping_state { + struct pam_data *pd; + struct cache_req_result *result; +}; + +void passkey_kerberos_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + errno_t ret = EOK; + int child_status; + + ret = pam_passkey_auth_recv(req, &child_status); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); + + pam_check_user_search(preq); + +done: + pam_check_user_done(preq, ret); +} + +errno_t passkey_kerberos(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq) +{ + errno_t ret; + const char *prompt; + const char *key; + const char *pin; + size_t pin_len; + struct pk_child_user_data *data; + struct tevent_req *req; + int timeout; + char *verify_opts; + bool debug_libfido2; + enum passkey_user_verification verification; + + ret = sss_authtok_get_passkey(preq, preq->pd->authtok, + &prompt, &key, &pin, &pin_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failure to get passkey authtok\n"); + return EIO; + } + + if (prompt == NULL || key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Passkey prompt and key are missing or invalid.\n"); + return EIO; + } + + data = sss_ptr_hash_lookup(pctx->pk_table_data->table, key, + struct pk_child_user_data); + if (data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to lookup passkey authtok\n"); + return EIO; + } + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_string(pctx->rctx->cdb, preq, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, + &verify_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Always use verification sent from passkey krb5 plugin */ + if (strcasecmp(data->user_verification, "false") == 0) { + verification = PAM_PASSKEY_VERIFICATION_OFF; + } else { + verification = PAM_PASSKEY_VERIFICATION_ON; + } + + ret = confdb_get_bool(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, + &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + req = pam_passkey_auth_send(preq->cctx, preq->cctx->ev, timeout, debug_libfido2, + verification, pd, data, + PASSKEY_KERBEROS_AUTH ); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "passkey auth send failed [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, passkey_kerberos_cb, preq); + + ret = EAGAIN; + +done: + + return ret; + +} + + +errno_t passkey_kerberos_get_devinfo(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq) +{ + errno_t ret; + const char *prompt; + const char *key; + const char *pin; + size_t pin_len; + struct pk_child_user_data *data; + struct tevent_req *req; + int timeout; + char *verify_opts; + bool debug_libfido2; + enum passkey_user_verification verification; + + if (pd->cmd != SSS_PAM_PASSKEY_PREAUTH) { + DEBUG(SSSDBG_OP_FAILURE, "passkey_kerberos_get_devinfo : invalid command\n"); + ret = PAM_SYSTEM_ERR; + goto done; + } + + ret = sss_authtok_get_passkey(preq, preq->pd->authtok, + &prompt, &key, &pin, &pin_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failure to get passkey authtok\n"); + return EIO; + } + + if (prompt == NULL || key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Passkey prompt and key are missing or invalid.\n"); + return EIO; + } + + DEBUG(SSSDBG_TRACE_FUNC, "lookup passkey auth keys: %s\nprompt: %s\n", + key ? key : "NULL", prompt ? prompt : "NULL"); + + data = sss_ptr_hash_lookup(pctx->pk_table_data->table, key, + struct pk_child_user_data); + if (data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to lookup passkey authtok\n"); + return EIO; + } + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_string(pctx->rctx->cdb, preq, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, + &verify_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Always use verification sent from passkey krb5 plugin */ + if (strcasecmp(data->user_verification, "false") == 0) { + verification = PAM_PASSKEY_VERIFICATION_OFF; + } else { + verification = PAM_PASSKEY_VERIFICATION_ON; + } + + ret = confdb_get_bool(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, + &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + req = pam_passkey_auth_send(preq->cctx, preq->cctx->ev, timeout, debug_libfido2, + verification, pd, data, + PASSKEY_GET_DEVINFO); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "passkey auth send failed [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, pam_forwarder_get_devinfo_cb, preq); + + ret = EAGAIN; + +done: + + return ret; + +} + +errno_t passkey_local(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_ctx *pam_ctx, + struct pam_auth_req *preq, + struct pam_data *pd) +{ + struct tevent_req *req; + struct passkey_ctx *pctx; + errno_t ret; + + pctx = talloc_zero(mem_ctx, struct passkey_ctx); + if (pctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pctx == NULL\n"); + return ENOMEM; + } + + pctx->pd = pd; + pctx->pam_ctx = pam_ctx; + pctx->ev = ev; + pctx->preq = preq; + + DEBUG(SSSDBG_TRACE_FUNC, "Checking for passkey authentication data\n"); + + req = pam_passkey_get_mapping_send(mem_ctx, pctx->ev, pctx); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_get_mapping_send failed.\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, pam_passkey_get_user_done, pctx); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + talloc_free(pctx); + } + + return ret; +} + +errno_t passkey_local_get_devinfo(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_ctx *pam_ctx, + struct pam_auth_req *preq, + struct pam_data *pd) +{ + struct tevent_req *req; + struct passkey_ctx *pctx; + errno_t ret; + + pctx = talloc_zero(mem_ctx, struct passkey_ctx); + if (pctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pctx == NULL\n"); + return ENOMEM; + } + + pctx->pd = pd; + pctx->pam_ctx = pam_ctx; + pctx->ev = ev; + pctx->preq = preq; + + if (pctx->pd->cmd != SSS_PAM_PASSKEY_PREAUTH) { + DEBUG(SSSDBG_OP_FAILURE, "passkey_local_get_devinfo : invalid command\n"); + ret = PAM_SYSTEM_ERR; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Checking for passkey authentication data\n"); + + req = pam_passkey_get_mapping_send(mem_ctx, pctx->ev, pctx); + if (req == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "pam_passkey_get_mapping_send failed.\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, pam_passkey_get_devinfo, pctx); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + talloc_free(pctx); + } + + return ret; +} +struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct passkey_ctx *pk_ctx) +{ + + struct passkey_get_mapping_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + int ret; + static const char *attrs[] = { SYSDB_NAME, SYSDB_USER_PASSKEY, NULL }; + + req = tevent_req_create(mem_ctx, &state, struct passkey_get_mapping_state); + if (req == NULL) { + ret = ENOMEM; + goto done; + } + + subreq = cache_req_user_by_name_attrs_send(state, pk_ctx->ev, + pk_ctx->pam_ctx->rctx, + pk_ctx->pam_ctx->rctx->ncache, 0, + pk_ctx->pd->domain, + pk_ctx->pd->user, attrs); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, pam_passkey_get_mapping_done, req); + + return req; + +done: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +void pam_passkey_get_mapping_done(struct tevent_req *subreq) +{ + struct cache_req_result *result; + struct tevent_req *req; + struct passkey_get_mapping_state *state; + + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct passkey_get_mapping_state); + + ret = cache_req_user_by_name_attrs_recv(state, subreq, &result); + state->result = result; + + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req_result **_result) +{ + struct passkey_get_mapping_state *state = NULL; + + state = tevent_req_data(req, struct passkey_get_mapping_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_result = talloc_steal(mem_ctx, state->result); + return EOK; +} + +errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, + const char *verify_opts, + enum passkey_user_verification *_user_verification) +{ + int ret; + TALLOC_CTX *tmp_ctx; + char **opts; + size_t c; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + if (verify_opts == NULL) { + ret = EOK; + goto done; + } + + ret = split_on_separator(tmp_ctx, verify_opts, ',', true, true, &opts, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d], %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + for (c = 0; opts[c] != NULL; c++) { + if (strncasecmp(opts[c], USER_VERIFICATION, USER_VERIFICATION_LEN) == 0) { + if (strcasecmp("true", &opts[c][USER_VERIFICATION_LEN]) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "user_verification set to true.\n"); + *_user_verification = PAM_PASSKEY_VERIFICATION_ON; + } else if (strcasecmp("false", &opts[c][USER_VERIFICATION_LEN]) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "user_verification set to false.\n"); + *_user_verification = PAM_PASSKEY_VERIFICATION_OFF; + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unsupported passkey verification option [%s], " \ + "skipping.\n", opts[c]); + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t passkey_local_verification(TALLOC_CTX *mem_ctx, + struct passkey_ctx *pctx, + struct confdb_ctx *cdb, + struct sysdb_ctx *sysdb, + const char *domain_name, + struct pk_child_user_data *pk_data, + enum passkey_user_verification *_user_verification, + bool *_debug_libfido2) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char *verification_from_ldap; + char *verify_opts = NULL; + bool debug_libfido2 = false; + enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_domain_get_passkey_user_verification(tmp_ctx, sysdb, domain_name, + &verification_from_ldap); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkeyUserVerification from sysdb: [%d]: %s\n", + ret, sss_strerror(ret)); + /* This is expected for AD and LDAP */ + ret = EOK; + goto done; + } + + ret = confdb_get_bool(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, + &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* If require user verification setting is set in LDAP, use it */ + if (verification_from_ldap != NULL) { + if (strcasecmp(verification_from_ldap, "true") == 0) { + verification = PAM_PASSKEY_VERIFICATION_ON; + } else if (strcasecmp(verification_from_ldap, "false") == 0) { + verification = PAM_PASSKEY_VERIFICATION_OFF; + } + DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification is being enforced from LDAP\n"); + } else { + /* No verification set in LDAP, fallback to local sssd.conf setting */ + ret = confdb_get_string(pctx->pam_ctx->rctx->cdb, tmp_ctx, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, + &verify_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + + ret = read_passkey_conf_verification(tmp_ctx, verify_opts, &verification); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to parse passkey verificaton options.\n"); + /* Continue anyway */ + } + DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification is being enforced from local configuration\n"); + } + DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification setting [%s]\n", + pam_passkey_verification_enum_to_string(verification)); + + *_user_verification = verification; + *_debug_libfido2 = debug_libfido2; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static bool mapping_is_passkey(TALLOC_CTX *tmp_ctx, + const char *mapping_str) +{ + int ret; + char **mappings; + + if (mapping_str == NULL) { + return false; + } + + ret = split_on_separator(tmp_ctx, (const char *) mapping_str, ':', true, true, + &mappings, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Incorrectly formatted passkey data [%d]: %s\n", + ret, sss_strerror(ret)); + return false; + } + + if (strcasecmp(mappings[0], "passkey") != 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Mapping data found is not passkey related\n"); + return false; + } + + return true; +} + +errno_t process_passkey_data(TALLOC_CTX *mem_ctx, + struct ldb_message *user_mesg, + const char *domain, + struct pk_child_user_data *_data) +{ + struct ldb_message_element *el; + TALLOC_CTX *tmp_ctx; + int ret; + int num_creds = 0; + char **mappings; + const char **kh_mappings; + const char **public_keys; + const char *domain_name; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ERROR("talloc_new() failed\n"); + return ENOMEM; + } + + el = ldb_msg_find_element(user_mesg, SYSDB_USER_PASSKEY); + if (el == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found\n"); + ret = ENOENT; + goto done; + } + + kh_mappings = talloc_zero_array(tmp_ctx, const char *, el->num_values + 1); + if (kh_mappings == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + public_keys = talloc_zero_array(tmp_ctx, const char *, el->num_values + 1); + if (public_keys == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + for (int i = 0; i < el->num_values; i++) { + /* This attribute may contain other mapping data unrelated to passkey. In that case + * let's skip it. For example, AD user altSecurityIdentities may store ssh public key + * or smart card mapping data */ + if ((mapping_is_passkey(tmp_ctx, (const char *)el->values[i].data)) == false) { + continue; + } + ret = split_on_separator(tmp_ctx, (const char *) el->values[i].data, ',', true, true, + &mappings, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Incorrectly formatted passkey data [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + kh_mappings[num_creds] = talloc_strdup(kh_mappings, mappings[0] + strlen(PASSKEY_PREFIX)); + if (kh_mappings[num_creds] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup key handle failed.\n"); + ret = ENOMEM; + goto done; + } + + public_keys[num_creds] = talloc_strdup(public_keys, mappings[1]); + if (public_keys[num_creds] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup public key failed.\n"); + ret = ENOMEM; + goto done; + } + + num_creds++; + } + + if (num_creds == 0) { + ret = ENOENT; + goto done; + } + + domain_name = talloc_strdup(tmp_ctx, domain); + if (domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup domain failed.\n"); + ret = ENOMEM; + goto done; + } + + _data->domain = talloc_steal(mem_ctx, domain_name); + _data->key_handles = talloc_steal(mem_ctx, kh_mappings); + _data->public_keys = talloc_steal(mem_ctx, public_keys); + _data->num_credentials = num_creds; + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + +void pam_forwarder_passkey_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + errno_t ret = EOK; + int child_status; + + ret = pam_passkey_auth_recv(req, &child_status); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + preq->pd->passkey_local_done = true; + + DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); + preq->pd->pam_status = PAM_SUCCESS; + pam_reply(preq); + + return; + +done: + pam_check_user_done(preq, ret); +} + +#define PASSKEY_PINUV "/var/run/passkey-pinuv" +#define PASSKEY_PINONLY "/var/run/passkey-pinonly" +#define PASSKEY_NODEV "/var/run/passkey-nodev" + +void pam_forwarder_get_devinfo_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + errno_t ret = EOK; + const char* devinfo = "errdev"; + int child_status; + + ret = pam_passkey_auth_recv(req, &child_status); + talloc_free(req); + if (ret != EOK) + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_recv for devinfo failed [%d]: %s\n", + ret, sss_strerror(ret)); + + DEBUG(SSSDBG_TRACE_FUNC, "passkey child devinfo finished with status [%d]\n", child_status); + + if (child_status != 0) { + int fd; + devinfo = "nodev"; + fd = creat(PASSKEY_NODEV, 0000); + close(fd); + } else { + (void)remove (PASSKEY_NODEV); + if (access(PASSKEY_PINUV, F_OK) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "devinfo pinuv file indicator\n"); + devinfo = "pinuv"; + } else if (access(PASSKEY_PINONLY, F_OK) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "devinfo pinonly file indicator\n"); + devinfo = "pinonly"; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "devinfo invalid file indicator\n"); + devinfo = "errdev"; + } + } + + /* remove indicators + * They will be rebuild according the device capabilities + * (see passkey_child) + * during authentication request. + */ + (void)remove (PASSKEY_PINUV); + (void)remove (PASSKEY_PINONLY); + + ret = pam_add_response(preq->pd, SSS_PAM_PASSKEY_DEVINFO, strlen(devinfo) + 1, + (const uint8_t *) devinfo); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "devinfo: pam_add_response failed. [%d]: %s\n", + ret, sss_strerror(ret)); + } + + + preq->pd->pam_status = PAM_SUCCESS; + + pam_reply(preq); + + return; +} + +void pam_passkey_get_user_done(struct tevent_req *req) +{ + int ret; + struct passkey_ctx *pctx; + bool debug_libfido2 = false; + char *domain_name = NULL; + int timeout; + struct cache_req_result *result = NULL; + struct pk_child_user_data *pk_data = NULL; + enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; + + pctx = tevent_req_callback_data(req, struct passkey_ctx); + + ret = pam_passkey_get_mapping_recv(pctx, req, &result); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_name_attrs_recv failed [%d]: %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + if (result == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "cache req result == NULL\n"); + ret = ENOMEM; + goto done; + } + + pk_data = talloc_zero(pctx, struct pk_child_user_data); + if (!pk_data) { + DEBUG(SSSDBG_CRIT_FAILURE, "pk_data == NULL\n"); + ret = ENOMEM; + goto done; + } + + /* Use dns_name for AD/IPA - for LDAP fallback to domain->name */ + if (result->domain != NULL) { + domain_name = result->domain->dns_name; + if (domain_name == NULL) { + domain_name = result->domain->name; + } + } + + if (domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid or missing domain name\n"); + ret = EIO; + goto done; + } + + /* Get passkey data */ + DEBUG(SSSDBG_TRACE_ALL, "Processing passkey data\n"); + ret = process_passkey_data(pk_data, result->msgs[0], domain_name, pk_data); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "process_passkey_data failed: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* timeout */ + ret = confdb_get_int(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = passkey_local_verification(pctx, pctx, pctx->pam_ctx->rctx->cdb, + result->domain->sysdb, result->domain->dns_name, + pk_data, &verification, &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to check passkey verification [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Preauth respond with prompt_pin true or false based on user verification */ + if (pctx->pd->cmd == SSS_PAM_PREAUTH) { + const char *prompt_pin = verification == PAM_PASSKEY_VERIFICATION_OFF ? "false" : "true"; + + ret = pam_add_response(pctx->pd, SSS_PAM_PASSKEY_INFO, strlen(prompt_pin) + 1, + (const uint8_t *) prompt_pin); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed. [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + pctx->pd->pam_status = PAM_SUCCESS; + pam_reply(pctx->preq); + talloc_free(pk_data); + return; + } + + req = pam_passkey_auth_send(pctx, pctx->ev, timeout, debug_libfido2, + verification, pctx->pd, pk_data, + PASSKEY_LOCAL_AUTH); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_send failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, pam_forwarder_passkey_cb, pctx->preq); + +done: + if (pk_data != NULL) { + talloc_free(pk_data); + } + + if (ret == ENOENT) { + /* No passkey data, continue through to typical auth flow */ + DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found, skipping passkey auth\n"); + pctx->preq->passkey_data_exists = false; + pam_check_user_search(pctx->preq); + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected passkey error [%d]: %s.\n", + ret, sss_strerror(ret)); + pctx->preq->passkey_data_exists = false; + pctx->preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(pctx->preq); + } + + return; +} + +void pam_passkey_get_devinfo(struct tevent_req *req) +{ + int ret; + struct passkey_ctx *pctx; + bool debug_libfido2 = false; + char *domain_name = NULL; + int timeout; + struct cache_req_result *result = NULL; + struct pk_child_user_data *pk_data = NULL; + enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; + + + DEBUG(SSSDBG_TRACE_ALL, "pam_passkey_get_devinfo... \n"); + + pctx = tevent_req_callback_data(req, struct passkey_ctx); + + + ret = pam_passkey_get_mapping_recv(pctx, req, &result); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "devinfo cache_req_user_by_name_attrs_recv failed [%d]: %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + if (result == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "idevinfo cache req result == NULL\n"); + ret = ENOMEM; + goto done; + } + + pk_data = talloc_zero(pctx, struct pk_child_user_data); + if (!pk_data) { + DEBUG(SSSDBG_CRIT_FAILURE, "devinfo pk_data == NULL\n"); + ret = ENOMEM; + goto done; + } + + /* Use dns_name for AD/IPA - for LDAP fallback to domain->name */ + if (result->domain != NULL) { + domain_name = result->domain->dns_name; + if (domain_name == NULL) { + domain_name = result->domain->name; + } + } + + if (domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "devinfo Invalid or missing domain name\n"); + ret = EIO; + goto done; + } + + /* Get passkey data */ + DEBUG(SSSDBG_TRACE_ALL, "devinfo Processing passkey data\n"); + ret = process_passkey_data(pk_data, result->msgs[0], domain_name, pk_data); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "process_passkey_data for devinfo failed: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* timeout */ + ret = confdb_get_int(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "devinfo Failed to read passkey_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = passkey_local_verification(pctx, pctx, pctx->pam_ctx->rctx->cdb, + result->domain->sysdb, result->domain->dns_name, + pk_data, &verification, &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to check passkey devinfo verification [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + req = pam_passkey_auth_send(pctx, pctx->ev, timeout, debug_libfido2, + verification, pctx->pd, pk_data, + PASSKEY_GET_DEVINFO); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_send devinfo failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, pam_forwarder_get_devinfo_cb, pctx->preq); + + ret = EOK; + +done: + if (pk_data != NULL) { + talloc_free(pk_data); + } + + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_ALL, "Unexpected passkey devinfo error [%d]: %s.\n", + ret, sss_strerror(ret)); + /* the error does not affect existence of passkey_data + * pctx->preq->passkey_data_exists = false; + */ + pctx->preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(pctx->preq); + } + + return; +} + +struct pam_passkey_auth_send_state { + struct pam_data *pd; + struct tevent_context *ev; + struct tevent_timer *timeout_handler; + struct sss_child_ctx_old *child_ctx; + struct child_io_fds *io; + const char *logfile; + const char **extra_args; + char *verify_opts; + int timeout; + int child_status; + enum passkey_auth_action auth_action; +}; + +static errno_t passkey_child_exec(struct tevent_req *req); +static void pam_passkey_auth_done(int child_status, + struct tevent_signal *sige, + void *pvt); + +static void pam_passkey_get_devinfo_done(int child_status, + struct tevent_signal *sige, + void *pvt); + +static int pin_destructor(void *ptr) +{ + uint8_t *pin = talloc_get_type(ptr, uint8_t); + if (pin == NULL) return EOK; + + sss_erase_talloc_mem_securely(pin); + + return EOK; +} + +errno_t get_passkey_child_write_buffer(TALLOC_CTX *mem_ctx, + struct pam_data *pd, + uint8_t **_buf, size_t *_len) +{ + int ret; + uint8_t *buf; + size_t len; + const char *pin = NULL; + + if (pd == NULL || pd->authtok == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing authtok.\n"); + return EINVAL; + } + + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY || + sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + ret = sss_authtok_get_passkey_pin(pd->authtok, &pin, &len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_passkey_pin failed [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (pin == NULL || len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "Missing PIN.\n"); + return EINVAL; + } + + buf = talloc_size(mem_ctx, len); + if (buf == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + talloc_set_destructor((void *) buf, pin_destructor); + + safealign_memcpy(buf, pin, len, NULL); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported authtok type [%d].\n", + sss_authtok_get_type(pd->authtok)); + return EINVAL; + } + + *_len = len; + *_buf = buf; + + return EOK; +} + +static void pam_passkey_child_read_data(struct tevent_req *subreq) +{ + uint8_t *buf; + ssize_t buf_len; + char *str; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); + int ret; + + ret = read_pipe_recv(subreq, state, &buf, &buf_len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + str = malloc(sizeof(char) * buf_len); + if (str == NULL) { + return; + } + + snprintf(str, buf_len, "%s", buf); + + sss_authtok_set_passkey_reply(state->pd->authtok, str, 0); + + free(str); + + tevent_req_done(req); + return; +} + +static void passkey_child_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); + + int ret; + + DEBUG(SSSDBG_TRACE_LIBS, "Sending passkey data complete\n"); + + ret = write_pipe_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + PIPE_FD_CLOSE(state->io->write_to_child_fd); + + if (state->auth_action == PASSKEY_KERBEROS_AUTH) { + /* Read data back from passkey child */ + subreq = read_pipe_send(state, state->ev, state->io->read_from_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "read_pipe_send failed.\n"); + return; + } + + tevent_req_set_callback(subreq, pam_passkey_child_read_data, req); + } +} + +errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, + struct pk_child_user_data *pk_data, + enum passkey_auth_action auth_action, + char **_result_kh, + char **_result_pk) +{ + errno_t ret; + char *result_kh = NULL; + char *result_pk = NULL; + + int index = pk_data->num_credentials - 1; + /* start we last credential because it is the newer one + * therefore te mos accurate */ + + if (index < 0) { + ret = EINVAL; + goto done; + } + + result_kh = talloc_strdup(mem_ctx, pk_data->key_handles[index]); + if (auth_action == PASSKEY_LOCAL_AUTH) { + result_pk = talloc_strdup(mem_ctx, pk_data->public_keys[index]); + if (result_pk == NULL) { + ret = ENOMEM; + goto done; + } + } + + for (int i = index - 1; i >= 0; i--) { + result_kh = talloc_strdup_append(result_kh, ","); + if (result_kh == NULL) { + ret = ENOMEM; + goto done; + } + + result_kh = talloc_strdup_append(result_kh, pk_data->key_handles[i]); + if (result_kh == NULL) { + ret = ENOMEM; + goto done; + } + + if (auth_action == PASSKEY_LOCAL_AUTH) { + result_pk = talloc_strdup_append(result_pk, ","); + if (result_pk == NULL) { + ret = ENOMEM; + goto done; + } + + result_pk = talloc_strdup_append(result_pk, pk_data->public_keys[i]); + if (result_kh == NULL || result_pk == NULL) { + ret = ENOMEM; + goto done; + } + } + } + + *_result_kh = result_kh; + *_result_pk = result_pk; + + ret = EOK; +done: + return ret; +} + +struct tevent_req * +pam_passkey_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int timeout, + bool debug_libfido2, + enum passkey_user_verification verification, + struct pam_data *pd, + struct pk_child_user_data *pk_data, + enum passkey_auth_action auth_action) +{ + struct tevent_req *req; + struct pam_passkey_auth_send_state *state; + size_t arg_c = 0; + char *result_kh; + char *result_pk; + int num_args; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct pam_passkey_auth_send_state); + if (req == NULL) { + return NULL; + } + + state->pd = pd; + state->ev = ev; + + state->timeout = timeout; + state->auth_action = auth_action; + state->logfile = PASSKEY_CHILD_LOG_FILE; + state->io = talloc(state, struct child_io_fds); + if (state->io == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc child fds failed.\n"); + ret = ENOMEM; + goto done; + } + state->io->write_to_child_fd = -1; + state->io->read_from_child_fd = -1; + talloc_set_destructor((void *) state->io, child_io_destructor); + + num_args = 11; + state->extra_args = talloc_zero_array(state, const char *, num_args + 1); + if (state->extra_args == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + switch (verification) { + case PAM_PASSKEY_VERIFICATION_ON: + state->extra_args[arg_c++] = "--user-verification=true"; + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification true\n"); + break; + case PAM_PASSKEY_VERIFICATION_OFF: + state->extra_args[arg_c++] = "--user-verification=false"; + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification false\n"); + break; + default: + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification unset\n"); + break; + } + + ret = pam_passkey_concatenate_keys(state, pk_data, state->auth_action, + &result_kh, &result_pk); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_concatenate keys failed - [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto done; + } + + switch (auth_action) { + + case PASSKEY_KERBEROS_AUTH: { + DEBUG(SSSDBG_TRACE_FUNC, "auth_action: PASSKEY_KERBEROS_AUTH\n"); + state->extra_args[arg_c++] = pk_data->crypto_challenge; + state->extra_args[arg_c++] = "--cryptographic-challenge"; + state->extra_args[arg_c++] = result_kh; + state->extra_args[arg_c++] = "--key-handle"; + state->extra_args[arg_c++] = pk_data->domain; + state->extra_args[arg_c++] = "--domain"; + state->extra_args[arg_c++] = "--get-assert"; + break; + } + case PASSKEY_LOCAL_AUTH: { + DEBUG(SSSDBG_TRACE_FUNC, "auth_action: PASSKEY_LOCAL_AUTH\n"); + state->extra_args[arg_c++] = result_pk; + state->extra_args[arg_c++] = "--public-key"; + state->extra_args[arg_c++] = result_kh; + state->extra_args[arg_c++] = "--key-handle"; + state->extra_args[arg_c++] = pk_data->domain; + state->extra_args[arg_c++] = "--domain"; + state->extra_args[arg_c++] = state->pd->user; + state->extra_args[arg_c++] = "--username"; + state->extra_args[arg_c++] = "--authenticate"; + break; + } + /* ADDED VALMIDO */ + case PASSKEY_GET_DEVINFO: { + DEBUG(SSSDBG_TRACE_FUNC, "auth_action: PASSKEY_GET_DEVINFO\n"); + state->extra_args[arg_c++] = result_kh; + state->extra_args[arg_c++] = "--key-handle"; + state->extra_args[arg_c++] = pk_data->domain; + state->extra_args[arg_c++] = "--domain"; + state->extra_args[arg_c++] = "--get-device-info"; + break; + } + /* END VALMIDO */ + default: { + ret = ERR_INTERNAL; + DEBUG(SSSDBG_OP_FAILURE, "auth_send invalid action]\n"); + goto done; + } + } + + ret = passkey_child_exec(req); + +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void +passkey_child_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = + talloc_get_type(pvt, struct tevent_req); + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + + DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for passkey child, " + "consider increasing passkey_child_timeout\n"); + child_handler_destroy(state->child_ctx); + state->child_ctx = NULL; + state->child_status = ETIMEDOUT; + tevent_req_error(req, ERR_PASSKEY_CHILD_TIMEOUT); +} + +static errno_t passkey_child_exec(struct tevent_req *req) +{ + struct pam_passkey_auth_send_state *state; + struct tevent_req *subreq; + int pipefd_from_child[2] = PIPE_INIT; + int pipefd_to_child[2] = PIPE_INIT; + pid_t child_pid; + uint8_t *write_buf = NULL; + size_t write_buf_len = 0; + struct timeval tv; + int ret; + + state = tevent_req_data(req, struct pam_passkey_auth_send_state); + + ret = pipe(pipefd_from_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + child_pid = fork(); + if (child_pid == 0) { /* child */ + exec_child_ex(state, pipefd_to_child, pipefd_from_child, + PASSKEY_CHILD_PATH, state->logfile, state->extra_args, + false, STDIN_FILENO, STDOUT_FILENO); + /* We should never get here */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec passkey child\n"); + goto done; + } else if (child_pid > 0) { /* parent */ + state->io->read_from_child_fd = pipefd_from_child[0]; + PIPE_FD_CLOSE(pipefd_from_child[1]); + sss_fd_nonblocking(state->io->read_from_child_fd); + + state->io->write_to_child_fd = pipefd_to_child[1]; + PIPE_FD_CLOSE(pipefd_to_child[0]); + sss_fd_nonblocking(state->io->write_to_child_fd); + + /* Set up SIGCHLD handler */ + if (state->auth_action == PASSKEY_KERBEROS_AUTH) + ret = child_handler_setup(state->ev, child_pid, NULL, req, &state->child_ctx); + else if (state->auth_action == PASSKEY_LOCAL_AUTH) + ret = child_handler_setup(state->ev, child_pid, + pam_passkey_auth_done, req, + &state->child_ctx); + else if (state->auth_action == PASSKEY_GET_DEVINFO) + ret = child_handler_setup(state->ev, child_pid, + pam_passkey_get_devinfo_done, req, + &state->child_ctx); + else { + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: internal error - invalid auth_action\n"); + ret = ERR_INTERNAL; + goto done; + } + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_PASSKEY_CHILD; + goto done; + } + + /* Set up timeout handler */ + tv = tevent_timeval_current_ofs(state->timeout, 0); + state->timeout_handler = tevent_add_timer(state->ev, req, tv, + passkey_child_timeout, req); + if (state->timeout_handler == NULL) { + ret = ERR_PASSKEY_CHILD; + goto done; + } + + if (state->auth_action != PASSKEY_GET_DEVINFO) { + if (sss_authtok_get_type(state->pd->authtok) != SSS_AUTHTOK_TYPE_EMPTY) { + /* PIN is needed */ + ret = get_passkey_child_write_buffer(state, state->pd, &write_buf, + &write_buf_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "get_passkey_child_write_buffer failed [%d]: %s.\n", + ret, sss_strerror(ret)); + goto done; + } + } + } + + if (write_buf_len != 0) { + subreq = write_pipe_send(state, state->ev, write_buf, write_buf_len, + state->io->write_to_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "write_pipe_send failed.\n"); + ret = ERR_PASSKEY_CHILD; + goto done; + } + tevent_req_set_callback(subreq, passkey_child_write_done, req); + } + /* Now either wait for the timeout to fire or the child to finish */ + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + return EAGAIN; + +done: + if (ret != EOK) { + PIPE_CLOSE(pipefd_from_child); + PIPE_CLOSE(pipefd_to_child); + } + + return ret; +} + +static void +pam_passkey_get_devinfo_done(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + state->child_status = WEXITSTATUS(child_status); + if (WIFEXITED(child_status)) { + if (WEXITSTATUS(child_status) != 0) { + DEBUG(SSSDBG_OP_FAILURE, + PASSKEY_CHILD_PATH " devinfo failed with status [%d]. Check passkey_child" + " logs for more information.\n", + WEXITSTATUS(child_status)); + tevent_req_error(req, ERR_PASSKEY_CHILD); + return; + } + } else if (WIFSIGNALED(child_status)) { + DEBUG(SSSDBG_OP_FAILURE, + PASSKEY_CHILD_PATH " devinfo was terminated by signal [%d]. Check passkey_child" + " logs for more information.\n", + WTERMSIG(child_status)); + tevent_req_error(req, ECHILD); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "devinfo data is valid. Mark done\n"); + tevent_req_done(req); + return; +} + +errno_t pam_passkey_auth_recv(struct tevent_req *req, + int *child_status) +{ + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + + *child_status = state->child_status; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +errno_t decode_pam_passkey_msg(TALLOC_CTX *mem_ctx, + uint8_t *buf, + size_t len, + struct pk_child_user_data **_data) +{ + + size_t p = 0; + size_t pctr = 0; + errno_t ret; + size_t offset; + struct pk_child_user_data *data = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + data = talloc_zero(tmp_ctx, struct pk_child_user_data); + if (data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to talloc passkey data.\n"); + ret = ENOMEM; + goto done; + } + + data->user_verification = talloc_strdup(data, (char *) &buf[p]); + if (data->user_verification == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey prompt.\n"); + ret = ENOMEM; + goto done; + } + + offset = strlen(data->user_verification) + 1; + if (offset >= len) { + DEBUG(SSSDBG_OP_FAILURE, "passkey prompt offset failure.\n"); + ret = EIO; + goto done; + } + + data->crypto_challenge = talloc_strdup(data, (char *) &buf[p + offset]); + if (data->crypto_challenge == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey challenge.\n"); + ret = ENOMEM; + goto done; + } + + offset += strlen(data->crypto_challenge) + 1; + if (offset >= len) { + DEBUG(SSSDBG_OP_FAILURE, "passkey challenge offset failure.\n"); + ret = EIO; + goto done; + } + + + data->domain = talloc_strdup(data, (char *) &buf[p] + offset); + if (data->domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey domain.\n"); + ret = ENOMEM; + goto done; + } + + offset += strlen(data->domain) + 1; + if (offset >= len) { + DEBUG(SSSDBG_OP_FAILURE, "passkey domain offset failure.\n"); + ret = EIO; + goto done; + } + + SAFEALIGN_COPY_UINT32(&data->num_credentials, &buf[p + offset], &pctr); + size_t list_sz = (size_t) data->num_credentials; + + offset += sizeof(uint32_t); + + data->key_handles = talloc_zero_array(data, const char *, list_sz); + + for (int i = 0; i < list_sz; i++) { + data->key_handles[i] = talloc_strdup(data->key_handles, (char *) &buf[p + offset]); + if (data->key_handles[i] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey list.\n"); + ret = ENOMEM; + goto done; + } + + offset += strlen(data->key_handles[i]) + 1; + } + + *_data = talloc_steal(mem_ctx, data); + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t save_passkey_data(TALLOC_CTX *mem_ctx, + struct pam_ctx *pctx, + struct pk_child_user_data *data, + struct pam_auth_req *preq) +{ + char *pk_key; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Passkey data (pk_table_data) is stolen onto client ctx, it will + * be freed when the client closes, and the sss_ptr_hash interface + * takes care of automatically removing it from the hash table then */ + pctx->pk_table_data = talloc_zero(tmp_ctx, struct pam_passkey_table_data); + if (pctx->pk_table_data == NULL) { + return ENOMEM; + } + + if (pctx->pk_table_data->table == NULL) { + pctx->pk_table_data->table = sss_ptr_hash_create(pctx->pk_table_data, + NULL, NULL); + if (pctx->pk_table_data->table == NULL) { + ret = ENOMEM; + goto done; + } + } + + pk_key = talloc_asprintf(tmp_ctx, "%s", data->crypto_challenge); + if (pk_key == NULL) { + ret = ENOMEM; + goto done; + } + + pctx->pk_table_data->key = talloc_strdup(pctx->pk_table_data, pk_key); + if (pctx->pk_table_data->key == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_ptr_hash_add(pctx->pk_table_data->table, pk_key, data, + struct pk_child_user_data); + if (ret == EEXIST) { + DEBUG(SSSDBG_TRACE_FUNC, "pk_table key [%s] already exists\n", + pk_key); + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to add pk data to hash table " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + talloc_steal(mem_ctx, pctx->pk_table_data); + pctx->pk_table_data->data = talloc_steal(mem_ctx, data); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t pam_eval_passkey_response(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq, + bool *_pk_preauth_done) +{ + struct response_data *pk_resp; + struct pk_child_user_data *pk_data; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + pk_resp = pd->resp_list; + + while (pk_resp != NULL) { + switch (pk_resp->type) { + case SSS_PAM_PASSKEY_KRB_INFO: + if (!pctx->passkey_auth) { + /* Passkey auth is disabled. To avoid passkey prompts appearing, + * don't send SSS_PAM_PASSKEY_KRB_INFO to the client and + * add a dummy response to fallback to normal auth */ + pk_resp->do_not_send_to_client = true; + ret = pam_add_response(pd, SSS_OTP, 0, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + break; + } + ret = decode_pam_passkey_msg(tmp_ctx, pk_resp->data, pk_resp->len, &pk_data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to decode passkey msg\n"); + ret = EIO; + goto done; + } + + ret = save_passkey_data(preq->cctx, pctx, pk_data, preq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to save passkey msg\n"); + ret = EIO; + goto done; + } + break; + /* Passkey non-kerberos preauth has already run */ + case SSS_PAM_PASSKEY_INFO: + *_pk_preauth_done = true; + default: + break; + } + pk_resp = pk_resp->next; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + + +static void +pam_passkey_auth_done(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + state->child_status = WEXITSTATUS(child_status); + if (WIFEXITED(child_status)) { + if (WEXITSTATUS(child_status) != 0) { + DEBUG(SSSDBG_OP_FAILURE, + PASSKEY_CHILD_PATH " failed with status [%d]. Check passkey_child" + " logs for more information.\n", + WEXITSTATUS(child_status)); + tevent_req_error(req, ERR_PASSKEY_CHILD); + return; + } + } else if (WIFSIGNALED(child_status)) { + DEBUG(SSSDBG_OP_FAILURE, + PASSKEY_CHILD_PATH " was terminated by signal [%d]. Check passkey_child" + " logs for more information.\n", + WTERMSIG(child_status)); + tevent_req_error(req, ECHILD); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "passkey data is valid. Mark done\n"); + + tevent_req_done(req); + return; +} + +bool may_do_passkey_auth(struct pam_ctx *pctx, + struct pam_data *pd) +{ +#ifndef BUILD_PASSKEY + DEBUG(SSSDBG_TRACE_FUNC, "Passkey auth not possible, SSSD built without passkey support!\n"); + return false; +#else + if (!pctx->passkey_auth) { + return false; + } + + if (pd->cmd != SSS_PAM_PREAUTH && + pd->cmd != SSS_PAM_AUTHENTICATE && + pd->cmd != SSS_PAM_PASSKEY_PREAUTH) { + return false; + } + + if (pd->service == NULL || *pd->service == '\0') { + return false; + } + + return true; +#endif /* BUILD_PASSKEY */ +} diff --git a/src/responder/pam/pamsrv_passkey.h b/src/responder/pam/pamsrv_passkey.h index 48074d04263..dad5ea0ad3f 100644 --- a/src/responder/pam/pamsrv_passkey.h +++ b/src/responder/pam/pamsrv_passkey.h @@ -1,87 +1,102 @@ -/* - Authors: - Justin Stephenson - - Copyright (C) 2022 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 . -*/ - -#ifndef __PAMSRV_PASSKEY_H__ -#define __PAMSRV_PASSKEY_H__ - -#include -#include "util/util.h" -#include "util/sss_ptr_hash.h" -#include "responder/common/responder.h" -#include "responder/common/cache_req/cache_req.h" -#include "responder/pam/pamsrv.h" -#include "lib/certmap/sss_certmap.h" - -enum passkey_user_verification { - PAM_PASSKEY_VERIFICATION_ON, - PAM_PASSKEY_VERIFICATION_OFF, - PAM_PASSKEY_VERIFICATION_OMIT, - PAM_PASSKEY_VERIFICATION_INVALID -}; - -errno_t passkey_local(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - struct pam_ctx *pam_ctx, - struct pam_auth_req *preq, - struct pam_data *pd); -errno_t passkey_kerberos(struct pam_ctx *pctx, - struct pam_data *pd, - struct pam_auth_req *preq); - -struct pk_child_user_data { - /* Both Kerberos and non-kerberos */ - const char *domain; - size_t num_credentials; - const char *user_verification; - const char **key_handles; - /* Kerberos PA only */ - const char *crypto_challenge; - /* Non-kerberos only */ - const char *user; - const char **public_keys; -}; - -errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, - const char *verify_opts, - enum passkey_user_verification *_user_verification); - -void pam_forwarder_passkey_cb(struct tevent_req *req); -struct tevent_req *pam_passkey_auth_send(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - int timeout, - bool debug_libfido2, - enum passkey_user_verification verification, - struct pam_data *pd, - struct pk_child_user_data *pk_data, - bool kerberos_pa); -errno_t pam_passkey_auth_recv(struct tevent_req *req, - int *child_status); -errno_t pam_eval_passkey_response(struct pam_ctx *pctx, - struct pam_data *pd, - struct pam_auth_req *preq, - bool *_pk_preauth_done); -errno_t process_passkey_data(TALLOC_CTX *mem_ctx, - struct ldb_message *user_mesg, - const char *domain, - struct pk_child_user_data *_data); -bool may_do_passkey_auth(struct pam_ctx *pctx, - struct pam_data *pd); - -#endif /* __PAMSRV_PASSKEY_H__ */ +/* + Authors: + Justin Stephenson + + Copyright (C) 2022 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 . +*/ + +#ifndef __PAMSRV_PASSKEY_H__ +#define __PAMSRV_PASSKEY_H__ + +#include +#include "util/util.h" +#include "util/sss_ptr_hash.h" +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/pam/pamsrv.h" +#include "lib/certmap/sss_certmap.h" + +enum passkey_user_verification { + PAM_PASSKEY_VERIFICATION_ON, + PAM_PASSKEY_VERIFICATION_OFF, + PAM_PASSKEY_VERIFICATION_OMIT, + PAM_PASSKEY_VERIFICATION_INVALID +}; + +enum passkey_auth_action { + PASSKEY_KERBEROS_AUTH, + PASSKEY_LOCAL_AUTH, + PASSKEY_GET_DEVINFO +}; + +errno_t passkey_local(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_ctx *pam_ctx, + struct pam_auth_req *preq, + struct pam_data *pd); +errno_t passkey_kerberos(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq); + +errno_t passkey_local_get_devinfo(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_ctx *pam_ctx, + struct pam_auth_req *preq, + struct pam_data *pd); + +errno_t passkey_kerberos_get_devinfo(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq); +struct pk_child_user_data { + /* Both Kerberos and non-kerberos */ + const char *domain; + size_t num_credentials; + const char *user_verification; + const char **key_handles; + /* Kerberos PA only */ + const char *crypto_challenge; + /* Non-kerberos only */ + const char *user; + const char **public_keys; +}; + +errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, + const char *verify_opts, + enum passkey_user_verification *_user_verification); + +void pam_forwarder_passkey_cb(struct tevent_req *req); +struct tevent_req *pam_passkey_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int timeout, + bool debug_libfido2, + enum passkey_user_verification verification, + struct pam_data *pd, + struct pk_child_user_data *pk_data, + enum passkey_auth_action auth_action); +errno_t pam_passkey_auth_recv(struct tevent_req *req, + int *child_status); +errno_t pam_eval_passkey_response(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq, + bool *_pk_preauth_done); +errno_t process_passkey_data(TALLOC_CTX *mem_ctx, + struct ldb_message *user_mesg, + const char *domain, + struct pk_child_user_data *_data); +bool may_do_passkey_auth(struct pam_ctx *pctx, + struct pam_data *pd); + +#endif /* __PAMSRV_PASSKEY_H__ */ diff --git a/src/sss_client/pam_message.h b/src/sss_client/pam_message.h index d6fb254f208..57d34b23daa 100644 --- a/src/sss_client/pam_message.h +++ b/src/sss_client/pam_message.h @@ -1,80 +1,81 @@ -/* - Authors: - Sumit Bose - - Copyright (C) 2015 Red Hat - - PAM client - create message blob - - 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 . -*/ - -#ifndef _PAM_MESSAGE_H_ -#define _PAM_MESSAGE_H_ - -#include -#include -#include - -#include "sss_client/sss_cli.h" - -struct cert_auth_info; - -struct pam_items { - const char *pam_service; - const char *pam_user; - const char *pam_tty; - const char *pam_ruser; - const char *pam_rhost; - char *pam_authtok; - char *pam_newauthtok; - const char *pamstack_authtok; - const char *pamstack_oldauthtok; - size_t pam_service_size; - size_t pam_user_size; - size_t pam_tty_size; - size_t pam_ruser_size; - size_t pam_rhost_size; - enum sss_authtok_type pam_authtok_type; - size_t pam_authtok_size; - enum sss_authtok_type pam_newauthtok_type; - size_t pam_newauthtok_size; - pid_t cli_pid; - pid_t child_pid; - uint32_t flags; - const char *login_name; - char *domain_name; - const char *requested_domains; - size_t requested_domains_size; - char *otp_vendor; - char *otp_token_id; - char *otp_challenge; - char *oauth2_url; - char *oauth2_url_complete; - char *oauth2_pin; - char *first_factor; - char *passkey_key; - char *passkey_prompt_pin; - bool password_prompting; - - 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); - -#endif /* _PAM_MESSAGE_H_ */ +/* + Authors: + Sumit Bose + + Copyright (C) 2015 Red Hat + + PAM client - create message blob + + 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 . +*/ + +#ifndef _PAM_MESSAGE_H_ +#define _PAM_MESSAGE_H_ + +#include +#include +#include + +#include "sss_client/sss_cli.h" + +struct cert_auth_info; + +struct pam_items { + const char *pam_service; + const char *pam_user; + const char *pam_tty; + const char *pam_ruser; + const char *pam_rhost; + char *pam_authtok; + char *pam_newauthtok; + const char *pamstack_authtok; + const char *pamstack_oldauthtok; + size_t pam_service_size; + size_t pam_user_size; + size_t pam_tty_size; + size_t pam_ruser_size; + size_t pam_rhost_size; + enum sss_authtok_type pam_authtok_type; + size_t pam_authtok_size; + enum sss_authtok_type pam_newauthtok_type; + size_t pam_newauthtok_size; + pid_t cli_pid; + pid_t child_pid; + uint32_t flags; + const char *login_name; + char *domain_name; + const char *requested_domains; + size_t requested_domains_size; + char *otp_vendor; + char *otp_token_id; + char *otp_challenge; + char *oauth2_url; + char *oauth2_url_complete; + char *oauth2_pin; + char *first_factor; + char *passkey_key; + char *passkey_prompt_pin; + char *passkey_devinfo; + bool password_prompting; + + 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); + +#endif /* _PAM_MESSAGE_H_ */ diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c index 3f2317f3eb7..d1b3f246ca1 100644 --- a/src/sss_client/pam_sss.c +++ b/src/sss_client/pam_sss.c @@ -1,3270 +1,3438 @@ -/* - Authors: - Sumit Bose - - Copyright (C) 2009 Red Hat - Copyright (C) 2010, rhafer@suse.de, Novell Inc. - - 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 . -*/ - -#include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#ifdef HAVE_GDM_PAM_EXTENSIONS -#include -#endif - -#include "sss_pam_compat.h" -#include "sss_pam_macros.h" - -#include "sss_cli.h" -#include "pam_message.h" -#include "util/atomic_io.h" -#include "util/authtok-utils.h" -#include "util/dlinklist.h" -#include "util/memory_erase.h" - -#include -#define _(STRING) dgettext (PACKAGE, STRING) -#define _n(SINGULAR, PLURAL, VALUE) dngettext(PACKAGE, SINGULAR, PLURAL, VALUE) - -#define PWEXP_FLAG "pam_sss:password_expired_flag" -#define FD_DESTRUCTOR "pam_sss:fd_destructor" -#define PAM_SSS_AUTHOK_TYPE "pam_sss:authtok_type" -#define PAM_SSS_AUTHOK_SIZE "pam_sss:authtok_size" -#define PAM_SSS_AUTHOK_DATA "pam_sss:authtok_data" - -#define PW_RESET_MSG_FILENAME_TEMPLATE SSSD_CONF_DIR"/customize/%s/pam_sss_pw_reset_message.%s" -#define PW_RESET_MSG_MAX_SIZE 4096 - -#define OPT_RETRY_KEY "retry=" -#define OPT_DOMAINS_KEY "domains=" - -#define EXP_ACC_MSG _("Permission denied. ") -#define SRV_MSG _("Server message: ") -#define PASSKEY_LOCAL_AUTH_MSG _("Kerberos TGT will not be granted upon login, user experience will be affected.") -#define PASSKEY_DEFAULT_PIN_MSG _("Enter PIN:") - -#define DEBUG_MGS_LEN 1024 -#define MAX_AUTHTOK_SIZE (1024*1024) -#define CHECK_AND_RETURN_PI_STRING(s) ((s != NULL && *s != '\0')? s : "(not available)") -#define SERVICE_IS_GDM_SMARTCARD(pitem) (strcmp((pitem)->pam_service, \ - "gdm-smartcard") == 0) - -static void logger(pam_handle_t *pamh, int level, const char *fmt, ...) { - va_list ap; - - va_start(ap, fmt); - -#ifdef DEBUG - va_list apd; - char debug_msg[DEBUG_MGS_LEN]; - int ret; - va_copy(apd, ap); - - ret = vsnprintf(debug_msg, DEBUG_MGS_LEN, fmt, apd); - if (ret >= DEBUG_MGS_LEN) { - D(("the following message is truncated: %s", debug_msg)); - } else if (ret < 0) { - D(("vsnprintf failed to format debug message!")); - } else { - D((debug_msg)); - } - - va_end(apd); -#endif - - pam_vsyslog(pamh, LOG_AUTHPRIV|level, fmt, ap); - - va_end(ap); -} - -static void free_exp_data(pam_handle_t *pamh, void *ptr, int err) -{ - free(ptr); -} - -static void close_fd(pam_handle_t *pamh, void *ptr, int err) -{ -#ifdef PAM_DATA_REPLACE - if (err & PAM_DATA_REPLACE) { - /* Nothing to do */ - return; - } -#endif /* PAM_DATA_REPLACE */ - - D(("Closing the fd")); - - sss_pam_lock(); - sss_cli_close_socket(); - sss_pam_unlock(); -} - -struct cert_auth_info { - char *cert_user; - char *cert; - char *token_name; - char *module_name; - char *key_id; - char *label; - char *prompt_str; - char *pam_cert_user; - char *choice_list_id; - struct cert_auth_info *prev; - struct cert_auth_info *next; -}; - -static void free_cai(struct cert_auth_info *cai) -{ - if (cai != NULL) { - free(cai->cert_user); - free(cai->cert); - free(cai->token_name); - free(cai->module_name); - free(cai->key_id); - free(cai->label); - free(cai->prompt_str); - free(cai->choice_list_id); - free(cai); - } -} - -static void free_cert_list(struct cert_auth_info *list) -{ - struct cert_auth_info *cai; - struct cert_auth_info *cai_next; - - if (list != NULL) { - DLIST_FOR_EACH_SAFE(cai, cai_next, list) { - DLIST_REMOVE(list, cai); - free_cai(cai); - } - } -} - -static void overwrite_and_free_authtoks(struct pam_items *pi) -{ - if (pi->pam_authtok != NULL) { - sss_erase_mem_securely((void *)pi->pam_authtok, pi->pam_authtok_size); - free((void *)pi->pam_authtok); - pi->pam_authtok = NULL; - } - - if (pi->pam_newauthtok != NULL) { - sss_erase_mem_securely((void *)pi->pam_newauthtok, pi->pam_newauthtok_size); - free((void *)pi->pam_newauthtok); - pi->pam_newauthtok = NULL; - } - - if (pi->first_factor != NULL) { - sss_erase_mem_securely((void *)pi->first_factor, strlen(pi->first_factor)); - free((void *)pi->first_factor); - pi->first_factor = NULL; - } - - pi->pamstack_authtok = NULL; - pi->pamstack_oldauthtok = NULL; -} - -static void overwrite_and_free_pam_items(struct pam_items *pi) -{ - overwrite_and_free_authtoks(pi); - - free(pi->domain_name); - pi->domain_name = NULL; - - free(pi->otp_vendor); - pi->otp_vendor = NULL; - - free(pi->otp_token_id); - pi->otp_token_id = NULL; - - free(pi->otp_challenge); - pi->otp_challenge = NULL; - - free(pi->passkey_key); - pi->passkey_key = NULL; - - free(pi->passkey_prompt_pin); - pi->passkey_prompt_pin = NULL; - - 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) { - if (s1 == NULL && s2 == NULL) return 0; - if (s1 == NULL && s2 != NULL) return -1; - if (s1 != NULL && s2 == NULL) return 1; - return strcmp(s1, s2); -} - -enum { - SSS_PAM_CONV_DONE = 0, - SSS_PAM_CONV_STD, - SSS_PAM_CONV_REENTER, -}; - -static int do_pam_conversation(pam_handle_t *pamh, const int msg_style, - const char *msg, - const char *reenter_msg, - char **_answer) -{ - int ret; - int state = SSS_PAM_CONV_STD; - const struct pam_conv *conv; - const struct pam_message *mesg[1]; - struct pam_message *pam_msg; - struct pam_response *resp=NULL; - char *answer = NULL; - - if ((msg_style == PAM_TEXT_INFO || msg_style == PAM_ERROR_MSG) && - msg == NULL) return PAM_SYSTEM_ERR; - - if ((msg_style == PAM_PROMPT_ECHO_OFF || - msg_style == PAM_PROMPT_ECHO_ON) && - (msg == NULL || _answer == NULL)) return PAM_SYSTEM_ERR; - - if (msg_style == PAM_TEXT_INFO || msg_style == PAM_ERROR_MSG) { - logger(pamh, LOG_INFO, "User %s message: %s", - msg_style == PAM_TEXT_INFO ? "info" : "error", - msg); - } - - ret=pam_get_item(pamh, PAM_CONV, (const void **) &conv); - if (ret != PAM_SUCCESS) return ret; - if (conv == NULL || conv->conv == NULL) { - logger(pamh, LOG_ERR, "No conversation function"); - return PAM_SYSTEM_ERR; - } - - do { - pam_msg = malloc(sizeof(struct pam_message)); - if (pam_msg == NULL) { - D(("Malloc failed.")); - ret = PAM_SYSTEM_ERR; - goto failed; - } - - pam_msg->msg_style = msg_style; - if (state == SSS_PAM_CONV_REENTER) { - pam_msg->msg = reenter_msg; - } else { - pam_msg->msg = msg; - } - - mesg[0] = (const struct pam_message *) pam_msg; - - ret=conv->conv(1, mesg, &resp, - conv->appdata_ptr); - free(pam_msg); - if (ret != PAM_SUCCESS) { - D(("Conversation failure: %s.", pam_strerror(pamh,ret))); - goto failed; - } - - if (msg_style == PAM_PROMPT_ECHO_OFF || - msg_style == PAM_PROMPT_ECHO_ON) { - if (resp == NULL) { - D(("response expected, but resp==NULL")); - ret = PAM_SYSTEM_ERR; - goto failed; - } - - if (state == SSS_PAM_CONV_REENTER) { - if (null_strcmp(answer, resp[0].resp) != 0) { - logger(pamh, LOG_NOTICE, "Passwords do not match."); - sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); - free(resp[0].resp); - if (answer != NULL) { - sss_erase_mem_securely((void *) answer, strlen(answer)); - free(answer); - answer = NULL; - } - ret = do_pam_conversation(pamh, PAM_ERROR_MSG, - _("Passwords do not match"), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - ret = PAM_SYSTEM_ERR; - goto failed; - } - ret = PAM_CRED_ERR; - goto failed; - } - sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); - free(resp[0].resp); - } else { - if (resp[0].resp == NULL) { - D(("Empty password")); - answer = NULL; - } else { - answer = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); - sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); - free(resp[0].resp); - if(answer == NULL) { - D(("strndup failed")); - ret = PAM_BUF_ERR; - goto failed; - } - } - } - free(resp); - resp = NULL; - } - - if (reenter_msg != NULL && state == SSS_PAM_CONV_STD) { - state = SSS_PAM_CONV_REENTER; - } else { - state = SSS_PAM_CONV_DONE; - } - } while (state != SSS_PAM_CONV_DONE); - - if (_answer) *_answer = answer; - return PAM_SUCCESS; - -failed: - free(answer); - return ret; - -} - -static errno_t display_pw_reset_message(pam_handle_t *pamh, - const char *domain_name, - const char *suffix) -{ - int ret; - struct stat stat_buf; - char *msg_buf = NULL; - int fd = -1; - size_t size; - size_t total_len; - char *filename = NULL; - - if (strchr(suffix, '/') != NULL || strchr(domain_name, '/') != NULL) { - D(("Suffix [%s] or domain name [%s] contain illegal character.", suffix, - domain_name)); - return EINVAL; - } - - size = sizeof(PW_RESET_MSG_FILENAME_TEMPLATE) + strlen(domain_name) + - strlen(suffix); - filename = malloc(size); - if (filename == NULL) { - D(("malloc failed.")); - ret = ENOMEM; - goto done; - } - ret = snprintf(filename, size, PW_RESET_MSG_FILENAME_TEMPLATE, domain_name, - suffix); - if (ret < 0 || ret >= size) { - D(("snprintf failed.")); - ret = EFAULT; - goto done; - } - - fd = open(filename, O_RDONLY); - if (fd == -1) { - ret = errno; - D(("open failed [%d][%s].\n", ret, strerror(ret))); - goto done; - } - - ret = fstat(fd, &stat_buf); - if (ret == -1) { - ret = errno; - D(("fstat failed [%d][%s].", ret, strerror(ret))); - goto done; - } - - if (!S_ISREG(stat_buf.st_mode)) { - logger(pamh, LOG_ERR, - "Password reset message file is not a regular file."); - ret = EINVAL; - goto done; - } - - if (stat_buf.st_uid != 0 || stat_buf.st_gid != 0 || - (stat_buf.st_mode & ~S_IFMT) != 0644) { - logger(pamh, LOG_ERR,"Permission error, " - "file [%s] must be owned by root with permissions 0644.", - filename); - ret = EPERM; - goto done; - } - - if (stat_buf.st_size > PW_RESET_MSG_MAX_SIZE) { - logger(pamh, LOG_ERR, "Password reset message file is too large."); - ret = EFBIG; - goto done; - } - - msg_buf = malloc(stat_buf.st_size + 1); - if (msg_buf == NULL) { - D(("malloc failed.")); - ret = ENOMEM; - goto done; - } - - errno = 0; - total_len = sss_atomic_read_s(fd, msg_buf, stat_buf.st_size); - if (total_len == -1) { - ret = errno; - D(("read failed [%d][%s].", ret, strerror(ret))); - goto done; - } - - ret = close(fd); - fd = -1; - if (ret == -1) { - ret = errno; - D(("close failed [%d][%s].", ret, strerror(ret))); - } - - if (total_len != stat_buf.st_size) { - D(("read fewer bytes [%d] than expected [%d].", total_len, - stat_buf.st_size)); - ret = EIO; - goto done; - } - - msg_buf[stat_buf.st_size] = '\0'; - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, msg_buf, NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - } - -done: - if (fd != -1) { - close(fd); - } - free(msg_buf); - free(filename); - - return ret; -} - -static errno_t select_pw_reset_message(pam_handle_t *pamh, struct pam_items *pi) -{ - int ret; - char *locale; - const char *domain_name; - - domain_name = pi->domain_name; - if (domain_name == NULL || *domain_name == '\0') { - D(("Domain name is unknown.")); - return EINVAL; - } - - locale = setlocale(LC_MESSAGES, NULL); - - ret = -1; - if (locale != NULL) { - ret = display_pw_reset_message(pamh, domain_name, locale); - } - - if (ret != 0) { - ret = display_pw_reset_message(pamh, domain_name, "txt"); - } - - if (ret != 0) { - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, - _("Password reset by root is not supported."), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - } - } - - return ret; -} - -static int user_info_offline_auth(pam_handle_t *pamh, size_t buflen, - uint8_t *buf) -{ - int ret; - int64_t expire_date; - struct tm tm; - char expire_str[128]; - char user_msg[256]; - - expire_str[0] = '\0'; - - if (buflen != sizeof(uint32_t) + sizeof(int64_t)) { - D(("User info response data has the wrong size")); - return PAM_BUF_ERR; - } - - memcpy(&expire_date, buf + sizeof(uint32_t), sizeof(int64_t)); - - if (expire_date > 0) { - if (localtime_r((time_t *) &expire_date, &tm) != NULL) { - ret = strftime(expire_str, sizeof(expire_str), "%c", &tm); - if (ret == 0) { - D(("strftime failed.")); - expire_str[0] = '\0'; - } - } else { - D(("localtime_r failed")); - } - } - - ret = snprintf(user_msg, sizeof(user_msg), "%s%s%s.", - _("Authenticated with cached credentials"), - expire_str[0] ? _(", your cached password will expire at: ") : "", - expire_str[0] ? expire_str : ""); - if (ret < 0 || ret >= sizeof(user_msg)) { - D(("snprintf failed.")); - return PAM_SYSTEM_ERR; - } - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_grace_login(pam_handle_t *pamh, - size_t buflen, - uint8_t *buf) -{ - int ret; - uint32_t grace; - char user_msg[256]; - - if (buflen != 2* sizeof(uint32_t)) { - D(("User info response data has the wrong size")); - return PAM_BUF_ERR; - } - memcpy(&grace, buf + sizeof(uint32_t), sizeof(uint32_t)); - ret = snprintf(user_msg, sizeof(user_msg), - _("Your password has expired. " - "You have %1$d grace login(s) remaining."), - grace); - if (ret < 0 || ret >= sizeof(user_msg)) { - D(("snprintf failed.")); - return PAM_SYSTEM_ERR; - } - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); - - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -#define MINSEC 60 -#define HOURSEC (60*MINSEC) -#define DAYSEC (24*HOURSEC) -static int user_info_expire_warn(pam_handle_t *pamh, - size_t buflen, - uint8_t *buf) -{ - int ret; - uint32_t expire; - char user_msg[256]; - const char* unit; - - if (buflen != 2* sizeof(uint32_t)) { - D(("User info response data has the wrong size")); - return PAM_BUF_ERR; - } - memcpy(&expire, buf + sizeof(uint32_t), sizeof(uint32_t)); - /* expire == 0 indicates the password expired */ - if (expire != 0) { - if (expire >= DAYSEC) { - expire /= DAYSEC; - unit = _n("day", "days", expire); - } else if (expire >= HOURSEC) { - expire /= HOURSEC; - unit = _n("hour", "hours", expire); - } else if (expire >= MINSEC) { - expire /= MINSEC; - unit = _n("minute", "minutes", expire); - } else { - unit = _n("second", "seconds", expire); - } - - ret = snprintf(user_msg, sizeof(user_msg), - _("Your password will expire in %1$d %2$s."), expire, unit); - } else { - ret = snprintf(user_msg, sizeof(user_msg), - _("Your password has expired.")); - } - - if (ret < 0 || ret >= sizeof(user_msg)) { - D(("snprintf failed.")); - return PAM_SYSTEM_ERR; - } - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); - - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_offline_auth_delayed(pam_handle_t *pamh, size_t buflen, - uint8_t *buf) -{ - int ret; - int64_t delayed_until; - struct tm tm; - char delay_str[128]; - char user_msg[256]; - - delay_str[0] = '\0'; - - if (buflen != sizeof(uint32_t) + sizeof(int64_t)) { - D(("User info response data has the wrong size")); - return PAM_BUF_ERR; - } - - memcpy(&delayed_until, buf + sizeof(uint32_t), sizeof(int64_t)); - - if (delayed_until <= 0) { - D(("User info response data has an invalid value")); - return PAM_BUF_ERR; - } - - if (localtime_r((time_t *) &delayed_until, &tm) != NULL) { - ret = strftime(delay_str, sizeof(delay_str), "%c", &tm); - if (ret == 0) { - D(("strftime failed.")); - delay_str[0] = '\0'; - } - } else { - D(("localtime_r failed")); - } - - ret = snprintf(user_msg, sizeof(user_msg), "%s%s.", - _("Authentication is denied until: "), - delay_str); - if (ret < 0 || ret >= sizeof(user_msg)) { - D(("snprintf failed.")); - return PAM_SYSTEM_ERR; - } - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_offline_chpass(pam_handle_t *pamh) -{ - int ret; - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, - _("System is offline, password change not possible"), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_otp_chpass(pam_handle_t *pamh) -{ - int ret; - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, - _("After changing the OTP password, you need to " - "log out and back in order to acquire a ticket"), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_pin_locked(pam_handle_t *pamh) -{ - int ret; - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, _("PIN locked"), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_no_krb_tgt(pam_handle_t *pamh) -{ - int ret; - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, - _("No Kerberos TGT granted as " - "the server does not support this method. " - "Your single-sign on(SSO) experience will " - "be affected."), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_account_expired(pam_handle_t *pamh, size_t buflen, - uint8_t *buf) -{ - int ret; - uint32_t msg_len; - char *user_msg; - size_t bufsize = 0; - - /* resp_type and length of message are expected to be in buf */ - if (buflen < 2* sizeof(uint32_t)) { - D(("User info response data is too short")); - return PAM_BUF_ERR; - } - - /* msg_len = legth of message */ - memcpy(&msg_len, buf + sizeof(uint32_t), sizeof(uint32_t)); - - if (buflen != 2* sizeof(uint32_t) + msg_len) { - D(("User info response data has the wrong size")); - return PAM_BUF_ERR; - } - - bufsize = strlen(EXP_ACC_MSG) + 1; - - if (msg_len > 0) { - bufsize += strlen(SRV_MSG) + msg_len; - } - - user_msg = (char *)malloc(sizeof(char) * bufsize); - if (!user_msg) { - D(("Out of memory.")); - return PAM_SYSTEM_ERR; - } - - ret = snprintf(user_msg, bufsize, "%s%s%.*s", - EXP_ACC_MSG, - msg_len > 0 ? SRV_MSG : "", - (int)msg_len, - msg_len > 0 ? (char *)(buf + 2 * sizeof(uint32_t)) : "" ); - if (ret < 0 || ret > bufsize) { - D(("snprintf failed.")); - - free(user_msg); - return PAM_SYSTEM_ERR; - } - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); - free(user_msg); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_chpass_error(pam_handle_t *pamh, size_t buflen, - uint8_t *buf) -{ - int ret; - uint32_t msg_len; - char *user_msg; - size_t bufsize = 0; - - if (buflen < 2* sizeof(uint32_t)) { - D(("User info response data is too short")); - return PAM_BUF_ERR; - } - - memcpy(&msg_len, buf + sizeof(uint32_t), sizeof(uint32_t)); - - if (buflen != 2* sizeof(uint32_t) + msg_len) { - D(("User info response data has the wrong size")); - return PAM_BUF_ERR; - } - - bufsize = strlen(_("Password change failed. ")) + 1; - - if (msg_len > 0) { - bufsize += strlen(_("Server message: ")) + msg_len; - } - - user_msg = (char *)malloc(sizeof(char) * bufsize); - if (!user_msg) { - D(("Out of memory.")); - return PAM_SYSTEM_ERR; - } - - ret = snprintf(user_msg, bufsize, "%s%s%.*s", - _("Password change failed. "), - msg_len > 0 ? _("Server message: ") : "", - (int)msg_len, - msg_len > 0 ? (char *)(buf + 2 * sizeof(uint32_t)) : "" ); - if (ret < 0 || ret > bufsize) { - D(("snprintf failed.")); - - free(user_msg); - return PAM_SYSTEM_ERR; - } - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); - free(user_msg); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int eval_user_info_response(pam_handle_t *pamh, size_t buflen, - uint8_t *buf) -{ - int ret; - uint32_t type; - - if (buflen < sizeof(uint32_t)) { - D(("User info response data is too short")); - return PAM_BUF_ERR; - } - - memcpy(&type, buf, sizeof(uint32_t)); - - switch(type) { - case SSS_PAM_USER_INFO_OFFLINE_AUTH: - ret = user_info_offline_auth(pamh, buflen, buf); - break; - case SSS_PAM_USER_INFO_GRACE_LOGIN: - ret = user_info_grace_login(pamh, buflen, buf); - break; - case SSS_PAM_USER_INFO_EXPIRE_WARN: - ret = user_info_expire_warn(pamh, buflen, buf); - break; - case SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED: - ret = user_info_offline_auth_delayed(pamh, buflen, buf); - break; - case SSS_PAM_USER_INFO_OFFLINE_CHPASS: - ret = user_info_offline_chpass(pamh); - break; - case SSS_PAM_USER_INFO_OTP_CHPASS: - ret = user_info_otp_chpass(pamh); - break; - case SSS_PAM_USER_INFO_CHPASS_ERROR: - ret = user_info_chpass_error(pamh, buflen, buf); - break; - case SSS_PAM_USER_INFO_PIN_LOCKED: - ret = user_info_pin_locked(pamh); - break; - case SSS_PAM_USER_INFO_ACCOUNT_EXPIRED: - ret = user_info_account_expired(pamh, buflen, buf); - break; - case SSS_PAM_USER_INFO_NO_KRB_TGT: - ret = user_info_no_krb_tgt(pamh); - break; - default: - D(("Unknown user info type [%d]", type)); - ret = PAM_SYSTEM_ERR; - } - - return ret; -} - -static int parse_cert_info(struct pam_items *pi, uint8_t *buf, size_t len, - size_t *p, const char **cert_user, - const char **pam_cert_user) -{ - struct cert_auth_info *cai = NULL; - size_t offset; - int ret; - - if (buf[*p + (len - 1)] != '\0') { - D(("cert info does not end with \\0.")); - return EINVAL; - } - - cai = calloc(1, sizeof(struct cert_auth_info)); - if (cai == NULL) { - return ENOMEM; - } - - cai->cert_user = strdup((char *) &buf[*p]); - if (cai->cert_user == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - if (cert_user != NULL) { - *cert_user = cai->cert_user; - } - - offset = strlen(cai->cert_user) + 1; - if (offset >= len) { - D(("Cert message size mismatch")); - ret = EINVAL; - goto done; - } - - cai->token_name = strdup((char *) &buf[*p + offset]); - if (cai->token_name == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - - offset += strlen(cai->token_name) + 1; - if (offset >= len) { - D(("Cert message size mismatch")); - ret = EINVAL; - goto done; - } - - cai->module_name = strdup((char *) &buf[*p + offset]); - if (cai->module_name == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - - offset += strlen(cai->module_name) + 1; - if (offset >= len) { - D(("Cert message size mismatch")); - ret = EINVAL; - goto done; - } - - cai->key_id = strdup((char *) &buf[*p + offset]); - if (cai->key_id == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - - offset += strlen(cai->key_id) + 1; - if (offset >= len) { - D(("Cert message size mismatch")); - ret = EINVAL; - goto done; - } - - cai->label = strdup((char *) &buf[*p + offset]); - if (cai->label == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - - offset += strlen(cai->label) + 1; - if (offset >= len) { - D(("Cert message size mismatch")); - ret = EINVAL; - goto done; - } - - cai->prompt_str = strdup((char *) &buf[*p + offset]); - if (cai->prompt_str == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - - offset += strlen(cai->prompt_str) + 1; - if (offset >= len) { - D(("Cert message size mismatch")); - ret = EINVAL; - goto done; - } - - cai->pam_cert_user = strdup((char *) &buf[*p + offset]); - if (cai->pam_cert_user == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - if (pam_cert_user != NULL) { - *pam_cert_user = cai->pam_cert_user; - } - - D(("cert user: [%s] token name: [%s] module: [%s] key id: [%s] " - "prompt: [%s] pam cert user: [%s]", - cai->cert_user, cai->token_name, cai->module_name, - cai->key_id, cai->prompt_str, cai->pam_cert_user)); - - DLIST_ADD(pi->cert_list, cai); - ret = 0; - -done: - if (ret != 0) { - free_cai(cai); - } - - return ret; -} - -static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf, - struct pam_items *pi) -{ - int ret; - size_t p=0; - char *env_item; - int32_t c; - int32_t type; - int32_t len; - int32_t pam_status; - size_t offset; - const char *cert_user; - const char *pam_cert_user; - - if (buflen < (2*sizeof(int32_t))) { - D(("response buffer is too small")); - return PAM_BUF_ERR; - } - - memcpy(&pam_status, buf+p, sizeof(int32_t)); - p += sizeof(int32_t); - - - memcpy(&c, buf+p, sizeof(int32_t)); - p += sizeof(int32_t); - - while(c>0) { - if (buflen < (p+2*sizeof(int32_t))) { - D(("response buffer is too small")); - return PAM_BUF_ERR; - } - - memcpy(&type, buf+p, sizeof(int32_t)); - p += sizeof(int32_t); - - memcpy(&len, buf+p, sizeof(int32_t)); - p += sizeof(int32_t); - - if (buflen < (p + len)) { - D(("response buffer is too small")); - return PAM_BUF_ERR; - } - - switch(type) { - case SSS_PAM_SYSTEM_INFO: - if (buf[p + (len -1)] != '\0') { - D(("system info does not end with \\0.")); - break; - } - logger(pamh, LOG_INFO, "system info: [%s]", &buf[p]); - break; - case SSS_PAM_DOMAIN_NAME: - if (buf[p + (len -1)] != '\0') { - D(("domain name does not end with \\0.")); - break; - } - D(("domain name: [%s]", &buf[p])); - free(pi->domain_name); - pi->domain_name = strdup((char *) &buf[p]); - if (pi->domain_name == NULL) { - D(("strdup failed")); - } - break; - case SSS_ENV_ITEM: - case SSS_PAM_ENV_ITEM: - case SSS_ALL_ENV_ITEM: - if (buf[p + (len -1)] != '\0') { - D(("env item does not end with \\0.")); - break; - } - - D(("env item: [%s]", &buf[p])); - if (type == SSS_PAM_ENV_ITEM || type == SSS_ALL_ENV_ITEM) { - ret = pam_putenv(pamh, (char *)&buf[p]); - if (ret != PAM_SUCCESS) { - D(("pam_putenv failed.")); - break; - } - } - - if (type == SSS_ENV_ITEM || type == SSS_ALL_ENV_ITEM) { - env_item = strdup((char *)&buf[p]); - if (env_item == NULL) { - D(("strdup failed")); - break; - } - ret = putenv(env_item); - if (ret == -1) { - D(("putenv failed.")); - break; - } - } - break; - case SSS_PAM_USER_INFO: - ret = eval_user_info_response(pamh, len, &buf[p]); - if (ret != PAM_SUCCESS) { - D(("eval_user_info_response failed")); - } - break; - case SSS_PAM_TEXT_MSG: - if (buf[p + (len -1)] != '\0') { - D(("system info does not end with \\0.")); - break; - } - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, (char *) &buf[p], - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - } - break; - case SSS_OTP: - D(("OTP was used, removing authtokens.")); - overwrite_and_free_authtoks(pi); - ret = pam_set_item(pamh, PAM_AUTHTOK, NULL); - if (ret != PAM_SUCCESS) { - D(("Failed to remove PAM_AUTHTOK after using otp [%s]", - pam_strerror(pamh,ret))); - } - break; - case SSS_PAM_OTP_INFO: - if (buf[p + (len - 1)] != '\0') { - D(("otp info does not end with \\0.")); - break; - } - - free(pi->otp_vendor); - pi->otp_vendor = strdup((char *) &buf[p]); - if (pi->otp_vendor == NULL) { - D(("strdup failed")); - break; - } - - offset = strlen(pi->otp_vendor) + 1; - if (offset >= len) { - D(("OTP message size mismatch")); - free(pi->otp_vendor); - pi->otp_vendor = NULL; - break; - } - free(pi->otp_token_id); - pi->otp_token_id = strdup((char *) &buf[p + offset]); - if (pi->otp_token_id == NULL) { - D(("strdup failed")); - break; - } - - offset += strlen(pi->otp_token_id) + 1; - if (offset >= len) { - D(("OTP message size mismatch")); - free(pi->otp_token_id); - pi->otp_token_id = NULL; - break; - } - free(pi->otp_challenge); - pi->otp_challenge = strdup((char *) &buf[p + offset]); - if (pi->otp_challenge == NULL) { - D(("strdup failed")); - break; - } - - break; - case SSS_PAM_CERT_INFO: - case SSS_PAM_CERT_INFO_WITH_HINT: - if (buf[p + (len - 1)] != '\0') { - D(("cert info does not end with \\0.")); - break; - } - - if (type == SSS_PAM_CERT_INFO_WITH_HINT) { - pi->user_name_hint = true; - } else { - pi->user_name_hint = false; - } - - ret = parse_cert_info(pi, buf, len, &p, &cert_user, - &pam_cert_user); - if (ret != 0) { - D(("Failed to parse cert info")); - break; - } - - if ((pi->pam_user == NULL || *(pi->pam_user) == '\0') - && *cert_user != '\0' && *pam_cert_user != '\0') { - ret = pam_set_item(pamh, PAM_USER, pam_cert_user); - if (ret != PAM_SUCCESS) { - D(("Failed to set PAM_USER during " - "Smartcard authentication [%s]", - pam_strerror(pamh, ret))); - break; - } - - pi->pam_user = cert_user; - pi->pam_user_size = strlen(pi->pam_user) + 1; - } - break; - case SSS_PASSWORD_PROMPTING: - 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; - case SSS_CHILD_KEEP_ALIVE: - memcpy(&pi->child_pid, &buf[p], len); - break; - case SSS_PAM_OAUTH2_INFO: - if (buf[p + (len - 1)] != '\0') { - D(("oauth2 info does not end with \\0.")); - break; - } - - free(pi->oauth2_url); - pi->oauth2_url = strdup((char *) &buf[p]); - if (pi->oauth2_url == NULL) { - D(("strdup failed")); - break; - } - - offset = strlen(pi->oauth2_url) + 1; - if (offset >= len) { - D(("OAuth2 message size mismatch")); - free(pi->oauth2_url); - pi->oauth2_url = NULL; - break; - } - - free(pi->oauth2_url_complete); - pi->oauth2_url_complete = strdup((char *) &buf[p + offset]); - if (pi->oauth2_url_complete == NULL) { - D(("strdup failed")); - break; - } - - offset = offset + strlen(pi->oauth2_url_complete) + 1; - if (offset >= len) { - D(("OAuth2 message size mismatch")); - free(pi->oauth2_url_complete); - pi->oauth2_url_complete = NULL; - break; - } - - /* This field is optional. */ - if (pi->oauth2_url_complete[0] == '\0') { - free(pi->oauth2_url_complete); - pi->oauth2_url_complete = NULL; - } - - free(pi->oauth2_pin); - pi->oauth2_pin = strdup((char *) &buf[p + offset]); - if (pi->oauth2_pin == NULL) { - D(("strdup failed")); - break; - } - - break; - case SSS_PAM_PASSKEY_KRB_INFO: - free(pi->passkey_prompt_pin); - pi->passkey_prompt_pin = strdup((char *) &buf[p]); - if (pi->passkey_prompt_pin == NULL) { - D(("strdup failed")); - break; - } - - offset = strlen(pi->passkey_prompt_pin) + 1; - if (offset >= len) { - D(("Passkey message size mismatch")); - free(pi->passkey_prompt_pin); - pi->passkey_prompt_pin = NULL; - break; - } - - free(pi->passkey_key); - pi->passkey_key = strdup((char *) &buf[p + offset]); - if (pi->passkey_key == NULL) { - D(("strdup failed")); - break; - } - break; - case SSS_PAM_PASSKEY_INFO: - if (buf[p + (len - 1)] != '\0') { - D(("passkey info does not end with \\0.")); - break; - } - - free(pi->passkey_prompt_pin); - pi->passkey_prompt_pin = strdup((char *) &buf[p]); - if (pi->passkey_prompt_pin == NULL) { - D(("strdup failed")); - break; - } - break; - default: - D(("Unknown response type [%d]", type)); - } - p += len; - - --c; - } - - return PAM_SUCCESS; -} - -bool is_string_empty_or_whitespace(const char *str) -{ - int i; - - if (str == NULL) { - return true; - } - - for (i = 0; str[i] != '\0'; i++) { - if (!isspace(str[i])) { - return false; - } - } - - return true; -} - -static int get_pam_items(pam_handle_t *pamh, uint32_t flags, - struct pam_items *pi) -{ - int ret; - - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; - pi->pam_authtok = NULL; - pi->pam_authtok_size = 0; - pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_EMPTY; - pi->pam_newauthtok = NULL; - pi->pam_newauthtok_size = 0; - pi->first_factor = NULL; - - ret = pam_get_item(pamh, PAM_SERVICE, (const void **) &(pi->pam_service)); - if (ret != PAM_SUCCESS) return ret; - if (pi->pam_service == NULL) pi->pam_service=""; - pi->pam_service_size=strlen(pi->pam_service)+1; - - ret = pam_get_item(pamh, PAM_USER, (const void **) &(pi->pam_user)); - if (ret == PAM_PERM_DENIED && (flags & PAM_CLI_FLAGS_ALLOW_MISSING_NAME)) { - pi->pam_user = ""; - ret = PAM_SUCCESS; - } - if (ret != PAM_SUCCESS) return ret; - if (flags & PAM_CLI_FLAGS_ALLOW_MISSING_NAME) { - if (is_string_empty_or_whitespace(pi->pam_user)) { - pi->pam_user = ""; - } - } - if (pi->pam_user == NULL) { - D(("No user found, aborting.")); - return PAM_BAD_ITEM; - } - if (strcmp(pi->pam_user, "root") == 0) { - D(("pam_sss will not handle root.")); - return PAM_USER_UNKNOWN; - } - pi->pam_user_size=strlen(pi->pam_user)+1; - - - ret = pam_get_item(pamh, PAM_TTY, (const void **) &(pi->pam_tty)); - if (ret != PAM_SUCCESS) return ret; - if (pi->pam_tty == NULL) pi->pam_tty=""; - pi->pam_tty_size=strlen(pi->pam_tty)+1; - - ret = pam_get_item(pamh, PAM_RUSER, (const void **) &(pi->pam_ruser)); - if (ret != PAM_SUCCESS) return ret; - if (pi->pam_ruser == NULL) pi->pam_ruser=""; - pi->pam_ruser_size=strlen(pi->pam_ruser)+1; - - ret = pam_get_item(pamh, PAM_RHOST, (const void **) &(pi->pam_rhost)); - if (ret != PAM_SUCCESS) return ret; - if (pi->pam_rhost == NULL) pi->pam_rhost=""; - pi->pam_rhost_size=strlen(pi->pam_rhost)+1; - - ret = pam_get_item(pamh, PAM_AUTHTOK, - (const void **) &(pi->pamstack_authtok)); - if (ret != PAM_SUCCESS) return ret; - if (pi->pamstack_authtok == NULL) pi->pamstack_authtok=""; - - ret = pam_get_item(pamh, PAM_OLDAUTHTOK, - (const void **) &(pi->pamstack_oldauthtok)); - if (ret != PAM_SUCCESS) return ret; - if (pi->pamstack_oldauthtok == NULL) pi->pamstack_oldauthtok=""; - - pi->cli_pid = getpid(); - - pi->login_name = pam_modutil_getlogin(pamh); - if (pi->login_name == NULL) pi->login_name=""; - - pi->domain_name = NULL; - - if (pi->requested_domains == NULL) pi->requested_domains = ""; - pi->requested_domains_size = strlen(pi->requested_domains) + 1; - - pi->otp_vendor = NULL; - pi->otp_token_id = NULL; - pi->otp_challenge = NULL; - pi->password_prompting = false; - - pi->cert_list = NULL; - pi->selected_cert = NULL; - - pi->pc = NULL; - - pi->flags = flags; - - return PAM_SUCCESS; -} - -static void print_pam_items(struct pam_items *pi) -{ - if (pi == NULL) return; - - D(("Service: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_service))); - D(("User: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_user))); - D(("Tty: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_tty))); - D(("Ruser: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_ruser))); - D(("Rhost: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_rhost))); - D(("Pamstack_Authtok: %s", - CHECK_AND_RETURN_PI_STRING(pi->pamstack_authtok))); - D(("Pamstack_Oldauthtok: %s", - CHECK_AND_RETURN_PI_STRING(pi->pamstack_oldauthtok))); - D(("Authtok: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_authtok))); - D(("Newauthtok: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_newauthtok))); - D(("Cli_PID: %d", pi->cli_pid)); - D(("Child_PID: %d", pi->child_pid)); - D(("Requested domains: %s", pi->requested_domains)); - D(("Flags: %d", pi->flags)); -} - -static int send_and_receive(pam_handle_t *pamh, struct pam_items *pi, - enum sss_cli_command task, bool quiet_mode) -{ - int ret; - int sret; - int errnop; - struct sss_cli_req_data rd; - uint8_t *buf = NULL; - uint8_t *repbuf = NULL; - size_t replen; - int pam_status = PAM_SYSTEM_ERR; - - print_pam_items(pi); - - ret = pack_message_v3(pi, &rd.len, &buf); - if (ret != 0) { - D(("pack_message failed.")); - pam_status = PAM_SYSTEM_ERR; - goto done; - } - rd.data = buf; - - errnop = 0; - ret = sss_pam_make_request(task, &rd, &repbuf, &replen, &errnop); - - sret = pam_set_data(pamh, FD_DESTRUCTOR, NULL, close_fd); - if (sret != PAM_SUCCESS) { - D(("pam_set_data failed, client might leaks fds")); - } - - if (ret != PAM_SUCCESS) { - /* If there is no PAM responder socket during the access control step - * we assume this is on purpose, i.e. PAM responder is not configured. - * PAM_USER_UNKNOWN is returned to the PAM stack to avoid unexpected - * denials. */ - if (errnop == ESSS_NO_SOCKET && task == SSS_PAM_ACCT_MGMT) { - pam_status = PAM_USER_UNKNOWN; - } else { - if (errnop != 0 && errnop != ESSS_NO_SOCKET) { - logger(pamh, LOG_ERR, "Request to sssd failed. %s", - ssscli_err2string(errnop)); - } - - pam_status = PAM_AUTHINFO_UNAVAIL; - } - goto done; - } - -/* FIXME: add an end signature */ - if (replen < (2*sizeof(int32_t))) { - D(("response not in expected format.")); - pam_status = PAM_SYSTEM_ERR; - goto done; - } - - SAFEALIGN_COPY_UINT32(&pam_status, repbuf, NULL); - ret = eval_response(pamh, replen, repbuf, pi); - if (ret != PAM_SUCCESS) { - D(("eval_response failed.")); - pam_status = ret; - goto done; - } - - switch (task) { - case SSS_PAM_AUTHENTICATE: - logger(pamh, (pam_status == PAM_SUCCESS ? LOG_INFO : LOG_NOTICE), - "authentication %s; logname=%s uid=%lu euid=%d tty=%s " - "ruser=%s rhost=%s user=%s", - pam_status == PAM_SUCCESS ? "success" : "failure", - pi->login_name, getuid(), (unsigned long) geteuid(), - pi->pam_tty, pi->pam_ruser, pi->pam_rhost, pi->pam_user); - if (pam_status != PAM_SUCCESS) { - /* don't log if quiet_mode is on and pam_status is - * User not known to the underlying authentication module - */ - if (!quiet_mode || pam_status != 10) { - logger(pamh, LOG_NOTICE, "received for user %s: %d (%s)", - pi->pam_user, pam_status, - pam_strerror(pamh,pam_status)); - } - } - break; - case SSS_PAM_CHAUTHTOK_PRELIM: - if (pam_status != PAM_SUCCESS) { - /* don't log if quiet_mode is on and pam_status is - * User not known to the underlying authentication module - */ - if (!quiet_mode || pam_status != 10) { - logger(pamh, LOG_NOTICE, - "Authentication failed for user %s: %d (%s)", - pi->pam_user, pam_status, - pam_strerror(pamh,pam_status)); - } - } - break; - case SSS_PAM_CHAUTHTOK: - if (pam_status != PAM_SUCCESS) { - logger(pamh, LOG_NOTICE, - "Password change failed for user %s: %d (%s)", - pi->pam_user, pam_status, - pam_strerror(pamh,pam_status)); - } - break; - case SSS_PAM_ACCT_MGMT: - if (pam_status != PAM_SUCCESS) { - /* don't log if quiet_mode is on and pam_status is - * User not known to the underlying authentication module - */ - if (!quiet_mode || pam_status != 10) { - logger(pamh, LOG_NOTICE, - "Access denied for user %s: %d (%s)", - pi->pam_user, pam_status, - pam_strerror(pamh,pam_status)); - } - } - break; - case SSS_PAM_OPEN_SESSION: - case SSS_PAM_SETCRED: - case SSS_PAM_CLOSE_SESSION: - case SSS_PAM_PREAUTH: - break; - default: - D(("Illegal task [%#x]", task)); - pam_status = PAM_SYSTEM_ERR; - } - -done: - if (buf != NULL ) { - sss_erase_mem_securely((void *)buf, rd.len); - free(buf); - } - free(repbuf); - - return pam_status; -} - -static int prompt_password(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); - sss_erase_mem_securely((void *)answer, strlen(answer)); - free(answer); - answer=NULL; - if (pi->pam_authtok == NULL) { - return PAM_BUF_ERR; - } - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD; - pi->pam_authtok_size=strlen(pi->pam_authtok); - } - - return PAM_SUCCESS; -} - -static int prompt_2fa(pam_handle_t *pamh, struct pam_items *pi, - bool second_factor_optional, - const char *prompt_fa1, const char *prompt_fa2) -{ - int ret; - const struct pam_conv *conv; - const struct pam_message *mesg[2] = { NULL, NULL }; - struct pam_message m[2] = { {0}, {0} }; - struct pam_response *resp = NULL; - size_t needed_size; - - ret = pam_get_item(pamh, PAM_CONV, (const void **) &conv); - if (ret != PAM_SUCCESS) { - return ret; - } - if (conv == NULL || conv->conv == NULL) { - logger(pamh, LOG_ERR, "No conversation function"); - return PAM_SYSTEM_ERR; - } - - m[0].msg_style = PAM_PROMPT_ECHO_OFF; - m[0].msg = prompt_fa1; - m[1].msg_style = PAM_PROMPT_ECHO_OFF; - m[1].msg = prompt_fa2; - - mesg[0] = (const struct pam_message *) m; - /* The following assignment might look a bit odd but is recommended in the - * pam_conv man page to make sure that the second argument of the PAM - * conversation function can be interpreted in two different ways. - * Basically it is important that both the actual struct pam_message and - * the pointers to the struct pam_message are arrays. Since the assignment - * makes clear that mesg[] and (*mesg)[] are arrays it should be kept this - * way and not be replaced by other equivalent assignments. */ - mesg[1] = & (( *mesg )[1]); - - ret = conv->conv(2, mesg, &resp, conv->appdata_ptr); - if (ret != PAM_SUCCESS) { - D(("Conversation failure: %s.", pam_strerror(pamh, ret))); - return ret; - } - - if (resp == NULL) { - D(("response expected, but resp==NULL")); - return PAM_SYSTEM_ERR; - } - - if (resp[0].resp == NULL || *(resp[0].resp) == '\0') { - D(("Missing factor.")); - ret = PAM_CRED_INSUFFICIENT; - goto done; - } - - if (resp[1].resp == NULL || *(resp[1].resp) == '\0') { - /* Missing second factor, assume first factor contains combined 2FA - * credentials if the second factor is not optional. If it is optional - * then it is assumed that the first factor contain the password. */ - pi->pam_authtok = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); - if (pi->pam_authtok == NULL) { - D(("strndup failed.")); - ret = PAM_BUF_ERR; - goto done; - } - pi->pam_authtok_size = strlen(pi->pam_authtok) + 1; - pi->pam_authtok_type = second_factor_optional - ? SSS_AUTHTOK_TYPE_PASSWORD - : SSS_AUTHTOK_TYPE_2FA_SINGLE; - } else if (pi->pam_service != NULL && strcmp(pi->pam_service, "sshd") == 0 - && strcmp(resp[0].resp, resp[1].resp) == 0) { - /* Special handling for SSH with password authentication (ssh's - * 'PasswordAuthentication' option. In this mode the ssh client - * directly prompts the user for a password and the prompts we are - * sending are ignored. Since we send two prompts ssh * will create two - * response as well with the same content. We assume that the combined - * 2FA credentials are used even if the second factor is optional - * because there is no indication about the intention of the user. As a - * result we prefer the more secure variant. */ - - pi->pam_authtok = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); - if (pi->pam_authtok == NULL) { - D(("strndup failed.")); - ret = PAM_BUF_ERR; - goto done; - } - pi->pam_authtok_size = strlen(pi->pam_authtok) + 1; - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_2FA_SINGLE; - } else { - - ret = sss_auth_pack_2fa_blob(resp[0].resp, 0, resp[1].resp, 0, NULL, 0, - &needed_size); - if (ret != EAGAIN) { - D(("sss_auth_pack_2fa_blob failed.")); - ret = PAM_BUF_ERR; - goto done; - } - - pi->pam_authtok = malloc(needed_size); - if (pi->pam_authtok == NULL) { - D(("malloc failed.")); - ret = PAM_BUF_ERR; - goto done; - } - - ret = sss_auth_pack_2fa_blob(resp[0].resp, 0, resp[1].resp, 0, - (uint8_t *) pi->pam_authtok, needed_size, - &needed_size); - if (ret != EOK) { - D(("sss_auth_pack_2fa_blob failed.")); - ret = PAM_BUF_ERR; - goto done; - } - - pi->pam_authtok_size = needed_size; - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_2FA; - pi->first_factor = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); - if (pi->first_factor == NULL) { - D(("strndup failed.")); - ret = PAM_BUF_ERR; - goto done; - } - } - - ret = PAM_SUCCESS; - -done: - if (resp != NULL) { - if (resp[0].resp != NULL) { - sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); - free(resp[0].resp); - } - if (resp[1].resp != NULL) { - sss_erase_mem_securely((void *)resp[1].resp, strlen(resp[1].resp)); - free(resp[1].resp); - } - - free(resp); - resp = NULL; - } - - 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); - sss_erase_mem_securely((void *)answer, strlen(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; -} - -static int prompt_oauth2(pam_handle_t *pamh, struct pam_items *pi) -{ - char *answer = NULL; - char *msg; - int ret; - - if (pi->oauth2_url_complete != NULL) { - ret = asprintf(&msg, _("Authenticate at %1$s and press ENTER."), - pi->oauth2_url_complete); - } else { - ret = asprintf(&msg, _("Authenticate with PIN %1$s at %2$s and press " - "ENTER."), pi->oauth2_pin, pi->oauth2_url); - } - if (ret == -1) { - return PAM_SYSTEM_ERR; - } - - ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, msg, NULL, &answer); - free(msg); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return ret; - } - - /* We don't care about answer here. We just need to notify that the - * authentication has finished. */ - free(answer); - - pi->pam_authtok = strdup(pi->oauth2_pin); - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_OAUTH2; - pi->pam_authtok_size=strlen(pi->oauth2_pin); - - return PAM_SUCCESS; -} - -static int prompt_passkey(pam_handle_t *pamh, struct pam_items *pi, - const char *prompt_interactive, const char *prompt_touch) -{ - int ret; - const struct pam_conv *conv; - const struct pam_message *mesg[4] = { NULL, NULL, NULL, NULL }; - struct pam_message m[4] = { {0}, {0}, {0}, {0} }; - struct pam_response *resp = NULL; - bool kerberos_preauth; - bool prompt_pin; - int pin_idx = 0; - int msg_idx = 0; - size_t needed_size; - - ret = pam_get_item(pamh, PAM_CONV, (const void **) &conv); - if (ret != PAM_SUCCESS) { - return ret; - } - if (conv == NULL || conv->conv == NULL) { - logger(pamh, LOG_ERR, "No conversation function"); - return PAM_SYSTEM_ERR; - } - - kerberos_preauth = pi->passkey_key != NULL ? true : false; - if (!kerberos_preauth) { - m[msg_idx].msg_style = PAM_TEXT_INFO; - m[msg_idx].msg = PASSKEY_LOCAL_AUTH_MSG; - msg_idx++; - } - - if ((strcasecmp(pi->passkey_prompt_pin, "false")) == 0) { - prompt_pin = false; - } else { - prompt_pin = true; - } - - /* Interactive, prompt a message and wait before continuing */ - if (prompt_interactive != NULL && prompt_interactive[0] != '\0') { - m[msg_idx].msg_style = PAM_PROMPT_ECHO_OFF; - m[msg_idx].msg = prompt_interactive; - msg_idx++; - } - - /* Prompt for PIN - * - * If prompt_pin is false but a PIN is set on the device - * we still prompt for PIN */ - if (prompt_pin) { - m[msg_idx].msg_style = PAM_PROMPT_ECHO_OFF; - m[msg_idx].msg = PASSKEY_DEFAULT_PIN_MSG; - pin_idx = msg_idx; - msg_idx++; - } - - /* Prompt to remind the user to touch the device */ - if (prompt_touch != NULL && prompt_touch[0] != '\0') { - m[msg_idx].msg_style = PAM_PROMPT_ECHO_OFF; - m[msg_idx].msg = prompt_touch; - msg_idx++; - } - - mesg[0] = (const struct pam_message *) m; - /* The following assignment might look a bit odd but is recommended in the - * pam_conv man page to make sure that the second argument of the PAM - * conversation function can be interpreted in two different ways. - * Basically it is important that both the actual struct pam_message and - * the pointers to the struct pam_message are arrays. Since the assignment - * makes clear that mesg[] and (*mesg)[] are arrays it should be kept this - * way and not be replaced by other equivalent assignments. */ - for (int i = 1; i < msg_idx; i++) { - mesg[i] = & (( *mesg )[i]); - } - - ret = conv->conv(msg_idx, mesg, &resp, conv->appdata_ptr); - if (ret != PAM_SUCCESS) { - D(("Conversation failure: %s.", pam_strerror(pamh, ret))); - return ret; - } - - if (kerberos_preauth) { - if (!prompt_pin) { - resp[pin_idx].resp = NULL; - } - - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY_KRB; - sss_auth_passkey_calc_size(pi->passkey_prompt_pin, - pi->passkey_key, - resp[pin_idx].resp, - &needed_size); - - pi->pam_authtok = malloc(needed_size); - if (pi->pam_authtok == NULL) { - D(("malloc failed.")); - ret = PAM_BUF_ERR; - goto done; - } - - sss_auth_pack_passkey_blob((uint8_t *)pi->pam_authtok, - pi->passkey_prompt_pin, pi->passkey_key, - resp[pin_idx].resp); - - } else { - if (!prompt_pin) { - /* user verification = false, SSS_AUTHTOK_TYPE_PASSKEY will be reset to - * SSS_AUTHTOK_TYPE_NULL in PAM responder - */ - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY; - pi->pam_authtok = NULL; - pi->pam_authtok_size = 0; - ret = PAM_SUCCESS; - goto done; - } else { - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY; - pi->pam_authtok = strdup(resp[pin_idx].resp); - needed_size = strlen(pi->pam_authtok); - } - } - - pi->pam_authtok_size = needed_size; - - /* Fallback to password auth if no PIN was entered */ - if (prompt_pin) { - if (resp[pin_idx].resp == NULL || resp[pin_idx].resp[0] == '\0') { - ret = EIO; - goto done; - } - } - - ret = PAM_SUCCESS; - -done: - if (resp != NULL) { - if (resp[pin_idx].resp != NULL) { - sss_erase_mem_securely((void *)resp[pin_idx].resp, - strlen(resp[pin_idx].resp)); - free(resp[pin_idx].resp); - } - - free(resp); - resp = NULL; - } - - return ret; -} - -#define SC_PROMPT_FMT "PIN for %s: " - -#ifndef discard_const -#define discard_const(ptr) ((void *)((uintptr_t)(ptr))) -#endif - -#define CERT_SEL_PROMPT_FMT "%s" -#define SEL_TITLE discard_const("Please select a certificate") - -static int prompt_multi_cert_gdm(pam_handle_t *pamh, struct pam_items *pi) -{ -#ifdef HAVE_GDM_PAM_EXTENSIONS - int ret; - size_t cert_count = 0; - size_t c; - const struct pam_conv *conv; - struct cert_auth_info *cai; - GdmPamExtensionChoiceListRequest *request = NULL; - GdmPamExtensionChoiceListResponse *response = NULL; - struct pam_message prompt_message; - const struct pam_message *prompt_messages[1]; - struct pam_response *reply = NULL; - char *prompt; - - if (!GDM_PAM_EXTENSION_SUPPORTED(GDM_PAM_EXTENSION_CHOICE_LIST)) { - return ENOTSUP; - } - - if (pi->cert_list == NULL) { - return EINVAL; - } - - DLIST_FOR_EACH(cai, pi->cert_list) { - cert_count++; - } - - ret = pam_get_item(pamh, PAM_CONV, (const void **)&conv); - if (ret != PAM_SUCCESS) { - ret = EIO; - return ret; - } - - request = calloc(1, GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_SIZE(cert_count)); - if (request == NULL) { - ret = ENOMEM; - goto done; - } - GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_INIT(request, SEL_TITLE, cert_count); - - c = 0; - DLIST_FOR_EACH(cai, pi->cert_list) { - ret = asprintf(&prompt, CERT_SEL_PROMPT_FMT, cai->prompt_str); - if (ret == -1) { - ret = ENOMEM; - goto done; - } - free(cai->choice_list_id); - ret = asprintf(&cai->choice_list_id, "%zu", c); - if (ret == -1) { - cai->choice_list_id = NULL; - free(prompt); - ret = ENOMEM; - goto done; - } - - request->list.items[c].key = cai->choice_list_id; - request->list.items[c++].text = prompt; - } - - GDM_PAM_EXTENSION_MESSAGE_TO_BINARY_PROMPT_MESSAGE(request, - &prompt_message); - prompt_messages[0] = &prompt_message; - - ret = conv->conv(1, prompt_messages, &reply, conv->appdata_ptr); - if (ret != PAM_SUCCESS) { - ret = EIO; - goto done; - } - - ret = EIO; - response = GDM_PAM_EXTENSION_REPLY_TO_CHOICE_LIST_RESPONSE(reply); - if (response->key == NULL) { - goto done; - } - - DLIST_FOR_EACH(cai, pi->cert_list) { - if (strcmp(response->key, cai->choice_list_id) == 0) { - pam_info(pamh, "Certificate ‘%s’ selected", cai->key_id); - pi->selected_cert = cai; - ret = 0; - break; - } - } - -done: - if (request != NULL) { - for (c = 0; c < cert_count; c++) { - free(discard_const(request->list.items[c++].text)); - } - free(request); - } - free(response); - - return ret; -#else - return ENOTSUP; -#endif -} - -#define TEXT_CERT_SEL_PROMPT_FMT "%s\n[%zu]:\n%s\n" -#define TEXT_SEL_TITLE discard_const("Please select a certificate by typing " \ - "the corresponding number\n") - -static int prompt_multi_cert(pam_handle_t *pamh, struct pam_items *pi) -{ - int ret; - size_t cert_count = 0; - size_t tries = 0; - long int resp = -1; - struct cert_auth_info *cai; - char *prompt; - char *tmp; - char *answer; - char *ep; - - /* First check if gdm extension is supported */ - ret = prompt_multi_cert_gdm(pamh, pi); - if (ret != ENOTSUP) { - return ret; - } - - if (pi->cert_list == NULL) { - return EINVAL; - } - - prompt = strdup(TEXT_SEL_TITLE); - if (prompt == NULL) { - return ENOMEM; - } - - DLIST_FOR_EACH(cai, pi->cert_list) { - cert_count++; - ret = asprintf(&tmp, TEXT_CERT_SEL_PROMPT_FMT, prompt, cert_count, - cai->prompt_str); - free(prompt); - if (ret == -1) { - return ENOMEM; - } - - prompt = tmp; - } - - do { - ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_ON, prompt, NULL, - &answer); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - break; - } - - errno = 0; - resp = strtol(answer, &ep, 10); - if (errno == 0 && *ep == '\0' && resp > 0 && resp <= cert_count) { - /* do not free answer ealier because ep is pointing to it */ - free(answer); - break; - } - free(answer); - resp = -1; - } while (++tries < 5); - free(prompt); - - pi->selected_cert = NULL; - ret = ENOENT; - if (resp > 0 && resp <= cert_count) { - cert_count = 0; - DLIST_FOR_EACH(cai, pi->cert_list) { - cert_count++; - if (resp == cert_count) { - pam_info(pamh, "Certificate ‘%s’ selected", cai->key_id); - pi->selected_cert = cai; - ret = 0; - break; - } - } - } - - return ret; -} - -#define SC_INSERT_PROMPT _("Please (re)insert (different) Smartcard") - -static int prompt_sc_pin(pam_handle_t *pamh, struct pam_items *pi) -{ - int ret; - char *answer = NULL; - char *prompt = NULL; - size_t needed_size; - const struct pam_conv *conv; - const struct pam_message *mesg[2] = { NULL, NULL }; - struct pam_message m[2] = { { 0 }, { 0 } }; - struct pam_response *resp = NULL; - struct cert_auth_info *cai = pi->selected_cert; - - if (cai == NULL && (SERVICE_IS_GDM_SMARTCARD(pi) - || (pi->flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH))) { - ret = asprintf(&prompt, SC_INSERT_PROMPT); - } else if (cai == NULL || cai->token_name == NULL - || *cai->token_name == '\0') { - return PAM_SYSTEM_ERR; - } else { - ret = asprintf(&prompt, SC_PROMPT_FMT, cai->token_name); - } - - if (ret == -1) { - D(("asprintf failed.")); - return PAM_SYSTEM_ERR; - } - - if (cai == NULL) { - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, prompt, NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("Conversation failure: %s, ignored", pam_strerror(pamh, ret))); - } - } - - if (pi->user_name_hint) { - ret = pam_get_item(pamh, PAM_CONV, (const void **)&conv); - if (ret != PAM_SUCCESS) { - free(prompt); - return ret; - } - if (conv == NULL || conv->conv == NULL) { - logger(pamh, LOG_ERR, "No conversation function"); - free(prompt); - return PAM_SYSTEM_ERR; - } - - m[0].msg_style = PAM_PROMPT_ECHO_OFF; - m[0].msg = prompt; - m[1].msg_style = PAM_PROMPT_ECHO_ON; - m[1].msg = "User name hint: "; - - mesg[0] = (const struct pam_message *)m; - /* The following assignment might look a bit odd but is recommended in the - * pam_conv man page to make sure that the second argument of the PAM - * conversation function can be interpreted in two different ways. - * Basically it is important that both the actual struct pam_message and - * the pointers to the struct pam_message are arrays. Since the assignment - * makes clear that mesg[] and (*mesg)[] are arrays it should be kept this - * way and not be replaced by other equivalent assignments. */ - mesg[1] = &((*mesg)[1]); - - ret = conv->conv(2, mesg, &resp, conv->appdata_ptr); - free(prompt); - if (ret != PAM_SUCCESS) { - D(("Conversation failure: %s.", pam_strerror(pamh, ret))); - return ret; - } - - if (resp == NULL) { - D(("response expected, but resp==NULL")); - return PAM_SYSTEM_ERR; - } - - if (resp[0].resp == NULL || *(resp[0].resp) == '\0') { - D(("Missing PIN.")); - ret = PAM_CRED_INSUFFICIENT; - goto done; - } - - answer = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); - sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); - free(resp[0].resp); - resp[0].resp = NULL; - if (answer == NULL) { - D(("strndup failed")); - ret = PAM_BUF_ERR; - goto done; - } - - if (resp[1].resp != NULL && *(resp[1].resp) != '\0') { - ret = pam_set_item(pamh, PAM_USER, resp[1].resp); - free(resp[1].resp); - resp[1].resp = NULL; - if (ret != PAM_SUCCESS) { - D(("Failed to set PAM_USER with user name hint [%s]", - pam_strerror(pamh, ret))); - goto done; - } - - ret = pam_get_item(pamh, PAM_USER, (const void **)&(pi->pam_user)); - if (ret != PAM_SUCCESS) { - D(("Failed to get PAM_USER with user name hint [%s]", - pam_strerror(pamh, ret))); - goto done; - } - - pi->pam_user_size = strlen(pi->pam_user) + 1; - } - } else { - ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, prompt, NULL, - &answer); - free(prompt); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return ret; - } - } - - if (cai == NULL) { - /* it is expected that the user just replaces the Smartcard which - * would trigger gdm to restart the PAM module, so it is not - * expected that this part of the code is reached. */ - ret = PAM_AUTHINFO_UNAVAIL; - goto done; - } - - if (answer == NULL || *answer == '\0') { - D(("Missing PIN.")); - ret = PAM_CRED_INSUFFICIENT; - goto done; - } else { - - ret = sss_auth_pack_sc_blob(answer, 0, cai->token_name, 0, - cai->module_name, 0, - cai->key_id, 0, - cai->label, 0, - NULL, 0, &needed_size); - if (ret != EAGAIN) { - D(("sss_auth_pack_sc_blob failed.")); - ret = PAM_BUF_ERR; - goto done; - } - - pi->pam_authtok = malloc(needed_size); - if (pi->pam_authtok == NULL) { - D(("malloc failed.")); - ret = PAM_BUF_ERR; - goto done; - } - - ret = sss_auth_pack_sc_blob(answer, 0, cai->token_name, 0, - cai->module_name, 0, - cai->key_id, 0, - cai->label, 0, - (uint8_t *) pi->pam_authtok, needed_size, - &needed_size); - if (ret != EOK) { - D(("sss_auth_pack_sc_blob failed.")); - free((void *)pi->pam_authtok); - ret = PAM_BUF_ERR; - goto done; - } - - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_SC_PIN; - pi->pam_authtok_size = needed_size; - } - - ret = PAM_SUCCESS; - -done: - if (answer != NULL) { - sss_erase_mem_securely((void *)answer, strlen(answer)); - free(answer); - answer=NULL; - } - - if (resp != NULL) { - if (resp[0].resp != NULL) { - sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); - free(resp[0].resp); - } - if (resp[1].resp != NULL) { - sss_erase_mem_securely((void *)resp[1].resp, strlen(resp[1].resp)); - free(resp[1].resp); - } - - free(resp); - resp = NULL; - } - - return ret; -} - -static int prompt_new_password(pam_handle_t *pamh, struct pam_items *pi) -{ - int ret; - char *answer = NULL; - - ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, - _("New Password: "), - _("Reenter new Password: "), - &answer); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return ret; - } - if (answer == NULL) { - pi->pam_newauthtok = NULL; - pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_EMPTY; - pi->pam_newauthtok_size=0; - } else { - pi->pam_newauthtok = strdup(answer); - sss_erase_mem_securely((void *)answer, strlen(answer)); - free(answer); - answer=NULL; - if (pi->pam_newauthtok == NULL) { - return PAM_BUF_ERR; - } - pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_PASSWORD; - pi->pam_newauthtok_size=strlen(pi->pam_newauthtok); - } - - return PAM_SUCCESS; -} - -static void eval_argv(pam_handle_t *pamh, int argc, const char **argv, - uint32_t *flags, int *retries, bool *quiet_mode, - const char **domains) -{ - char *ep; - - *quiet_mode = false; - - for (; argc-- > 0; ++argv) { - if (strcmp(*argv, "forward_pass") == 0) { - *flags |= PAM_CLI_FLAGS_FORWARD_PASS; - } else if (strcmp(*argv, "use_first_pass") == 0) { - *flags |= PAM_CLI_FLAGS_USE_FIRST_PASS; - } else if (strcmp(*argv, "use_authtok") == 0) { - *flags |= PAM_CLI_FLAGS_USE_AUTHTOK; - } else if (strncmp(*argv, OPT_DOMAINS_KEY, strlen(OPT_DOMAINS_KEY)) == 0) { - if (*(*argv+strlen(OPT_DOMAINS_KEY)) == '\0') { - logger(pamh, LOG_ERR, "Missing argument to option domains."); - *domains = ""; - } else { - *domains = *argv+strlen(OPT_DOMAINS_KEY); - } - - } else if (strncmp(*argv, OPT_RETRY_KEY, strlen(OPT_RETRY_KEY)) == 0) { - if (*(*argv+6) == '\0') { - logger(pamh, LOG_ERR, "Missing argument to option retry."); - *retries = 0; - } else { - errno = 0; - *retries = strtol(*argv+6, &ep, 10); - if (errno != 0) { - D(("strtol failed [%d][%s]", errno, strerror(errno))); - *retries = 0; - } - if (*ep != '\0') { - logger(pamh, LOG_ERR, "Argument to option retry contains " - "extra characters."); - *retries = 0; - } - if (*retries < 0) { - logger(pamh, LOG_ERR, "Argument to option retry must not " - "be negative."); - *retries = 0; - } - } - } else if (strcmp(*argv, "quiet") == 0) { - *quiet_mode = true; - } else if (strcmp(*argv, "allow_chauthtok_by_root") == 0) { - *flags |= PAM_CLI_FLAGS_ALLOW_CHAUTHTOK_BY_ROOT; - } else if (strcmp(*argv, "ignore_unknown_user") == 0) { - *flags |= PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER; - } else if (strcmp(*argv, "ignore_authinfo_unavail") == 0) { - *flags |= PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL; - } else if (strcmp(*argv, "use_2fa") == 0) { - *flags |= PAM_CLI_FLAGS_USE_2FA; - } else if (strcmp(*argv, "allow_missing_name") == 0) { - *flags |= PAM_CLI_FLAGS_ALLOW_MISSING_NAME; - } else if (strcmp(*argv, "prompt_always") == 0) { - *flags |= PAM_CLI_FLAGS_PROMPT_ALWAYS; - } else if (strcmp(*argv, "try_cert_auth") == 0) { - *flags |= PAM_CLI_FLAGS_TRY_CERT_AUTH; - } else if (strcmp(*argv, "require_cert_auth") == 0) { - *flags |= PAM_CLI_FLAGS_REQUIRE_CERT_AUTH; - } else { - logger(pamh, LOG_WARNING, "unknown option: %s", *argv); - } - } - - return; -} - -static int prompt_by_config(pam_handle_t *pamh, struct pam_items *pi) -{ - size_t c; - int ret = PAM_SUCCESS; - - if (pi->pc == NULL || *pi->pc == NULL) { - return PAM_SYSTEM_ERR; - } - - 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: - if (pi->password_prompting) { - ret = prompt_2fa(pamh, pi, true, pc_get_2fa_1st_prompt(pi->pc[c]), - pc_get_2fa_2nd_prompt(pi->pc[c])); - } else { - ret = prompt_2fa(pamh, pi, false, 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_PASSKEY: - ret = prompt_passkey(pamh, pi, - pc_get_passkey_inter_prompt(pi->pc[c]), - pc_get_passkey_touch_prompt(pi->pc[c])); - break; - case PC_TYPE_SC_PIN: - ret = prompt_sc_pin(pamh, pi); - /* Todo: add extra string option */ - break; - default: - ret = PAM_SYSTEM_ERR; - } - - /* 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) -{ - int ret; - const char *pin = NULL; - - if ((flags & PAM_CLI_FLAGS_USE_FIRST_PASS) - || ( pi->pamstack_authtok != NULL - && *(pi->pamstack_authtok) != '\0' - && !(flags & PAM_CLI_FLAGS_PROMPT_ALWAYS))) { - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PAM_STACKED; - pi->pam_authtok = strdup(pi->pamstack_authtok); - if (pi->pam_authtok == NULL) { - D(("option use_first_pass set, but no password found")); - return PAM_BUF_ERR; - } - pi->pam_authtok_size = strlen(pi->pam_authtok); - } else { - if (pi->oauth2_url != NULL) { - /* Prompt config is not supported for OAuth2. */ - ret = prompt_oauth2(pamh, pi); - } else if (pi->pc != NULL) { - ret = prompt_by_config(pamh, pi); - } 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 if (SERVICE_IS_GDM_SMARTCARD(pi) - || (pi->flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH)) { - /* Use pin prompt as fallback for gdm-smartcard */ - ret = prompt_sc_pin(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, true, _("First Factor: "), - _("Second Factor (optional): ")); - } else { - ret = prompt_2fa(pamh, pi, false, _("First Factor: "), - _("Second Factor: ")); - } - } else if (pi->passkey_prompt_pin) { - ret = prompt_passkey(pamh, pi, - _("Insert your passkey device, then press ENTER."), - ""); - /* Fallback to password auth if no PIN was entered */ - if (ret == EIO) { - ret = prompt_password(pamh, pi, _("Password: ")); - if (pi->pam_authtok_size == 0) { - D(("Empty password failure")); - pi->passkey_prompt_pin = NULL; - return PAM_AUTHTOK_ERR; - } - } - } else { - ret = prompt_password(pamh, pi, _("Password: ")); - } - } - if (ret != PAM_SUCCESS) { - D(("failed to get password from user")); - return ret; - } - - if (flags & PAM_CLI_FLAGS_FORWARD_PASS) { - if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_PASSWORD - || pi->pam_authtok_type == SSS_AUTHTOK_TYPE_PAM_STACKED) { - ret = pam_set_item(pamh, PAM_AUTHTOK, pi->pam_authtok); - } else if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_SC_PIN) { - pin = sss_auth_get_pin_from_sc_blob((uint8_t *) pi->pam_authtok, - pi->pam_authtok_size); - if (pin != NULL) { - ret = pam_set_item(pamh, PAM_AUTHTOK, pin); - } else { - ret = PAM_SYSTEM_ERR; - } - } else if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_2FA - && pi->first_factor != NULL) { - ret = pam_set_item(pamh, PAM_AUTHTOK, pi->first_factor); - } else { - ret = PAM_SYSTEM_ERR; - } - if (ret != PAM_SUCCESS) { - D(("Failed to set PAM_AUTHTOK [%s], " - "authtok may not be available for other modules", - pam_strerror(pamh,ret))); - } - } - } - - return PAM_SUCCESS; -} - -static int check_authtok_data(pam_handle_t *pamh, struct pam_items *pi) -{ - int pam_status; - int *authtok_type; - size_t *authtok_size; - char *authtok_data; - - pam_status = pam_get_data(pamh, PAM_SSS_AUTHOK_TYPE, - (const void **) &authtok_type); - if (pam_status != PAM_SUCCESS) { - D(("pam_get_data failed.")); - return EIO; - } - - pam_status = pam_get_data(pamh, PAM_SSS_AUTHOK_SIZE, - (const void **) &authtok_size); - if (pam_status != PAM_SUCCESS) { - D(("pam_get_data failed.")); - return EIO; - } - - pam_status = pam_get_data(pamh, PAM_SSS_AUTHOK_DATA, - (const void **) &authtok_data); - if (pam_status != PAM_SUCCESS) { - D(("pam_get_data failed.")); - return EIO; - } - - pi->pam_authtok = malloc(*authtok_size); - if (pi->pam_authtok == NULL) { - D(("malloc failed.")); - return ENOMEM; - } - memcpy(pi->pam_authtok, authtok_data, *authtok_size); - - pi->pam_authtok_type = *authtok_type; - pi->pam_authtok_size = *authtok_size; - - return 0; -} - -static int keep_authtok_data(pam_handle_t *pamh, struct pam_items *pi) -{ - int pam_status; - int *authtok_type; - size_t *authtok_size; - char *authtok_data; - - authtok_type = malloc(sizeof(int)); - if (authtok_type == NULL) { - D(("malloc failed.")); - return ENOMEM; - } - *authtok_type = pi->pam_authtok_type; - - pam_status = pam_set_data(pamh, PAM_SSS_AUTHOK_TYPE, authtok_type, - free_exp_data); - if (pam_status != PAM_SUCCESS) { - free(authtok_type); - D(("pam_set_data failed.")); - return EIO; - } - - authtok_size = malloc(sizeof(size_t)); - if (authtok_size == NULL) { - D(("malloc failed.")); - return ENOMEM; - } - *authtok_size = pi->pam_authtok_size; - - pam_status = pam_set_data(pamh, PAM_SSS_AUTHOK_SIZE, authtok_size, - free_exp_data); - if (pam_status != PAM_SUCCESS) { - free(authtok_size); - D(("pam_set_data failed.")); - return EIO; - } - - authtok_data = malloc(pi->pam_authtok_size); - if (authtok_data == NULL) { - D(("malloc failed.")); - return ENOMEM; - } - memcpy(authtok_data, pi->pam_authtok, pi->pam_authtok_size); - - pam_status = pam_set_data(pamh, PAM_SSS_AUTHOK_DATA, authtok_data, - free_exp_data); - if (pam_status != PAM_SUCCESS) { - free(authtok_data); - D(("pam_set_data failed.")); - return EIO; - } - - return 0; -} - -static int get_authtok_for_password_change(pam_handle_t *pamh, - struct pam_items *pi, - uint32_t flags, - int pam_flags) -{ - int ret; - const int *exp_data = NULL; - ret = pam_get_data(pamh, PWEXP_FLAG, (const void **) &exp_data); - if (ret != PAM_SUCCESS) { - exp_data = NULL; - } - - if (pam_flags & PAM_PRELIM_CHECK) { - if (!(flags & PAM_CLI_FLAGS_ALLOW_CHAUTHTOK_BY_ROOT) && getuid() == 0 && !exp_data ) - return PAM_SUCCESS; - - 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, true, - _("First Factor (Current Password): "), - _("Second Factor (optional): ")); - } else { - ret = prompt_2fa(pamh, pi, false, - _("First Factor (Current Password): "), - _("Second Factor: ")); - } - } else if ((flags & PAM_CLI_FLAGS_USE_FIRST_PASS) - && check_authtok_data(pamh, pi) != 0) { - if (pi->pamstack_oldauthtok == NULL) { - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; - pi->pam_authtok = NULL; - pi->pam_authtok_size = 0; - } else { - pi->pam_authtok = strdup(pi->pamstack_oldauthtok); - if (pi->pam_authtok == NULL) { - D(("strdup failed")); - return PAM_BUF_ERR; - } - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD; - pi->pam_authtok_size = strlen(pi->pam_authtok); - } - ret = PAM_SUCCESS; - } else { - ret = prompt_password(pamh, pi, _("Current Password: ")); - } - if (ret != PAM_SUCCESS) { - D(("failed to get credentials from user")); - return ret; - } - - ret = pam_set_item(pamh, PAM_OLDAUTHTOK, pi->pam_authtok); - if (ret != PAM_SUCCESS) { - D(("Failed to set PAM_OLDAUTHTOK [%s], " - "oldauthtok may not be available", - pam_strerror(pamh,ret))); - return ret; - } - - if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_2FA) { - ret = keep_authtok_data(pamh, pi); - if (ret != 0) { - D(("Failed to store authtok data to pam handle. Password " - "change might fail.")); - } - } - - return PAM_SUCCESS; - } - - if (check_authtok_data(pamh, pi) != 0) { - if (pi->pamstack_oldauthtok == NULL) { - if (getuid() != 0) { - D(("no password found for chauthtok")); - return PAM_BUF_ERR; - } else { - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; - pi->pam_authtok = NULL; - pi->pam_authtok_size = 0; - } - } else { - pi->pam_authtok = strdup(pi->pamstack_oldauthtok); - if (pi->pam_authtok == NULL) { - D(("strdup failed")); - return PAM_BUF_ERR; - } - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD; - pi->pam_authtok_size = strlen(pi->pam_authtok); - } - } - - if (flags & PAM_CLI_FLAGS_USE_AUTHTOK) { - pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_PASSWORD; - pi->pam_newauthtok = strdup(pi->pamstack_authtok); - if (pi->pam_newauthtok == NULL) { - D(("option use_authtok set, but no new password found")); - return PAM_BUF_ERR; - } - pi->pam_newauthtok_size = strlen(pi->pam_newauthtok); - } else { - ret = prompt_new_password(pamh, pi); - if (ret != PAM_SUCCESS) { - D(("failed to get new password from user")); - return ret; - } - - if (flags & PAM_CLI_FLAGS_FORWARD_PASS) { - ret = pam_set_item(pamh, PAM_AUTHTOK, pi->pam_newauthtok); - if (ret != PAM_SUCCESS) { - D(("Failed to set PAM_AUTHTOK [%s], " - "oldauthtok may not be available", - pam_strerror(pamh,ret))); - } - } - } - - return PAM_SUCCESS; -} - -#define SC_ENTER_LABEL_FMT "Please insert smart card labeled\n %s" -#define SC_ENTER_FMT "Please insert smart card" - -static int check_login_token_name(pam_handle_t *pamh, struct pam_items *pi, - int retries, bool quiet_mode) -{ - int ret; - int pam_status; - char *login_token_name; - char *prompt = NULL; - uint32_t orig_flags = pi->flags; - - login_token_name = getenv("PKCS11_LOGIN_TOKEN_NAME"); - if (login_token_name == NULL - && !(pi->flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH)) { - return PAM_SUCCESS; - } - - if (login_token_name == NULL) { - ret = asprintf(&prompt, SC_ENTER_FMT); - } else { - ret = asprintf(&prompt, SC_ENTER_LABEL_FMT, login_token_name); - } - if (ret == -1) { - return ENOMEM; - } - - pi->flags |= PAM_CLI_FLAGS_REQUIRE_CERT_AUTH; - - /* TODO: check multiple cert case */ - while (pi->cert_list == NULL || pi->cert_list->token_name == NULL - || (login_token_name != NULL - && strcmp(login_token_name, - pi->cert_list->token_name) != 0)) { - - free_cert_list(pi->cert_list); - pi->cert_list = NULL; - if (retries < 0) { - ret = PAM_AUTHINFO_UNAVAIL; - goto done; - } - retries--; - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, prompt, NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - goto done; - } - - pam_status = send_and_receive(pamh, pi, SSS_PAM_PREAUTH, quiet_mode); - if (pam_status != PAM_SUCCESS) { - D(("send_and_receive returned [%d] during pre-auth", pam_status)); - /* - * Since we are waiting for the right Smartcard to be inserted errors - * can be ignored here. - */ - } - } - - ret = PAM_SUCCESS; - -done: - - pi->flags = orig_flags; - free(prompt); - - return ret; -} - -static int pam_sss(enum sss_cli_command task, pam_handle_t *pamh, - int pam_flags, int argc, const char **argv) -{ - int ret; - int pam_status; - struct pam_items pi = { 0 }; - uint32_t flags = 0; - const int *exp_data; - int *pw_exp_data; - bool retry = false; - bool quiet_mode = false; - int retries = 0; - const char *domains = NULL; - - bindtextdomain(PACKAGE, LOCALEDIR); - - D(("Hello pam_sssd: %#x", task)); - - eval_argv(pamh, argc, argv, &flags, &retries, &quiet_mode, &domains); - - /* Fail all authentication on misconfigured domains= parameter. The admin - * probably wanted to restrict authentication, so it's safer to fail */ - if (domains && strcmp(domains, "") == 0) { - return PAM_SYSTEM_ERR; - } - - pi.requested_domains = domains; - - ret = get_pam_items(pamh, flags, &pi); - if (ret != PAM_SUCCESS) { - D(("get items returned error: %s", pam_strerror(pamh,ret))); - if ((flags & PAM_CLI_FLAGS_TRY_CERT_AUTH) - || (flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) ) { - return PAM_AUTHINFO_UNAVAIL; - } - if (flags & PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER && ret == PAM_USER_UNKNOWN) { - ret = PAM_IGNORE; - } - if (flags & PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL - && ret == PAM_AUTHINFO_UNAVAIL) { - ret = PAM_IGNORE; - } - return ret; - } - - do { - retry = false; - - switch(task) { - case SSS_PAM_AUTHENTICATE: - /* - * Only do preauth if - * - PAM_CLI_FLAGS_USE_FIRST_PASS is not set - * - no password is on the stack or PAM_CLI_FLAGS_PROMPT_ALWAYS is set - * - preauth indicator file exists. - */ - if ( !(flags & PAM_CLI_FLAGS_USE_FIRST_PASS) - && (pi.pam_authtok == NULL - || (flags & PAM_CLI_FLAGS_PROMPT_ALWAYS)) - && access(PAM_PREAUTH_INDICATOR, F_OK) == 0) { - - if (flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) { - /* Do not use PAM_CLI_FLAGS_REQUIRE_CERT_AUTH in the first - * SSS_PAM_PREAUTH run. In case a card is already inserted - * we do not have to prompt to insert a card. */ - pi.flags &= ~PAM_CLI_FLAGS_REQUIRE_CERT_AUTH; - pi.flags |= PAM_CLI_FLAGS_TRY_CERT_AUTH; - } - - pam_status = send_and_receive(pamh, &pi, SSS_PAM_PREAUTH, - quiet_mode); - - pi.flags = flags; - if (pam_status != PAM_SUCCESS) { - D(("send_and_receive returned [%d] during pre-auth", - pam_status)); - /* - * Since we are only interested in the result message - * and will always use password authentication - * as a fallback (except for gdm-smartcard), - * errors can be ignored here. - */ - } - } - - if (flags & PAM_CLI_FLAGS_TRY_CERT_AUTH - && pi.cert_list == NULL) { - D(("No certificates for authentication available.")); - overwrite_and_free_pam_items(&pi); - return PAM_AUTHINFO_UNAVAIL; - } - - if (SERVICE_IS_GDM_SMARTCARD(&pi) - || (flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH)) { - ret = check_login_token_name(pamh, &pi, retries, - quiet_mode); - if (ret != PAM_SUCCESS) { - D(("check_login_token_name failed.\n")); - } - } - - ret = get_authtok_for_authentication(pamh, &pi, flags); - if (ret != PAM_SUCCESS) { - D(("failed to get authentication token: %s", - pam_strerror(pamh, ret))); - overwrite_and_free_pam_items(&pi); - return ret; - } - break; - case SSS_PAM_CHAUTHTOK: - /* - * Even if we only want to change the (long term) password - * there are cases where more than the password is needed to - * get the needed privileges in a backend to change the - * password. - * - * E.g. with mandatory 2-factor authentication we have to ask - * not only for the current password but for the second - * factor, e.g. the one-time token value, as well. - * - * The means the preauth step has to be done here as well but - * only if - * - PAM_PRELIM_CHECK is set - * - PAM_CLI_FLAGS_USE_FIRST_PASS is not set - * - no password is on the stack or PAM_CLI_FLAGS_PROMPT_ALWAYS is set - * - preauth indicator file exists. - */ - if ( (pam_flags & PAM_PRELIM_CHECK) - && !(flags & PAM_CLI_FLAGS_USE_FIRST_PASS) - && (pi.pam_authtok == NULL - || (flags & PAM_CLI_FLAGS_PROMPT_ALWAYS)) - && access(PAM_PREAUTH_INDICATOR, F_OK) == 0) { - pam_status = send_and_receive(pamh, &pi, SSS_PAM_PREAUTH, - quiet_mode); - if (pam_status != PAM_SUCCESS) { - D(("send_and_receive returned [%d] during pre-auth", - pam_status)); - /* - * Since we are only interested in the result message - * and will always use password authentication - * as a fallback, errors can be ignored here. - */ - } - } - - ret = get_authtok_for_password_change(pamh, &pi, flags, pam_flags); - if (ret != PAM_SUCCESS) { - D(("failed to get tokens for password change: %s", - pam_strerror(pamh, ret))); - overwrite_and_free_pam_items(&pi); - return ret; - } - - if (pam_flags & PAM_PRELIM_CHECK) { - if (pi.pam_authtok_type == SSS_AUTHTOK_TYPE_2FA) { - /* We cannot validate the credentials with an OTP - * token value during PAM_PRELIM_CHECK because it - * would be invalid for the actual password change. So - * we are done. */ - - overwrite_and_free_pam_items(&pi); - return PAM_SUCCESS; - } - task = SSS_PAM_CHAUTHTOK_PRELIM; - } - break; - case SSS_PAM_ACCT_MGMT: - case SSS_PAM_SETCRED: - case SSS_PAM_OPEN_SESSION: - case SSS_PAM_CLOSE_SESSION: - break; - default: - D(("Illegal task [%#x]", task)); - overwrite_and_free_pam_items(&pi); - return PAM_SYSTEM_ERR; - } - - pam_status = send_and_receive(pamh, &pi, task, quiet_mode); - - if (flags & PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER - && pam_status == PAM_USER_UNKNOWN) { - pam_status = PAM_IGNORE; - } - if (flags & PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL - && pam_status == PAM_AUTHINFO_UNAVAIL) { - pam_status = PAM_IGNORE; - } - - switch (task) { - case SSS_PAM_AUTHENTICATE: - /* We allow sssd to send the return code PAM_NEW_AUTHTOK_REQD during - * authentication, see sss_cli.h for details */ - if (pam_status == PAM_NEW_AUTHTOK_REQD) { - D(("Authtoken expired, trying to change it")); - - pw_exp_data = malloc(sizeof(int)); - if (pw_exp_data == NULL) { - D(("malloc failed.")); - pam_status = PAM_BUF_ERR; - break; - } - *pw_exp_data = 1; - - pam_status = pam_set_data(pamh, PWEXP_FLAG, pw_exp_data, - free_exp_data); - if (pam_status != PAM_SUCCESS) { - D(("pam_set_data failed.")); - } - } - break; - case SSS_PAM_ACCT_MGMT: - if (pam_status == PAM_SUCCESS && - pam_get_data(pamh, PWEXP_FLAG, (const void **) &exp_data) == - PAM_SUCCESS) { - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, - _("Password expired. Change your password now."), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - } - pam_status = PAM_NEW_AUTHTOK_REQD; - } - break; - case SSS_PAM_CHAUTHTOK: - if (pam_status != PAM_SUCCESS && pam_status != PAM_USER_UNKNOWN) { - ret = pam_set_item(pamh, PAM_AUTHTOK, NULL); - if (ret != PAM_SUCCESS) { - D(("Failed to unset PAM_AUTHTOK [%s]", - pam_strerror(pamh,ret))); - } - ret = pam_set_item(pamh, PAM_OLDAUTHTOK, NULL); - if (ret != PAM_SUCCESS) { - D(("Failed to unset PAM_OLDAUTHTOK [%s]", - pam_strerror(pamh,ret))); - } - } - break; - case SSS_PAM_CHAUTHTOK_PRELIM: - if (pam_status == PAM_PERM_DENIED && pi.pam_authtok_size == 0 && - getuid() == 0 && - pam_get_data(pamh, PWEXP_FLAG, (const void **) &exp_data) != - PAM_SUCCESS) { - - ret = select_pw_reset_message(pamh, &pi); - if (ret != 0) { - D(("select_pw_reset_message failed.\n")); - } - } - default: - /* nothing to do */ - break; - } - - overwrite_and_free_pam_items(&pi); - - D(("retries [%d].", retries)); - - if (pam_status != PAM_SUCCESS && - (task == SSS_PAM_AUTHENTICATE || task == SSS_PAM_CHAUTHTOK_PRELIM) && - retries > 0) { - retry = true; - retries--; - - flags &= ~PAM_CLI_FLAGS_USE_FIRST_PASS; - ret = pam_set_item(pamh, PAM_AUTHTOK, NULL); - if (ret != PAM_SUCCESS) { - D(("Failed to unset PAM_AUTHTOK [%s]", - pam_strerror(pamh,ret))); - } - ret = pam_set_item(pamh, PAM_OLDAUTHTOK, NULL); - if (ret != PAM_SUCCESS) { - D(("Failed to unset PAM_OLDAUTHTOK [%s]", - pam_strerror(pamh,ret))); - } - } - } while(retry); - - return pam_status; -} - -PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, - const char **argv ) -{ - return pam_sss(SSS_PAM_AUTHENTICATE, pamh, flags, argc, argv); -} - - -PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, - const char **argv ) -{ - return pam_sss(SSS_PAM_SETCRED, pamh, flags, argc, argv); -} - -PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, - const char **argv ) -{ - return pam_sss(SSS_PAM_ACCT_MGMT, pamh, flags, argc, argv); -} - -PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, - const char **argv ) -{ - return pam_sss(SSS_PAM_CHAUTHTOK, pamh, flags, argc, argv); -} - -PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, - const char **argv ) -{ - return pam_sss(SSS_PAM_OPEN_SESSION, pamh, flags, argc, argv); -} - -PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, - const char **argv ) -{ - return pam_sss(SSS_PAM_CLOSE_SESSION, pamh, flags, argc, argv); -} - - -#ifdef PAM_STATIC - -/* static module data */ - -struct pam_module _pam_sssd_modstruct ={ - "pam_sssd", - pam_sm_authenticate, - pam_sm_setcred, - pam_sm_acct_mgmt, - pam_sm_open_session, - pam_sm_close_session, - pam_sm_chauthtok -}; - -#endif +/* + Authors: + Sumit Bose + + Copyright (C) 2009 Red Hat + Copyright (C) 2010, rhafer@suse.de, Novell Inc. + + 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 . +*/ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef HAVE_GDM_PAM_EXTENSIONS +#include +#endif + +#include "sss_pam_compat.h" +#include "sss_pam_macros.h" + +#include "sss_cli.h" +#include "pam_message.h" +#include "util/atomic_io.h" +#include "util/authtok-utils.h" +#include "util/dlinklist.h" +#include "util/memory_erase.h" + +#include +#define _(STRING) dgettext (PACKAGE, STRING) +#define _n(SINGULAR, PLURAL, VALUE) dngettext(PACKAGE, SINGULAR, PLURAL, VALUE) + +#define PWEXP_FLAG "pam_sss:password_expired_flag" +#define FD_DESTRUCTOR "pam_sss:fd_destructor" +#define PAM_SSS_AUTHOK_TYPE "pam_sss:authtok_type" +#define PAM_SSS_AUTHOK_SIZE "pam_sss:authtok_size" +#define PAM_SSS_AUTHOK_DATA "pam_sss:authtok_data" + +#define PW_RESET_MSG_FILENAME_TEMPLATE SSSD_CONF_DIR"/customize/%s/pam_sss_pw_reset_message.%s" +#define PW_RESET_MSG_MAX_SIZE 4096 + +#define OPT_RETRY_KEY "retry=" +#define OPT_DOMAINS_KEY "domains=" + +#define EXP_ACC_MSG _("Permission denied. ") +#define SRV_MSG _("Server message: ") +#define PASSKEY_LOCAL_AUTH_MSG _("Kerberos TGT will not be granted upon login, user experience will be affected.") +#define PASSKEY_DEFAULT_PIN_MSG _("Enter PIN:") + +#define DEBUG_MGS_LEN 1024 +#define MAX_AUTHTOK_SIZE (1024*1024) +#define CHECK_AND_RETURN_PI_STRING(s) ((s != NULL && *s != '\0')? s : "(not available)") +#define SERVICE_IS_GDM_SMARTCARD(pitem) (strcmp((pitem)->pam_service, \ + "gdm-smartcard") == 0) + +static void logger(pam_handle_t *pamh, int level, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + +#ifdef DEBUG + va_list apd; + char debug_msg[DEBUG_MGS_LEN]; + int ret; + va_copy(apd, ap); + + ret = vsnprintf(debug_msg, DEBUG_MGS_LEN, fmt, apd); + if (ret >= DEBUG_MGS_LEN) { + D(("the following message is truncated: %s", debug_msg)); + } else if (ret < 0) { + D(("vsnprintf failed to format debug message!")); + } else { + D((debug_msg)); + } + + va_end(apd); +#endif + + pam_vsyslog(pamh, LOG_AUTHPRIV|level, fmt, ap); + + va_end(ap); +} + +static void free_exp_data(pam_handle_t *pamh, void *ptr, int err) +{ + free(ptr); +} + +static void close_fd(pam_handle_t *pamh, void *ptr, int err) +{ +#ifdef PAM_DATA_REPLACE + if (err & PAM_DATA_REPLACE) { + /* Nothing to do */ + return; + } +#endif /* PAM_DATA_REPLACE */ + + D(("Closing the fd")); + + sss_pam_lock(); + sss_cli_close_socket(); + sss_pam_unlock(); +} + +struct cert_auth_info { + char *cert_user; + char *cert; + char *token_name; + char *module_name; + char *key_id; + char *label; + char *prompt_str; + char *pam_cert_user; + char *choice_list_id; + struct cert_auth_info *prev; + struct cert_auth_info *next; +}; + +static void free_cai(struct cert_auth_info *cai) +{ + if (cai != NULL) { + free(cai->cert_user); + free(cai->cert); + free(cai->token_name); + free(cai->module_name); + free(cai->key_id); + free(cai->label); + free(cai->prompt_str); + free(cai->choice_list_id); + free(cai); + } +} + +static void free_cert_list(struct cert_auth_info *list) +{ + struct cert_auth_info *cai; + struct cert_auth_info *cai_next; + + if (list != NULL) { + DLIST_FOR_EACH_SAFE(cai, cai_next, list) { + DLIST_REMOVE(list, cai); + free_cai(cai); + } + } +} + +static void overwrite_and_free_authtoks(struct pam_items *pi) +{ + if (pi->pam_authtok != NULL) { + sss_erase_mem_securely((void *)pi->pam_authtok, pi->pam_authtok_size); + free((void *)pi->pam_authtok); + pi->pam_authtok = NULL; + } + + if (pi->pam_newauthtok != NULL) { + sss_erase_mem_securely((void *)pi->pam_newauthtok, pi->pam_newauthtok_size); + free((void *)pi->pam_newauthtok); + pi->pam_newauthtok = NULL; + } + + if (pi->first_factor != NULL) { + sss_erase_mem_securely((void *)pi->first_factor, strlen(pi->first_factor)); + free((void *)pi->first_factor); + pi->first_factor = NULL; + } + + pi->pamstack_authtok = NULL; + pi->pamstack_oldauthtok = NULL; +} + +static void overwrite_and_free_pam_items(struct pam_items *pi) +{ + overwrite_and_free_authtoks(pi); + + free(pi->domain_name); + pi->domain_name = NULL; + + free(pi->otp_vendor); + pi->otp_vendor = NULL; + + free(pi->otp_token_id); + pi->otp_token_id = NULL; + + free(pi->otp_challenge); + pi->otp_challenge = NULL; + + free(pi->passkey_key); + pi->passkey_key = NULL; + + free(pi->passkey_prompt_pin); + pi->passkey_prompt_pin = NULL; + + free(pi->passkey_devinfo); + pi->passkey_devinfo = NULL; + + + 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) { + if (s1 == NULL && s2 == NULL) return 0; + if (s1 == NULL && s2 != NULL) return -1; + if (s1 != NULL && s2 == NULL) return 1; + return strcmp(s1, s2); +} + +enum { + SSS_PAM_CONV_DONE = 0, + SSS_PAM_CONV_STD, + SSS_PAM_CONV_REENTER, +}; + +static int do_pam_conversation(pam_handle_t *pamh, const int msg_style, + const char *msg, + const char *reenter_msg, + char **_answer) +{ + int ret; + int state = SSS_PAM_CONV_STD; + const struct pam_conv *conv; + const struct pam_message *mesg[1]; + struct pam_message *pam_msg; + struct pam_response *resp=NULL; + char *answer = NULL; + + if ((msg_style == PAM_TEXT_INFO || msg_style == PAM_ERROR_MSG) && + msg == NULL) return PAM_SYSTEM_ERR; + + if ((msg_style == PAM_PROMPT_ECHO_OFF || + msg_style == PAM_PROMPT_ECHO_ON) && + (msg == NULL || _answer == NULL)) return PAM_SYSTEM_ERR; + + if (msg_style == PAM_TEXT_INFO || msg_style == PAM_ERROR_MSG) { + logger(pamh, LOG_INFO, "User %s message: %s", + msg_style == PAM_TEXT_INFO ? "info" : "error", + msg); + } + + ret=pam_get_item(pamh, PAM_CONV, (const void **) &conv); + if (ret != PAM_SUCCESS) return ret; + if (conv == NULL || conv->conv == NULL) { + logger(pamh, LOG_ERR, "No conversation function"); + return PAM_SYSTEM_ERR; + } + + do { + pam_msg = malloc(sizeof(struct pam_message)); + if (pam_msg == NULL) { + D(("Malloc failed.")); + ret = PAM_SYSTEM_ERR; + goto failed; + } + + pam_msg->msg_style = msg_style; + if (state == SSS_PAM_CONV_REENTER) { + pam_msg->msg = reenter_msg; + } else { + pam_msg->msg = msg; + } + + mesg[0] = (const struct pam_message *) pam_msg; + + ret=conv->conv(1, mesg, &resp, + conv->appdata_ptr); + free(pam_msg); + if (ret != PAM_SUCCESS) { + D(("Conversation failure: %s.", pam_strerror(pamh,ret))); + goto failed; + } + + if (msg_style == PAM_PROMPT_ECHO_OFF || + msg_style == PAM_PROMPT_ECHO_ON) { + if (resp == NULL) { + D(("response expected, but resp==NULL")); + ret = PAM_SYSTEM_ERR; + goto failed; + } + + if (state == SSS_PAM_CONV_REENTER) { + if (null_strcmp(answer, resp[0].resp) != 0) { + logger(pamh, LOG_NOTICE, "Passwords do not match."); + sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); + free(resp[0].resp); + if (answer != NULL) { + sss_erase_mem_securely((void *) answer, strlen(answer)); + free(answer); + answer = NULL; + } + ret = do_pam_conversation(pamh, PAM_ERROR_MSG, + _("Passwords do not match"), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + ret = PAM_SYSTEM_ERR; + goto failed; + } + ret = PAM_CRED_ERR; + goto failed; + } + sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); + free(resp[0].resp); + } else { + if (resp[0].resp == NULL) { + D(("Empty password")); + answer = NULL; + } else { + answer = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); + sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); + free(resp[0].resp); + if(answer == NULL) { + D(("strndup failed")); + ret = PAM_BUF_ERR; + goto failed; + } + } + } + free(resp); + resp = NULL; + } + + if (reenter_msg != NULL && state == SSS_PAM_CONV_STD) { + state = SSS_PAM_CONV_REENTER; + } else { + state = SSS_PAM_CONV_DONE; + } + } while (state != SSS_PAM_CONV_DONE); + + if (_answer) *_answer = answer; + return PAM_SUCCESS; + +failed: + free(answer); + return ret; + +} + +static errno_t display_pw_reset_message(pam_handle_t *pamh, + const char *domain_name, + const char *suffix) +{ + int ret; + struct stat stat_buf; + char *msg_buf = NULL; + int fd = -1; + size_t size; + size_t total_len; + char *filename = NULL; + + if (strchr(suffix, '/') != NULL || strchr(domain_name, '/') != NULL) { + D(("Suffix [%s] or domain name [%s] contain illegal character.", suffix, + domain_name)); + return EINVAL; + } + + size = sizeof(PW_RESET_MSG_FILENAME_TEMPLATE) + strlen(domain_name) + + strlen(suffix); + filename = malloc(size); + if (filename == NULL) { + D(("malloc failed.")); + ret = ENOMEM; + goto done; + } + ret = snprintf(filename, size, PW_RESET_MSG_FILENAME_TEMPLATE, domain_name, + suffix); + if (ret < 0 || ret >= size) { + D(("snprintf failed.")); + ret = EFAULT; + goto done; + } + + fd = open(filename, O_RDONLY); + if (fd == -1) { + ret = errno; + D(("open failed [%d][%s].\n", ret, strerror(ret))); + goto done; + } + + ret = fstat(fd, &stat_buf); + if (ret == -1) { + ret = errno; + D(("fstat failed [%d][%s].", ret, strerror(ret))); + goto done; + } + + if (!S_ISREG(stat_buf.st_mode)) { + logger(pamh, LOG_ERR, + "Password reset message file is not a regular file."); + ret = EINVAL; + goto done; + } + + if (stat_buf.st_uid != 0 || stat_buf.st_gid != 0 || + (stat_buf.st_mode & ~S_IFMT) != 0644) { + logger(pamh, LOG_ERR,"Permission error, " + "file [%s] must be owned by root with permissions 0644.", + filename); + ret = EPERM; + goto done; + } + + if (stat_buf.st_size > PW_RESET_MSG_MAX_SIZE) { + logger(pamh, LOG_ERR, "Password reset message file is too large."); + ret = EFBIG; + goto done; + } + + msg_buf = malloc(stat_buf.st_size + 1); + if (msg_buf == NULL) { + D(("malloc failed.")); + ret = ENOMEM; + goto done; + } + + errno = 0; + total_len = sss_atomic_read_s(fd, msg_buf, stat_buf.st_size); + if (total_len == -1) { + ret = errno; + D(("read failed [%d][%s].", ret, strerror(ret))); + goto done; + } + + ret = close(fd); + fd = -1; + if (ret == -1) { + ret = errno; + D(("close failed [%d][%s].", ret, strerror(ret))); + } + + if (total_len != stat_buf.st_size) { + D(("read fewer bytes [%d] than expected [%d].", total_len, + stat_buf.st_size)); + ret = EIO; + goto done; + } + + msg_buf[stat_buf.st_size] = '\0'; + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, msg_buf, NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + } + +done: + if (fd != -1) { + close(fd); + } + free(msg_buf); + free(filename); + + return ret; +} + +static errno_t select_pw_reset_message(pam_handle_t *pamh, struct pam_items *pi) +{ + int ret; + char *locale; + const char *domain_name; + + domain_name = pi->domain_name; + if (domain_name == NULL || *domain_name == '\0') { + D(("Domain name is unknown.")); + return EINVAL; + } + + locale = setlocale(LC_MESSAGES, NULL); + + ret = -1; + if (locale != NULL) { + ret = display_pw_reset_message(pamh, domain_name, locale); + } + + if (ret != 0) { + ret = display_pw_reset_message(pamh, domain_name, "txt"); + } + + if (ret != 0) { + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, + _("Password reset by root is not supported."), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + } + } + + return ret; +} + +static int user_info_offline_auth(pam_handle_t *pamh, size_t buflen, + uint8_t *buf) +{ + int ret; + int64_t expire_date; + struct tm tm; + char expire_str[128]; + char user_msg[256]; + + expire_str[0] = '\0'; + + if (buflen != sizeof(uint32_t) + sizeof(int64_t)) { + D(("User info response data has the wrong size")); + return PAM_BUF_ERR; + } + + memcpy(&expire_date, buf + sizeof(uint32_t), sizeof(int64_t)); + + if (expire_date > 0) { + if (localtime_r((time_t *) &expire_date, &tm) != NULL) { + ret = strftime(expire_str, sizeof(expire_str), "%c", &tm); + if (ret == 0) { + D(("strftime failed.")); + expire_str[0] = '\0'; + } + } else { + D(("localtime_r failed")); + } + } + + ret = snprintf(user_msg, sizeof(user_msg), "%s%s%s.", + _("Authenticated with cached credentials"), + expire_str[0] ? _(", your cached password will expire at: ") : "", + expire_str[0] ? expire_str : ""); + if (ret < 0 || ret >= sizeof(user_msg)) { + D(("snprintf failed.")); + return PAM_SYSTEM_ERR; + } + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_grace_login(pam_handle_t *pamh, + size_t buflen, + uint8_t *buf) +{ + int ret; + uint32_t grace; + char user_msg[256]; + + if (buflen != 2* sizeof(uint32_t)) { + D(("User info response data has the wrong size")); + return PAM_BUF_ERR; + } + memcpy(&grace, buf + sizeof(uint32_t), sizeof(uint32_t)); + ret = snprintf(user_msg, sizeof(user_msg), + _("Your password has expired. " + "You have %1$d grace login(s) remaining."), + grace); + if (ret < 0 || ret >= sizeof(user_msg)) { + D(("snprintf failed.")); + return PAM_SYSTEM_ERR; + } + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); + + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +#define MINSEC 60 +#define HOURSEC (60*MINSEC) +#define DAYSEC (24*HOURSEC) +static int user_info_expire_warn(pam_handle_t *pamh, + size_t buflen, + uint8_t *buf) +{ + int ret; + uint32_t expire; + char user_msg[256]; + const char* unit; + + if (buflen != 2* sizeof(uint32_t)) { + D(("User info response data has the wrong size")); + return PAM_BUF_ERR; + } + memcpy(&expire, buf + sizeof(uint32_t), sizeof(uint32_t)); + /* expire == 0 indicates the password expired */ + if (expire != 0) { + if (expire >= DAYSEC) { + expire /= DAYSEC; + unit = _n("day", "days", expire); + } else if (expire >= HOURSEC) { + expire /= HOURSEC; + unit = _n("hour", "hours", expire); + } else if (expire >= MINSEC) { + expire /= MINSEC; + unit = _n("minute", "minutes", expire); + } else { + unit = _n("second", "seconds", expire); + } + + ret = snprintf(user_msg, sizeof(user_msg), + _("Your password will expire in %1$d %2$s."), expire, unit); + } else { + ret = snprintf(user_msg, sizeof(user_msg), + _("Your password has expired.")); + } + + if (ret < 0 || ret >= sizeof(user_msg)) { + D(("snprintf failed.")); + return PAM_SYSTEM_ERR; + } + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); + + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_offline_auth_delayed(pam_handle_t *pamh, size_t buflen, + uint8_t *buf) +{ + int ret; + int64_t delayed_until; + struct tm tm; + char delay_str[128]; + char user_msg[256]; + + delay_str[0] = '\0'; + + if (buflen != sizeof(uint32_t) + sizeof(int64_t)) { + D(("User info response data has the wrong size")); + return PAM_BUF_ERR; + } + + memcpy(&delayed_until, buf + sizeof(uint32_t), sizeof(int64_t)); + + if (delayed_until <= 0) { + D(("User info response data has an invalid value")); + return PAM_BUF_ERR; + } + + if (localtime_r((time_t *) &delayed_until, &tm) != NULL) { + ret = strftime(delay_str, sizeof(delay_str), "%c", &tm); + if (ret == 0) { + D(("strftime failed.")); + delay_str[0] = '\0'; + } + } else { + D(("localtime_r failed")); + } + + ret = snprintf(user_msg, sizeof(user_msg), "%s%s.", + _("Authentication is denied until: "), + delay_str); + if (ret < 0 || ret >= sizeof(user_msg)) { + D(("snprintf failed.")); + return PAM_SYSTEM_ERR; + } + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_offline_chpass(pam_handle_t *pamh) +{ + int ret; + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, + _("System is offline, password change not possible"), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_otp_chpass(pam_handle_t *pamh) +{ + int ret; + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, + _("After changing the OTP password, you need to " + "log out and back in order to acquire a ticket"), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_pin_locked(pam_handle_t *pamh) +{ + int ret; + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, _("PIN locked"), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_no_krb_tgt(pam_handle_t *pamh) +{ + int ret; + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, + _("No Kerberos TGT granted as " + "the server does not support this method. " + "Your single-sign on(SSO) experience will " + "be affected."), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_account_expired(pam_handle_t *pamh, size_t buflen, + uint8_t *buf) +{ + int ret; + uint32_t msg_len; + char *user_msg; + size_t bufsize = 0; + + /* resp_type and length of message are expected to be in buf */ + if (buflen < 2* sizeof(uint32_t)) { + D(("User info response data is too short")); + return PAM_BUF_ERR; + } + + /* msg_len = legth of message */ + memcpy(&msg_len, buf + sizeof(uint32_t), sizeof(uint32_t)); + + if (buflen != 2* sizeof(uint32_t) + msg_len) { + D(("User info response data has the wrong size")); + return PAM_BUF_ERR; + } + + bufsize = strlen(EXP_ACC_MSG) + 1; + + if (msg_len > 0) { + bufsize += strlen(SRV_MSG) + msg_len; + } + + user_msg = (char *)malloc(sizeof(char) * bufsize); + if (!user_msg) { + D(("Out of memory.")); + return PAM_SYSTEM_ERR; + } + + ret = snprintf(user_msg, bufsize, "%s%s%.*s", + EXP_ACC_MSG, + msg_len > 0 ? SRV_MSG : "", + (int)msg_len, + msg_len > 0 ? (char *)(buf + 2 * sizeof(uint32_t)) : "" ); + if (ret < 0 || ret > bufsize) { + D(("snprintf failed.")); + + free(user_msg); + return PAM_SYSTEM_ERR; + } + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); + free(user_msg); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_chpass_error(pam_handle_t *pamh, size_t buflen, + uint8_t *buf) +{ + int ret; + uint32_t msg_len; + char *user_msg; + size_t bufsize = 0; + + if (buflen < 2* sizeof(uint32_t)) { + D(("User info response data is too short")); + return PAM_BUF_ERR; + } + + memcpy(&msg_len, buf + sizeof(uint32_t), sizeof(uint32_t)); + + if (buflen != 2* sizeof(uint32_t) + msg_len) { + D(("User info response data has the wrong size")); + return PAM_BUF_ERR; + } + + bufsize = strlen(_("Password change failed. ")) + 1; + + if (msg_len > 0) { + bufsize += strlen(_("Server message: ")) + msg_len; + } + + user_msg = (char *)malloc(sizeof(char) * bufsize); + if (!user_msg) { + D(("Out of memory.")); + return PAM_SYSTEM_ERR; + } + + ret = snprintf(user_msg, bufsize, "%s%s%.*s", + _("Password change failed. "), + msg_len > 0 ? _("Server message: ") : "", + (int)msg_len, + msg_len > 0 ? (char *)(buf + 2 * sizeof(uint32_t)) : "" ); + if (ret < 0 || ret > bufsize) { + D(("snprintf failed.")); + + free(user_msg); + return PAM_SYSTEM_ERR; + } + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); + free(user_msg); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int eval_user_info_response(pam_handle_t *pamh, size_t buflen, + uint8_t *buf) +{ + int ret; + uint32_t type; + + if (buflen < sizeof(uint32_t)) { + D(("User info response data is too short")); + return PAM_BUF_ERR; + } + + memcpy(&type, buf, sizeof(uint32_t)); + + switch(type) { + case SSS_PAM_USER_INFO_OFFLINE_AUTH: + ret = user_info_offline_auth(pamh, buflen, buf); + break; + case SSS_PAM_USER_INFO_GRACE_LOGIN: + ret = user_info_grace_login(pamh, buflen, buf); + break; + case SSS_PAM_USER_INFO_EXPIRE_WARN: + ret = user_info_expire_warn(pamh, buflen, buf); + break; + case SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED: + ret = user_info_offline_auth_delayed(pamh, buflen, buf); + break; + case SSS_PAM_USER_INFO_OFFLINE_CHPASS: + ret = user_info_offline_chpass(pamh); + break; + case SSS_PAM_USER_INFO_OTP_CHPASS: + ret = user_info_otp_chpass(pamh); + break; + case SSS_PAM_USER_INFO_CHPASS_ERROR: + ret = user_info_chpass_error(pamh, buflen, buf); + break; + case SSS_PAM_USER_INFO_PIN_LOCKED: + ret = user_info_pin_locked(pamh); + break; + case SSS_PAM_USER_INFO_ACCOUNT_EXPIRED: + ret = user_info_account_expired(pamh, buflen, buf); + break; + case SSS_PAM_USER_INFO_NO_KRB_TGT: + ret = user_info_no_krb_tgt(pamh); + break; + default: + D(("Unknown user info type [%d]", type)); + ret = PAM_SYSTEM_ERR; + } + + return ret; +} + +static int parse_cert_info(struct pam_items *pi, uint8_t *buf, size_t len, + size_t *p, const char **cert_user, + const char **pam_cert_user) +{ + struct cert_auth_info *cai = NULL; + size_t offset; + int ret; + + if (buf[*p + (len - 1)] != '\0') { + D(("cert info does not end with \\0.")); + return EINVAL; + } + + cai = calloc(1, sizeof(struct cert_auth_info)); + if (cai == NULL) { + return ENOMEM; + } + + cai->cert_user = strdup((char *) &buf[*p]); + if (cai->cert_user == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + if (cert_user != NULL) { + *cert_user = cai->cert_user; + } + + offset = strlen(cai->cert_user) + 1; + if (offset >= len) { + D(("Cert message size mismatch")); + ret = EINVAL; + goto done; + } + + cai->token_name = strdup((char *) &buf[*p + offset]); + if (cai->token_name == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + + offset += strlen(cai->token_name) + 1; + if (offset >= len) { + D(("Cert message size mismatch")); + ret = EINVAL; + goto done; + } + + cai->module_name = strdup((char *) &buf[*p + offset]); + if (cai->module_name == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + + offset += strlen(cai->module_name) + 1; + if (offset >= len) { + D(("Cert message size mismatch")); + ret = EINVAL; + goto done; + } + + cai->key_id = strdup((char *) &buf[*p + offset]); + if (cai->key_id == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + + offset += strlen(cai->key_id) + 1; + if (offset >= len) { + D(("Cert message size mismatch")); + ret = EINVAL; + goto done; + } + + cai->label = strdup((char *) &buf[*p + offset]); + if (cai->label == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + + offset += strlen(cai->label) + 1; + if (offset >= len) { + D(("Cert message size mismatch")); + ret = EINVAL; + goto done; + } + + cai->prompt_str = strdup((char *) &buf[*p + offset]); + if (cai->prompt_str == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + + offset += strlen(cai->prompt_str) + 1; + if (offset >= len) { + D(("Cert message size mismatch")); + ret = EINVAL; + goto done; + } + + cai->pam_cert_user = strdup((char *) &buf[*p + offset]); + if (cai->pam_cert_user == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + if (pam_cert_user != NULL) { + *pam_cert_user = cai->pam_cert_user; + } + + D(("cert user: [%s] token name: [%s] module: [%s] key id: [%s] " + "prompt: [%s] pam cert user: [%s]", + cai->cert_user, cai->token_name, cai->module_name, + cai->key_id, cai->prompt_str, cai->pam_cert_user)); + + DLIST_ADD(pi->cert_list, cai); + ret = 0; + +done: + if (ret != 0) { + free_cai(cai); + } + + return ret; +} + +static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf, + struct pam_items *pi) +{ + int ret; + size_t p=0; + char *env_item; + int32_t c; + int32_t type; + int32_t len; + int32_t pam_status; + size_t offset; + const char *cert_user; + const char *pam_cert_user; + + if (buflen < (2*sizeof(int32_t))) { + D(("response buffer is too small")); + return PAM_BUF_ERR; + } + + memcpy(&pam_status, buf+p, sizeof(int32_t)); + p += sizeof(int32_t); + + + memcpy(&c, buf+p, sizeof(int32_t)); + p += sizeof(int32_t); + + while(c>0) { + if (buflen < (p+2*sizeof(int32_t))) { + D(("response buffer is too small")); + return PAM_BUF_ERR; + } + + memcpy(&type, buf+p, sizeof(int32_t)); + p += sizeof(int32_t); + + memcpy(&len, buf+p, sizeof(int32_t)); + p += sizeof(int32_t); + + if (buflen < (p + len)) { + D(("response buffer is too small")); + return PAM_BUF_ERR; + } + + switch(type) { + case SSS_PAM_SYSTEM_INFO: + if (buf[p + (len -1)] != '\0') { + D(("system info does not end with \\0.")); + break; + } + logger(pamh, LOG_INFO, "system info: [%s]", &buf[p]); + break; + case SSS_PAM_DOMAIN_NAME: + if (buf[p + (len -1)] != '\0') { + D(("domain name does not end with \\0.")); + break; + } + D(("domain name: [%s]", &buf[p])); + free(pi->domain_name); + pi->domain_name = strdup((char *) &buf[p]); + if (pi->domain_name == NULL) { + D(("strdup failed")); + } + break; + case SSS_ENV_ITEM: + case SSS_PAM_ENV_ITEM: + case SSS_ALL_ENV_ITEM: + if (buf[p + (len -1)] != '\0') { + D(("env item does not end with \\0.")); + break; + } + + D(("env item: [%s]", &buf[p])); + if (type == SSS_PAM_ENV_ITEM || type == SSS_ALL_ENV_ITEM) { + ret = pam_putenv(pamh, (char *)&buf[p]); + if (ret != PAM_SUCCESS) { + D(("pam_putenv failed.")); + break; + } + } + + if (type == SSS_ENV_ITEM || type == SSS_ALL_ENV_ITEM) { + env_item = strdup((char *)&buf[p]); + if (env_item == NULL) { + D(("strdup failed")); + break; + } + ret = putenv(env_item); + if (ret == -1) { + D(("putenv failed.")); + break; + } + } + break; + case SSS_PAM_USER_INFO: + ret = eval_user_info_response(pamh, len, &buf[p]); + if (ret != PAM_SUCCESS) { + D(("eval_user_info_response failed")); + } + break; + case SSS_PAM_TEXT_MSG: + if (buf[p + (len -1)] != '\0') { + D(("system info does not end with \\0.")); + break; + } + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, (char *) &buf[p], + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + } + break; + case SSS_OTP: + D(("OTP was used, removing authtokens.")); + overwrite_and_free_authtoks(pi); + ret = pam_set_item(pamh, PAM_AUTHTOK, NULL); + if (ret != PAM_SUCCESS) { + D(("Failed to remove PAM_AUTHTOK after using otp [%s]", + pam_strerror(pamh,ret))); + } + break; + case SSS_PAM_OTP_INFO: + if (buf[p + (len - 1)] != '\0') { + D(("otp info does not end with \\0.")); + break; + } + + free(pi->otp_vendor); + pi->otp_vendor = strdup((char *) &buf[p]); + if (pi->otp_vendor == NULL) { + D(("strdup failed")); + break; + } + + offset = strlen(pi->otp_vendor) + 1; + if (offset >= len) { + D(("OTP message size mismatch")); + free(pi->otp_vendor); + pi->otp_vendor = NULL; + break; + } + free(pi->otp_token_id); + pi->otp_token_id = strdup((char *) &buf[p + offset]); + if (pi->otp_token_id == NULL) { + D(("strdup failed")); + break; + } + + offset += strlen(pi->otp_token_id) + 1; + if (offset >= len) { + D(("OTP message size mismatch")); + free(pi->otp_token_id); + pi->otp_token_id = NULL; + break; + } + free(pi->otp_challenge); + pi->otp_challenge = strdup((char *) &buf[p + offset]); + if (pi->otp_challenge == NULL) { + D(("strdup failed")); + break; + } + + break; + case SSS_PAM_CERT_INFO: + case SSS_PAM_CERT_INFO_WITH_HINT: + if (buf[p + (len - 1)] != '\0') { + D(("cert info does not end with \\0.")); + break; + } + + if (type == SSS_PAM_CERT_INFO_WITH_HINT) { + pi->user_name_hint = true; + } else { + pi->user_name_hint = false; + } + + ret = parse_cert_info(pi, buf, len, &p, &cert_user, + &pam_cert_user); + if (ret != 0) { + D(("Failed to parse cert info")); + break; + } + + if ((pi->pam_user == NULL || *(pi->pam_user) == '\0') + && *cert_user != '\0' && *pam_cert_user != '\0') { + ret = pam_set_item(pamh, PAM_USER, pam_cert_user); + if (ret != PAM_SUCCESS) { + D(("Failed to set PAM_USER during " + "Smartcard authentication [%s]", + pam_strerror(pamh, ret))); + break; + } + + pi->pam_user = cert_user; + pi->pam_user_size = strlen(pi->pam_user) + 1; + } + break; + case SSS_PASSWORD_PROMPTING: + 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; + case SSS_CHILD_KEEP_ALIVE: + memcpy(&pi->child_pid, &buf[p], len); + break; + case SSS_PAM_OAUTH2_INFO: + if (buf[p + (len - 1)] != '\0') { + D(("oauth2 info does not end with \\0.")); + break; + } + + free(pi->oauth2_url); + pi->oauth2_url = strdup((char *) &buf[p]); + if (pi->oauth2_url == NULL) { + D(("strdup failed")); + break; + } + + offset = strlen(pi->oauth2_url) + 1; + if (offset >= len) { + D(("OAuth2 message size mismatch")); + free(pi->oauth2_url); + pi->oauth2_url = NULL; + break; + } + + free(pi->oauth2_url_complete); + pi->oauth2_url_complete = strdup((char *) &buf[p + offset]); + if (pi->oauth2_url_complete == NULL) { + D(("strdup failed")); + break; + } + + offset = offset + strlen(pi->oauth2_url_complete) + 1; + if (offset >= len) { + D(("OAuth2 message size mismatch")); + free(pi->oauth2_url_complete); + pi->oauth2_url_complete = NULL; + break; + } + + /* This field is optional. */ + if (pi->oauth2_url_complete[0] == '\0') { + free(pi->oauth2_url_complete); + pi->oauth2_url_complete = NULL; + } + + free(pi->oauth2_pin); + pi->oauth2_pin = strdup((char *) &buf[p + offset]); + if (pi->oauth2_pin == NULL) { + D(("strdup failed")); + break; + } + + break; + case SSS_PAM_PASSKEY_KRB_INFO: + free(pi->passkey_prompt_pin); + pi->passkey_prompt_pin = strdup((char *) &buf[p]); + if (pi->passkey_prompt_pin == NULL) { + D(("strdup failed")); + break; + } + + offset = strlen(pi->passkey_prompt_pin) + 1; + if (offset >= len) { + D(("Passkey message size mismatch")); + free(pi->passkey_prompt_pin); + pi->passkey_prompt_pin = NULL; + break; + } + + free(pi->passkey_key); + pi->passkey_key = strdup((char *) &buf[p + offset]); + if (pi->passkey_key == NULL) { + D(("strdup failed")); + break; + } + /* devinfo shall be retrieved according key handle(s) + */ + free (pi->passkey_devinfo); + pi->passkey_devinfo = NULL; + + break; + case SSS_PAM_PASSKEY_INFO: + if (buf[p + (len - 1)] != '\0') { + D(("passkey info does not end with \\0.")); + break; + } + + free(pi->passkey_prompt_pin); + pi->passkey_prompt_pin = strdup((char *) &buf[p]); + if (pi->passkey_prompt_pin == NULL) { + D(("strdup failed")); + break; + } + /* devinfo shall be retrieved according key handle(s) + */ + free (pi->passkey_devinfo); + pi->passkey_devinfo = NULL; + + break; + case SSS_PAM_PASSKEY_DEVINFO: + if (buf[p + (len - 1)] != '\0') { + D(("passkey devinfo does not end with \\0.")); + break; + } + + free(pi->passkey_devinfo); + pi->passkey_devinfo = strdup((char *) &buf[p]); + if (pi->passkey_devinfo == NULL) { + D(("strdup failed")); + break; + } + break; + default: + D(("Unknown response type [%d]", type)); + } + p += len; + + --c; + } + + return PAM_SUCCESS; +} + +bool is_string_empty_or_whitespace(const char *str) +{ + int i; + + if (str == NULL) { + return true; + } + + for (i = 0; str[i] != '\0'; i++) { + if (!isspace(str[i])) { + return false; + } + } + + return true; +} + +static int get_pam_items(pam_handle_t *pamh, uint32_t flags, + struct pam_items *pi) +{ + int ret; + + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; + pi->pam_authtok = NULL; + pi->pam_authtok_size = 0; + pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_EMPTY; + pi->pam_newauthtok = NULL; + pi->pam_newauthtok_size = 0; + pi->first_factor = NULL; + + ret = pam_get_item(pamh, PAM_SERVICE, (const void **) &(pi->pam_service)); + if (ret != PAM_SUCCESS) return ret; + if (pi->pam_service == NULL) pi->pam_service=""; + pi->pam_service_size=strlen(pi->pam_service)+1; + + ret = pam_get_item(pamh, PAM_USER, (const void **) &(pi->pam_user)); + if (ret == PAM_PERM_DENIED && (flags & PAM_CLI_FLAGS_ALLOW_MISSING_NAME)) { + pi->pam_user = ""; + ret = PAM_SUCCESS; + } + if (ret != PAM_SUCCESS) return ret; + if (flags & PAM_CLI_FLAGS_ALLOW_MISSING_NAME) { + if (is_string_empty_or_whitespace(pi->pam_user)) { + pi->pam_user = ""; + } + } + if (pi->pam_user == NULL) { + D(("No user found, aborting.")); + return PAM_BAD_ITEM; + } + if (strcmp(pi->pam_user, "root") == 0) { + D(("pam_sss will not handle root.")); + return PAM_USER_UNKNOWN; + } + pi->pam_user_size=strlen(pi->pam_user)+1; + + + ret = pam_get_item(pamh, PAM_TTY, (const void **) &(pi->pam_tty)); + if (ret != PAM_SUCCESS) return ret; + if (pi->pam_tty == NULL) pi->pam_tty=""; + pi->pam_tty_size=strlen(pi->pam_tty)+1; + + ret = pam_get_item(pamh, PAM_RUSER, (const void **) &(pi->pam_ruser)); + if (ret != PAM_SUCCESS) return ret; + if (pi->pam_ruser == NULL) pi->pam_ruser=""; + pi->pam_ruser_size=strlen(pi->pam_ruser)+1; + + ret = pam_get_item(pamh, PAM_RHOST, (const void **) &(pi->pam_rhost)); + if (ret != PAM_SUCCESS) return ret; + if (pi->pam_rhost == NULL) pi->pam_rhost=""; + pi->pam_rhost_size=strlen(pi->pam_rhost)+1; + + ret = pam_get_item(pamh, PAM_AUTHTOK, + (const void **) &(pi->pamstack_authtok)); + if (ret != PAM_SUCCESS) return ret; + if (pi->pamstack_authtok == NULL) pi->pamstack_authtok=""; + + ret = pam_get_item(pamh, PAM_OLDAUTHTOK, + (const void **) &(pi->pamstack_oldauthtok)); + if (ret != PAM_SUCCESS) return ret; + if (pi->pamstack_oldauthtok == NULL) pi->pamstack_oldauthtok=""; + + pi->cli_pid = getpid(); + + pi->login_name = pam_modutil_getlogin(pamh); + if (pi->login_name == NULL) pi->login_name=""; + + pi->domain_name = NULL; + + if (pi->requested_domains == NULL) pi->requested_domains = ""; + pi->requested_domains_size = strlen(pi->requested_domains) + 1; + + pi->otp_vendor = NULL; + pi->otp_token_id = NULL; + pi->otp_challenge = NULL; + pi->password_prompting = false; + + pi->cert_list = NULL; + pi->selected_cert = NULL; + + pi->pc = NULL; + + pi->flags = flags; + + return PAM_SUCCESS; +} + +static void print_pam_items(struct pam_items *pi) +{ + if (pi == NULL) return; + + D(("Service: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_service))); + D(("User: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_user))); + D(("Tty: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_tty))); + D(("Ruser: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_ruser))); + D(("Rhost: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_rhost))); + D(("Pamstack_Authtok: %s", + CHECK_AND_RETURN_PI_STRING(pi->pamstack_authtok))); + D(("Pamstack_Oldauthtok: %s", + CHECK_AND_RETURN_PI_STRING(pi->pamstack_oldauthtok))); + D(("Authtok: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_authtok))); + D(("Newauthtok: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_newauthtok))); + D(("Cli_PID: %d", pi->cli_pid)); + D(("Child_PID: %d", pi->child_pid)); + D(("Requested domains: %s", pi->requested_domains)); + D(("Flags: %d", pi->flags)); +} + +static int send_and_receive(pam_handle_t *pamh, struct pam_items *pi, + enum sss_cli_command task, bool quiet_mode) +{ + int ret; + int sret; + int errnop; + struct sss_cli_req_data rd; + uint8_t *buf = NULL; + uint8_t *repbuf = NULL; + size_t replen; + int pam_status = PAM_SYSTEM_ERR; + + print_pam_items(pi); + + ret = pack_message_v3(pi, &rd.len, &buf); + if (ret != 0) { + D(("pack_message failed.")); + pam_status = PAM_SYSTEM_ERR; + goto done; + } + rd.data = buf; + + errnop = 0; + ret = sss_pam_make_request(task, &rd, &repbuf, &replen, &errnop); + + sret = pam_set_data(pamh, FD_DESTRUCTOR, NULL, close_fd); + if (sret != PAM_SUCCESS) { + D(("pam_set_data failed, client might leaks fds")); + } + + if (ret != PAM_SUCCESS) { + /* If there is no PAM responder socket during the access control step + * we assume this is on purpose, i.e. PAM responder is not configured. + * PAM_USER_UNKNOWN is returned to the PAM stack to avoid unexpected + * denials. */ + if (errnop == ESSS_NO_SOCKET && task == SSS_PAM_ACCT_MGMT) { + pam_status = PAM_USER_UNKNOWN; + } else { + if (errnop != 0 && errnop != ESSS_NO_SOCKET) { + logger(pamh, LOG_ERR, "Request to sssd failed. %s", + ssscli_err2string(errnop)); + } + + pam_status = PAM_AUTHINFO_UNAVAIL; + } + goto done; + } + +/* FIXME: add an end signature */ + if (replen < (2*sizeof(int32_t))) { + D(("response not in expected format.")); + pam_status = PAM_SYSTEM_ERR; + goto done; + } + + SAFEALIGN_COPY_UINT32(&pam_status, repbuf, NULL); + ret = eval_response(pamh, replen, repbuf, pi); + if (ret != PAM_SUCCESS) { + D(("eval_response failed.")); + pam_status = ret; + goto done; + } + + switch (task) { + case SSS_PAM_AUTHENTICATE: + logger(pamh, (pam_status == PAM_SUCCESS ? LOG_INFO : LOG_NOTICE), + "authentication %s; logname=%s uid=%lu euid=%d tty=%s " + "ruser=%s rhost=%s user=%s", + pam_status == PAM_SUCCESS ? "success" : "failure", + pi->login_name, getuid(), (unsigned long) geteuid(), + pi->pam_tty, pi->pam_ruser, pi->pam_rhost, pi->pam_user); + if (pam_status != PAM_SUCCESS) { + /* don't log if quiet_mode is on and pam_status is + * User not known to the underlying authentication module + */ + if (!quiet_mode || pam_status != 10) { + logger(pamh, LOG_NOTICE, "received for user %s: %d (%s)", + pi->pam_user, pam_status, + pam_strerror(pamh,pam_status)); + } + } + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + if (pam_status != PAM_SUCCESS) { + /* don't log if quiet_mode is on and pam_status is + * User not known to the underlying authentication module + */ + if (!quiet_mode || pam_status != 10) { + logger(pamh, LOG_NOTICE, + "Authentication failed for user %s: %d (%s)", + pi->pam_user, pam_status, + pam_strerror(pamh,pam_status)); + } + } + break; + case SSS_PAM_CHAUTHTOK: + if (pam_status != PAM_SUCCESS) { + logger(pamh, LOG_NOTICE, + "Password change failed for user %s: %d (%s)", + pi->pam_user, pam_status, + pam_strerror(pamh,pam_status)); + } + break; + case SSS_PAM_ACCT_MGMT: + if (pam_status != PAM_SUCCESS) { + /* don't log if quiet_mode is on and pam_status is + * User not known to the underlying authentication module + */ + if (!quiet_mode || pam_status != 10) { + logger(pamh, LOG_NOTICE, + "Access denied for user %s: %d (%s)", + pi->pam_user, pam_status, + pam_strerror(pamh,pam_status)); + } + } + break; + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_SETCRED: + case SSS_PAM_CLOSE_SESSION: + case SSS_PAM_PREAUTH: + break; + default: + D(("Illegal task [%#x]", task)); + pam_status = PAM_SYSTEM_ERR; + } + +done: + if (buf != NULL ) { + sss_erase_mem_securely((void *)buf, rd.len); + free(buf); + } + free(repbuf); + + return pam_status; +} + +static int prompt_password(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); + sss_erase_mem_securely((void *)answer, strlen(answer)); + free(answer); + answer=NULL; + if (pi->pam_authtok == NULL) { + return PAM_BUF_ERR; + } + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD; + pi->pam_authtok_size=strlen(pi->pam_authtok); + } + + return PAM_SUCCESS; +} + +static int prompt_2fa(pam_handle_t *pamh, struct pam_items *pi, + bool second_factor_optional, + const char *prompt_fa1, const char *prompt_fa2) +{ + int ret; + const struct pam_conv *conv; + const struct pam_message *mesg[2] = { NULL, NULL }; + struct pam_message m[2] = { {0}, {0} }; + struct pam_response *resp = NULL; + size_t needed_size; + + ret = pam_get_item(pamh, PAM_CONV, (const void **) &conv); + if (ret != PAM_SUCCESS) { + return ret; + } + if (conv == NULL || conv->conv == NULL) { + logger(pamh, LOG_ERR, "No conversation function"); + return PAM_SYSTEM_ERR; + } + + m[0].msg_style = PAM_PROMPT_ECHO_OFF; + m[0].msg = prompt_fa1; + m[1].msg_style = PAM_PROMPT_ECHO_OFF; + m[1].msg = prompt_fa2; + + mesg[0] = (const struct pam_message *) m; + /* The following assignment might look a bit odd but is recommended in the + * pam_conv man page to make sure that the second argument of the PAM + * conversation function can be interpreted in two different ways. + * Basically it is important that both the actual struct pam_message and + * the pointers to the struct pam_message are arrays. Since the assignment + * makes clear that mesg[] and (*mesg)[] are arrays it should be kept this + * way and not be replaced by other equivalent assignments. */ + mesg[1] = & (( *mesg )[1]); + + ret = conv->conv(2, mesg, &resp, conv->appdata_ptr); + if (ret != PAM_SUCCESS) { + D(("Conversation failure: %s.", pam_strerror(pamh, ret))); + return ret; + } + + if (resp == NULL) { + D(("response expected, but resp==NULL")); + return PAM_SYSTEM_ERR; + } + + if (resp[0].resp == NULL || *(resp[0].resp) == '\0') { + D(("Missing factor.")); + ret = PAM_CRED_INSUFFICIENT; + goto done; + } + + if (resp[1].resp == NULL || *(resp[1].resp) == '\0') { + /* Missing second factor, assume first factor contains combined 2FA + * credentials if the second factor is not optional. If it is optional + * then it is assumed that the first factor contain the password. */ + pi->pam_authtok = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); + if (pi->pam_authtok == NULL) { + D(("strndup failed.")); + ret = PAM_BUF_ERR; + goto done; + } + pi->pam_authtok_size = strlen(pi->pam_authtok) + 1; + pi->pam_authtok_type = second_factor_optional + ? SSS_AUTHTOK_TYPE_PASSWORD + : SSS_AUTHTOK_TYPE_2FA_SINGLE; + } else if (pi->pam_service != NULL && strcmp(pi->pam_service, "sshd") == 0 + && strcmp(resp[0].resp, resp[1].resp) == 0) { + /* Special handling for SSH with password authentication (ssh's + * 'PasswordAuthentication' option. In this mode the ssh client + * directly prompts the user for a password and the prompts we are + * sending are ignored. Since we send two prompts ssh * will create two + * response as well with the same content. We assume that the combined + * 2FA credentials are used even if the second factor is optional + * because there is no indication about the intention of the user. As a + * result we prefer the more secure variant. */ + + pi->pam_authtok = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); + if (pi->pam_authtok == NULL) { + D(("strndup failed.")); + ret = PAM_BUF_ERR; + goto done; + } + pi->pam_authtok_size = strlen(pi->pam_authtok) + 1; + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_2FA_SINGLE; + } else { + + ret = sss_auth_pack_2fa_blob(resp[0].resp, 0, resp[1].resp, 0, NULL, 0, + &needed_size); + if (ret != EAGAIN) { + D(("sss_auth_pack_2fa_blob failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + pi->pam_authtok = malloc(needed_size); + if (pi->pam_authtok == NULL) { + D(("malloc failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + ret = sss_auth_pack_2fa_blob(resp[0].resp, 0, resp[1].resp, 0, + (uint8_t *) pi->pam_authtok, needed_size, + &needed_size); + if (ret != EOK) { + D(("sss_auth_pack_2fa_blob failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + pi->pam_authtok_size = needed_size; + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_2FA; + pi->first_factor = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); + if (pi->first_factor == NULL) { + D(("strndup failed.")); + ret = PAM_BUF_ERR; + goto done; + } + } + + ret = PAM_SUCCESS; + +done: + if (resp != NULL) { + if (resp[0].resp != NULL) { + sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); + free(resp[0].resp); + } + if (resp[1].resp != NULL) { + sss_erase_mem_securely((void *)resp[1].resp, strlen(resp[1].resp)); + free(resp[1].resp); + } + + free(resp); + resp = NULL; + } + + 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); + sss_erase_mem_securely((void *)answer, strlen(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; +} + +static int prompt_oauth2(pam_handle_t *pamh, struct pam_items *pi) +{ + char *answer = NULL; + char *msg; + int ret; + + if (pi->oauth2_url_complete != NULL) { + ret = asprintf(&msg, _("Authenticate at %1$s and press ENTER."), + pi->oauth2_url_complete); + } else { + ret = asprintf(&msg, _("Authenticate with PIN %1$s at %2$s and press " + "ENTER."), pi->oauth2_pin, pi->oauth2_url); + } + if (ret == -1) { + return PAM_SYSTEM_ERR; + } + + ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, msg, NULL, &answer); + free(msg); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return ret; + } + + /* We don't care about answer here. We just need to notify that the + * authentication has finished. */ + free(answer); + + pi->pam_authtok = strdup(pi->oauth2_pin); + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_OAUTH2; + pi->pam_authtok_size=strlen(pi->oauth2_pin); + + return PAM_SUCCESS; +} + +static int prompt_passkey(pam_handle_t *pamh, struct pam_items *pi) +{ + int ret; + const struct pam_conv *conv; + const struct pam_message *mesg[4] = { NULL, NULL, NULL, NULL }; + struct pam_message m[4] = { {0}, {0}, {0}, {0} }; + struct pam_response *resp = NULL; + bool kerberos_preauth; + bool prompt_pin = false; + int pin_idx = 0; + int msg_idx = 0; + size_t needed_size; + + ret = pam_get_item(pamh, PAM_CONV, (const void **) &conv); + if (ret != PAM_SUCCESS) { + return ret; + } + if (conv == NULL || conv->conv == NULL) { + logger(pamh, LOG_ERR, "No conversation function"); + return PAM_SYSTEM_ERR; + } + + /* check device capabilities + */ + if ((strcasecmp(pi->passkey_devinfo, "dopin") != 0) && + (strcasecmp(pi->passkey_devinfo, "pinonly") != 0) && + (strcasecmp(pi->passkey_devinfo, "pinuv") != 0)) { + /* invalid device or no connected device + * fallback to passord + */ + return EIO; + } + kerberos_preauth = pi->passkey_key != NULL ? true : false; + if (!kerberos_preauth) { + m[msg_idx].msg_style = PAM_TEXT_INFO; + m[msg_idx].msg = PASSKEY_LOCAL_AUTH_MSG; + msg_idx++; + } + + if ( strcasecmp(pi->passkey_prompt_pin, "true") == 0) { + if ((strcasecmp(pi->passkey_devinfo, "dopin") == 0) || + (strcasecmp(pi->passkey_devinfo, "pinonly") == 0)) { + prompt_pin = true; + + m[msg_idx].msg_style = PAM_PROMPT_ECHO_OFF; + m[msg_idx].msg = PASSKEY_DEFAULT_PIN_MSG; + pin_idx = msg_idx; + msg_idx++; + } else { + /* let try UV according device verification capabilities */ + prompt_pin = false; + + /* Prompt to remind the user to perform verification (.eg fingerprint) */ + m[msg_idx].msg_style = PAM_TEXT_INFO; + m[msg_idx].msg = _("Perform User Verification on your device"); + msg_idx++; + } + } + + mesg[0] = (const struct pam_message *) m; + /* The following assignment might look a bit odd but is recommended in the + * pam_conv man page to make sure that the second argument of the PAM + * conversation function can be interpreted in two different ways. + * Basically it is important that both the actual struct pam_message and + * the pointers to the struct pam_message are arrays. Since the assignment + * makes clear that mesg[] and (*mesg)[] are arrays it should be kept this + * way and not be replaced by other equivalent assignments. */ + for (int i = 1; i < msg_idx; i++) { + mesg[i] = & (( *mesg )[i]); + } + + ret = conv->conv(msg_idx, mesg, &resp, conv->appdata_ptr); + if (ret != PAM_SUCCESS) { + D(("Conversation failure: %s.", pam_strerror(pamh, ret))); + return ret; + } + + if (kerberos_preauth) { + if (!prompt_pin) { + resp[pin_idx].resp = NULL; + /* fix passkey_child_credentials.c when NO PIN */ + resp[pin_idx].resp = strdup ("NULL"); + /* end fix */ +} + + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY_KRB; + sss_auth_passkey_calc_size(pi->passkey_prompt_pin, + pi->passkey_key, + resp[pin_idx].resp, + &needed_size); + + pi->pam_authtok = malloc(needed_size); + if (pi->pam_authtok == NULL) { + D(("malloc failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + sss_auth_pack_passkey_blob((uint8_t *)pi->pam_authtok, + pi->passkey_prompt_pin, pi->passkey_key, + resp[pin_idx].resp); + + } else { + if (!prompt_pin) { + /* user verification = false, SSS_AUTHTOK_TYPE_PASSKEY will be reset to + * SSS_AUTHTOK_TYPE_NULL in PAM responder + */ + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY; + pi->pam_authtok = NULL; + pi->pam_authtok_size = 0; + /* fix passkey_child_credentials.c when NO PIN */ + pi->pam_authtok = strdup("null"); + pi->pam_authtok_size = strlen(pi->pam_authtok); + /* end fix */ + ret = PAM_SUCCESS; + goto done; + } else { + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY; + pi->pam_authtok = strdup(resp[pin_idx].resp); + needed_size = strlen(pi->pam_authtok); + } + } + + pi->pam_authtok_size = needed_size; + + /* Fallback to password auth if no PIN was entered */ + if (prompt_pin) { + if (resp[pin_idx].resp == NULL || resp[pin_idx].resp[0] == '\0') { + ret = EIO; + goto done; + } + } + + ret = PAM_SUCCESS; + +done: + if (resp != NULL) { + if (resp[pin_idx].resp != NULL) { + sss_erase_mem_securely((void *)resp[pin_idx].resp, + strlen(resp[pin_idx].resp)); + free(resp[pin_idx].resp); + } + + free(resp); + resp = NULL; + } + + return ret; +} + +static int passkey_get_current_device_info(pam_handle_t *pamh, struct pam_items *pi, + bool quiet_mode) +{ + int ret; + // const char* prompt = _("Connect your passkey device, then press ENTER."); + const char* prompt = "Connect your passkey device, then press ENTER."; + char* answer; + + int ori_flags = pi->flags; + + pi->flags |= PAM_CLI_FLAGS_REQUIRE_PASSKEY_CACHED_DEVINFO; + + ret = send_and_receive(pamh, pi, SSS_PAM_PASSKEY_PREAUTH, quiet_mode); + if (ret != PAM_SUCCESS) { + D(("send_and_receive returned [%d] (ignored) during passkey_preauth", ret)); + ret = PAM_SUCCESS; + } + pi->flags = ori_flags; + + if (pi->passkey_devinfo == NULL) { + ret = PAM_SYSTEM_ERR; + goto done; + } + + if (strcasecmp(pi->passkey_devinfo, "dopin") == 0) { + /* this is an authentication retry (after a wrong UV or PIN) + * which assumes the device is connected + * the user will be prompted for a PIN + */ + goto done; + } + if (strcasecmp(pi->passkey_devinfo, "nodev") == 0) { + /* the cache indicates that no valid device has been + * connected, there we shall fallback to password authentication + */ + goto done; + } + + /* user shall confirm or reconfirm its device is ready */ + ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, prompt, NULL, &answer); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed ignored - we do inot need response but just ENTER")); + ret = PAM_SUCCESS; + } + + + if ((strcasecmp(pi->passkey_devinfo, "pinuv") == 0) || + (strcasecmp(pi->passkey_devinfo, "pinonly") == 0)) { + /* OK */ + goto done; + } + /* cache is empty or indicate invalid info + * request fresh info + */ + + if (pi->passkey_key != NULL) { + + size_t needed_size; + + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY_KRB; + sss_auth_passkey_calc_size(pi->passkey_prompt_pin, + pi->passkey_key, + "", // empty PIN but unsued + &needed_size); + + pi->pam_authtok = malloc(needed_size); + if (pi->pam_authtok == NULL) { + D(("malloc failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + sss_auth_pack_passkey_blob((uint8_t *)pi->pam_authtok, + pi->passkey_prompt_pin, + pi->passkey_key, + ""); + pi->pam_authtok_size = needed_size; + + } else { + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; + pi->pam_authtok = NULL; + pi->pam_authtok_size = 0; + } + ret = send_and_receive(pamh, pi, SSS_PAM_PASSKEY_PREAUTH, quiet_mode); + if (ret != PAM_SUCCESS) { + D(("send_and_receive returned [%d] (ignored) during passkey_preauth", ret)); + ret = PAM_SUCCESS; + } + + if (pi->passkey_devinfo != NULL && + ((strcasecmp(pi->passkey_devinfo, "pinuv") == 0) || + (strcasecmp(pi->passkey_devinfo, "pinonly") == 0))) { + /* OK */ + goto done; + } + + /* inform user she/he shall fallback to password authntication and wait its ENTER + */ + { + char* _prompt; + int r = asprintf(&_prompt, "%s %s", + pi->passkey_devinfo == NULL ? "SYSTEM ERROR DEVINFO" : pi->passkey_devinfo, + " Fallback to password authentication"); + if (r == -1) { + ret = ENOMEM; + goto done; + } + + ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, _prompt, NULL, &answer); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed ignored - we doi not need response but just ENTER")); + ret = PAM_SUCCESS; + } + free (_prompt); + } + + if (pi->passkey_devinfo == NULL) { + ret = PAM_SYSTEM_ERR; + goto done; + } + + ret = PAM_SUCCESS; + + done: + return ret; +} + +#define SC_PROMPT_FMT "PIN for %s: " + +#ifndef discard_const +#define discard_const(ptr) ((void *)((uintptr_t)(ptr))) +#endif + +#define CERT_SEL_PROMPT_FMT "%s" +#define SEL_TITLE discard_const("Please select a certificate") + +static int prompt_multi_cert_gdm(pam_handle_t *pamh, struct pam_items *pi) +{ +#ifdef HAVE_GDM_PAM_EXTENSIONS + int ret; + size_t cert_count = 0; + size_t c; + const struct pam_conv *conv; + struct cert_auth_info *cai; + GdmPamExtensionChoiceListRequest *request = NULL; + GdmPamExtensionChoiceListResponse *response = NULL; + struct pam_message prompt_message; + const struct pam_message *prompt_messages[1]; + struct pam_response *reply = NULL; + char *prompt; + + if (!GDM_PAM_EXTENSION_SUPPORTED(GDM_PAM_EXTENSION_CHOICE_LIST)) { + return ENOTSUP; + } + + if (pi->cert_list == NULL) { + return EINVAL; + } + + DLIST_FOR_EACH(cai, pi->cert_list) { + cert_count++; + } + + ret = pam_get_item(pamh, PAM_CONV, (const void **)&conv); + if (ret != PAM_SUCCESS) { + ret = EIO; + return ret; + } + + request = calloc(1, GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_SIZE(cert_count)); + if (request == NULL) { + ret = ENOMEM; + goto done; + } + GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_INIT(request, SEL_TITLE, cert_count); + + c = 0; + DLIST_FOR_EACH(cai, pi->cert_list) { + ret = asprintf(&prompt, CERT_SEL_PROMPT_FMT, cai->prompt_str); + if (ret == -1) { + ret = ENOMEM; + goto done; + } + free(cai->choice_list_id); + ret = asprintf(&cai->choice_list_id, "%zu", c); + if (ret == -1) { + cai->choice_list_id = NULL; + free(prompt); + ret = ENOMEM; + goto done; + } + + request->list.items[c].key = cai->choice_list_id; + request->list.items[c++].text = prompt; + } + + GDM_PAM_EXTENSION_MESSAGE_TO_BINARY_PROMPT_MESSAGE(request, + &prompt_message); + prompt_messages[0] = &prompt_message; + + ret = conv->conv(1, prompt_messages, &reply, conv->appdata_ptr); + if (ret != PAM_SUCCESS) { + ret = EIO; + goto done; + } + + ret = EIO; + response = GDM_PAM_EXTENSION_REPLY_TO_CHOICE_LIST_RESPONSE(reply); + if (response->key == NULL) { + goto done; + } + + DLIST_FOR_EACH(cai, pi->cert_list) { + if (strcmp(response->key, cai->choice_list_id) == 0) { + pam_info(pamh, "Certificate ‘%s’ selected", cai->key_id); + pi->selected_cert = cai; + ret = 0; + break; + } + } + +done: + if (request != NULL) { + for (c = 0; c < cert_count; c++) { + free(discard_const(request->list.items[c++].text)); + } + free(request); + } + free(response); + + return ret; +#else + return ENOTSUP; +#endif +} + +#define TEXT_CERT_SEL_PROMPT_FMT "%s\n[%zu]:\n%s\n" +#define TEXT_SEL_TITLE discard_const("Please select a certificate by typing " \ + "the corresponding number\n") + +static int prompt_multi_cert(pam_handle_t *pamh, struct pam_items *pi) +{ + int ret; + size_t cert_count = 0; + size_t tries = 0; + long int resp = -1; + struct cert_auth_info *cai; + char *prompt; + char *tmp; + char *answer; + char *ep; + + /* First check if gdm extension is supported */ + ret = prompt_multi_cert_gdm(pamh, pi); + if (ret != ENOTSUP) { + return ret; + } + + if (pi->cert_list == NULL) { + return EINVAL; + } + + prompt = strdup(TEXT_SEL_TITLE); + if (prompt == NULL) { + return ENOMEM; + } + + DLIST_FOR_EACH(cai, pi->cert_list) { + cert_count++; + ret = asprintf(&tmp, TEXT_CERT_SEL_PROMPT_FMT, prompt, cert_count, + cai->prompt_str); + free(prompt); + if (ret == -1) { + return ENOMEM; + } + + prompt = tmp; + } + + do { + ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_ON, prompt, NULL, + &answer); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + break; + } + + errno = 0; + resp = strtol(answer, &ep, 10); + if (errno == 0 && *ep == '\0' && resp > 0 && resp <= cert_count) { + /* do not free answer ealier because ep is pointing to it */ + free(answer); + break; + } + free(answer); + resp = -1; + } while (++tries < 5); + free(prompt); + + pi->selected_cert = NULL; + ret = ENOENT; + if (resp > 0 && resp <= cert_count) { + cert_count = 0; + DLIST_FOR_EACH(cai, pi->cert_list) { + cert_count++; + if (resp == cert_count) { + pam_info(pamh, "Certificate ‘%s’ selected", cai->key_id); + pi->selected_cert = cai; + ret = 0; + break; + } + } + } + + return ret; +} + +#define SC_INSERT_PROMPT _("Please (re)insert (different) Smartcard") + +static int prompt_sc_pin(pam_handle_t *pamh, struct pam_items *pi) +{ + int ret; + char *answer = NULL; + char *prompt = NULL; + size_t needed_size; + const struct pam_conv *conv; + const struct pam_message *mesg[2] = { NULL, NULL }; + struct pam_message m[2] = { { 0 }, { 0 } }; + struct pam_response *resp = NULL; + struct cert_auth_info *cai = pi->selected_cert; + + if (cai == NULL && (SERVICE_IS_GDM_SMARTCARD(pi) + || (pi->flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH))) { + ret = asprintf(&prompt, SC_INSERT_PROMPT); + } else if (cai == NULL || cai->token_name == NULL + || *cai->token_name == '\0') { + return PAM_SYSTEM_ERR; + } else { + ret = asprintf(&prompt, SC_PROMPT_FMT, cai->token_name); + } + + if (ret == -1) { + D(("asprintf failed.")); + return PAM_SYSTEM_ERR; + } + + if (cai == NULL) { + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, prompt, NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("Conversation failure: %s, ignored", pam_strerror(pamh, ret))); + } + } + + if (pi->user_name_hint) { + ret = pam_get_item(pamh, PAM_CONV, (const void **)&conv); + if (ret != PAM_SUCCESS) { + free(prompt); + return ret; + } + if (conv == NULL || conv->conv == NULL) { + logger(pamh, LOG_ERR, "No conversation function"); + free(prompt); + return PAM_SYSTEM_ERR; + } + + m[0].msg_style = PAM_PROMPT_ECHO_OFF; + m[0].msg = prompt; + m[1].msg_style = PAM_PROMPT_ECHO_ON; + m[1].msg = "User name hint: "; + + mesg[0] = (const struct pam_message *)m; + /* The following assignment might look a bit odd but is recommended in the + * pam_conv man page to make sure that the second argument of the PAM + * conversation function can be interpreted in two different ways. + * Basically it is important that both the actual struct pam_message and + * the pointers to the struct pam_message are arrays. Since the assignment + * makes clear that mesg[] and (*mesg)[] are arrays it should be kept this + * way and not be replaced by other equivalent assignments. */ + mesg[1] = &((*mesg)[1]); + + ret = conv->conv(2, mesg, &resp, conv->appdata_ptr); + free(prompt); + if (ret != PAM_SUCCESS) { + D(("Conversation failure: %s.", pam_strerror(pamh, ret))); + return ret; + } + + if (resp == NULL) { + D(("response expected, but resp==NULL")); + return PAM_SYSTEM_ERR; + } + + if (resp[0].resp == NULL || *(resp[0].resp) == '\0') { + D(("Missing PIN.")); + ret = PAM_CRED_INSUFFICIENT; + goto done; + } + + answer = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); + sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); + free(resp[0].resp); + resp[0].resp = NULL; + if (answer == NULL) { + D(("strndup failed")); + ret = PAM_BUF_ERR; + goto done; + } + + if (resp[1].resp != NULL && *(resp[1].resp) != '\0') { + ret = pam_set_item(pamh, PAM_USER, resp[1].resp); + free(resp[1].resp); + resp[1].resp = NULL; + if (ret != PAM_SUCCESS) { + D(("Failed to set PAM_USER with user name hint [%s]", + pam_strerror(pamh, ret))); + goto done; + } + + ret = pam_get_item(pamh, PAM_USER, (const void **)&(pi->pam_user)); + if (ret != PAM_SUCCESS) { + D(("Failed to get PAM_USER with user name hint [%s]", + pam_strerror(pamh, ret))); + goto done; + } + + pi->pam_user_size = strlen(pi->pam_user) + 1; + } + } else { + ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, prompt, NULL, + &answer); + free(prompt); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return ret; + } + } + + if (cai == NULL) { + /* it is expected that the user just replaces the Smartcard which + * would trigger gdm to restart the PAM module, so it is not + * expected that this part of the code is reached. */ + ret = PAM_AUTHINFO_UNAVAIL; + goto done; + } + + if (answer == NULL || *answer == '\0') { + D(("Missing PIN.")); + ret = PAM_CRED_INSUFFICIENT; + goto done; + } else { + + ret = sss_auth_pack_sc_blob(answer, 0, cai->token_name, 0, + cai->module_name, 0, + cai->key_id, 0, + cai->label, 0, + NULL, 0, &needed_size); + if (ret != EAGAIN) { + D(("sss_auth_pack_sc_blob failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + pi->pam_authtok = malloc(needed_size); + if (pi->pam_authtok == NULL) { + D(("malloc failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + ret = sss_auth_pack_sc_blob(answer, 0, cai->token_name, 0, + cai->module_name, 0, + cai->key_id, 0, + cai->label, 0, + (uint8_t *) pi->pam_authtok, needed_size, + &needed_size); + if (ret != EOK) { + D(("sss_auth_pack_sc_blob failed.")); + free((void *)pi->pam_authtok); + ret = PAM_BUF_ERR; + goto done; + } + + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_SC_PIN; + pi->pam_authtok_size = needed_size; + } + + ret = PAM_SUCCESS; + +done: + if (answer != NULL) { + sss_erase_mem_securely((void *)answer, strlen(answer)); + free(answer); + answer=NULL; + } + + if (resp != NULL) { + if (resp[0].resp != NULL) { + sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); + free(resp[0].resp); + } + if (resp[1].resp != NULL) { + sss_erase_mem_securely((void *)resp[1].resp, strlen(resp[1].resp)); + free(resp[1].resp); + } + + free(resp); + resp = NULL; + } + + return ret; +} + +static int prompt_new_password(pam_handle_t *pamh, struct pam_items *pi) +{ + int ret; + char *answer = NULL; + + ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, + _("New Password: "), + _("Reenter new Password: "), + &answer); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return ret; + } + if (answer == NULL) { + pi->pam_newauthtok = NULL; + pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_EMPTY; + pi->pam_newauthtok_size=0; + } else { + pi->pam_newauthtok = strdup(answer); + sss_erase_mem_securely((void *)answer, strlen(answer)); + free(answer); + answer=NULL; + if (pi->pam_newauthtok == NULL) { + return PAM_BUF_ERR; + } + pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_PASSWORD; + pi->pam_newauthtok_size=strlen(pi->pam_newauthtok); + } + + return PAM_SUCCESS; +} + +static void eval_argv(pam_handle_t *pamh, int argc, const char **argv, + uint32_t *flags, int *retries, bool *quiet_mode, + const char **domains) +{ + char *ep; + + *quiet_mode = false; + + for (; argc-- > 0; ++argv) { + if (strcmp(*argv, "forward_pass") == 0) { + *flags |= PAM_CLI_FLAGS_FORWARD_PASS; + } else if (strcmp(*argv, "use_first_pass") == 0) { + *flags |= PAM_CLI_FLAGS_USE_FIRST_PASS; + } else if (strcmp(*argv, "use_authtok") == 0) { + *flags |= PAM_CLI_FLAGS_USE_AUTHTOK; + } else if (strncmp(*argv, OPT_DOMAINS_KEY, strlen(OPT_DOMAINS_KEY)) == 0) { + if (*(*argv+strlen(OPT_DOMAINS_KEY)) == '\0') { + logger(pamh, LOG_ERR, "Missing argument to option domains."); + *domains = ""; + } else { + *domains = *argv+strlen(OPT_DOMAINS_KEY); + } + + } else if (strncmp(*argv, OPT_RETRY_KEY, strlen(OPT_RETRY_KEY)) == 0) { + if (*(*argv+6) == '\0') { + logger(pamh, LOG_ERR, "Missing argument to option retry."); + *retries = 0; + } else { + errno = 0; + *retries = strtol(*argv+6, &ep, 10); + if (errno != 0) { + D(("strtol failed [%d][%s]", errno, strerror(errno))); + *retries = 0; + } + if (*ep != '\0') { + logger(pamh, LOG_ERR, "Argument to option retry contains " + "extra characters."); + *retries = 0; + } + if (*retries < 0) { + logger(pamh, LOG_ERR, "Argument to option retry must not " + "be negative."); + *retries = 0; + } + } + } else if (strcmp(*argv, "quiet") == 0) { + *quiet_mode = true; + } else if (strcmp(*argv, "allow_chauthtok_by_root") == 0) { + *flags |= PAM_CLI_FLAGS_ALLOW_CHAUTHTOK_BY_ROOT; + } else if (strcmp(*argv, "ignore_unknown_user") == 0) { + *flags |= PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER; + } else if (strcmp(*argv, "ignore_authinfo_unavail") == 0) { + *flags |= PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL; + } else if (strcmp(*argv, "use_2fa") == 0) { + *flags |= PAM_CLI_FLAGS_USE_2FA; + } else if (strcmp(*argv, "allow_missing_name") == 0) { + *flags |= PAM_CLI_FLAGS_ALLOW_MISSING_NAME; + } else if (strcmp(*argv, "prompt_always") == 0) { + *flags |= PAM_CLI_FLAGS_PROMPT_ALWAYS; + } else if (strcmp(*argv, "try_cert_auth") == 0) { + *flags |= PAM_CLI_FLAGS_TRY_CERT_AUTH; + } else if (strcmp(*argv, "require_cert_auth") == 0) { + *flags |= PAM_CLI_FLAGS_REQUIRE_CERT_AUTH; + } else { + logger(pamh, LOG_WARNING, "unknown option: %s", *argv); + } + } + + return; +} + +static int prompt_by_config(pam_handle_t *pamh, struct pam_items *pi) +{ + size_t c; + int ret = PAM_SUCCESS; + + if (pi->pc == NULL || *pi->pc == NULL) { + return PAM_SYSTEM_ERR; + } + + 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: + if (pi->password_prompting) { + ret = prompt_2fa(pamh, pi, true, pc_get_2fa_1st_prompt(pi->pc[c]), + pc_get_2fa_2nd_prompt(pi->pc[c])); + } else { + ret = prompt_2fa(pamh, pi, false, 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_PASSKEY: + ret = prompt_passkey(pamh, pi); + /* no more supported. need devinfo. Cannot be done here + pc_get_passkey_inter_prompt(pi->pc[c]), + pc_get_passkey_touch_prompt(pi->pc[c])); + */ + break; + case PC_TYPE_SC_PIN: + ret = prompt_sc_pin(pamh, pi); + /* Todo: add extra string option */ + break; + default: + ret = PAM_SYSTEM_ERR; + } + + /* 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) +{ + int ret; + const char *pin = NULL; + + if ((flags & PAM_CLI_FLAGS_USE_FIRST_PASS) + || ( pi->pamstack_authtok != NULL + && *(pi->pamstack_authtok) != '\0' + && !(flags & PAM_CLI_FLAGS_PROMPT_ALWAYS))) { + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PAM_STACKED; + pi->pam_authtok = strdup(pi->pamstack_authtok); + if (pi->pam_authtok == NULL) { + D(("option use_first_pass set, but no password found")); + return PAM_BUF_ERR; + } + pi->pam_authtok_size = strlen(pi->pam_authtok); + } else { + if (pi->oauth2_url != NULL) { + /* Prompt config is not supported for OAuth2. */ + ret = prompt_oauth2(pamh, pi); + } else if (pi->pc != NULL) { + ret = prompt_by_config(pamh, pi); + } 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 if (SERVICE_IS_GDM_SMARTCARD(pi) + || (pi->flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH)) { + /* Use pin prompt as fallback for gdm-smartcard */ + ret = prompt_sc_pin(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, true, _("First Factor: "), + _("Second Factor (optional): ")); + } else { + ret = prompt_2fa(pamh, pi, false, _("First Factor: "), + _("Second Factor: ")); + } + } else if (pi->passkey_prompt_pin != NULL && pi->passkey_devinfo != NULL) { + ret = prompt_passkey(pamh, pi); + /* Fallback to password auth if no PIN was entered */ + if (ret == EIO) { + ret = prompt_password(pamh, pi, _("Password: ")); + if (pi->pam_authtok_size == 0) { + D(("Empty password failure")); + pi->passkey_prompt_pin = NULL; + return PAM_AUTHTOK_ERR; + } + } + } else { + ret = prompt_password(pamh, pi, _("Password: ")); + } + } + if (ret != PAM_SUCCESS) { + D(("failed to get password from user")); + return ret; + } + + if (flags & PAM_CLI_FLAGS_FORWARD_PASS) { + if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_PASSWORD + || pi->pam_authtok_type == SSS_AUTHTOK_TYPE_PAM_STACKED) { + ret = pam_set_item(pamh, PAM_AUTHTOK, pi->pam_authtok); + } else if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_SC_PIN) { + pin = sss_auth_get_pin_from_sc_blob((uint8_t *) pi->pam_authtok, + pi->pam_authtok_size); + if (pin != NULL) { + ret = pam_set_item(pamh, PAM_AUTHTOK, pin); + } else { + ret = PAM_SYSTEM_ERR; + } + } else if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_2FA + && pi->first_factor != NULL) { + ret = pam_set_item(pamh, PAM_AUTHTOK, pi->first_factor); + } else { + ret = PAM_SYSTEM_ERR; + } + if (ret != PAM_SUCCESS) { + D(("Failed to set PAM_AUTHTOK [%s], " + "authtok may not be available for other modules", + pam_strerror(pamh,ret))); + } + } + } + + return PAM_SUCCESS; +} + +static int check_authtok_data(pam_handle_t *pamh, struct pam_items *pi) +{ + int pam_status; + int *authtok_type; + size_t *authtok_size; + char *authtok_data; + + pam_status = pam_get_data(pamh, PAM_SSS_AUTHOK_TYPE, + (const void **) &authtok_type); + if (pam_status != PAM_SUCCESS) { + D(("pam_get_data failed.")); + return EIO; + } + + pam_status = pam_get_data(pamh, PAM_SSS_AUTHOK_SIZE, + (const void **) &authtok_size); + if (pam_status != PAM_SUCCESS) { + D(("pam_get_data failed.")); + return EIO; + } + + pam_status = pam_get_data(pamh, PAM_SSS_AUTHOK_DATA, + (const void **) &authtok_data); + if (pam_status != PAM_SUCCESS) { + D(("pam_get_data failed.")); + return EIO; + } + + pi->pam_authtok = malloc(*authtok_size); + if (pi->pam_authtok == NULL) { + D(("malloc failed.")); + return ENOMEM; + } + memcpy(pi->pam_authtok, authtok_data, *authtok_size); + + pi->pam_authtok_type = *authtok_type; + pi->pam_authtok_size = *authtok_size; + + return 0; +} + +static int keep_authtok_data(pam_handle_t *pamh, struct pam_items *pi) +{ + int pam_status; + int *authtok_type; + size_t *authtok_size; + char *authtok_data; + + authtok_type = malloc(sizeof(int)); + if (authtok_type == NULL) { + D(("malloc failed.")); + return ENOMEM; + } + *authtok_type = pi->pam_authtok_type; + + pam_status = pam_set_data(pamh, PAM_SSS_AUTHOK_TYPE, authtok_type, + free_exp_data); + if (pam_status != PAM_SUCCESS) { + free(authtok_type); + D(("pam_set_data failed.")); + return EIO; + } + + authtok_size = malloc(sizeof(size_t)); + if (authtok_size == NULL) { + D(("malloc failed.")); + return ENOMEM; + } + *authtok_size = pi->pam_authtok_size; + + pam_status = pam_set_data(pamh, PAM_SSS_AUTHOK_SIZE, authtok_size, + free_exp_data); + if (pam_status != PAM_SUCCESS) { + free(authtok_size); + D(("pam_set_data failed.")); + return EIO; + } + + authtok_data = malloc(pi->pam_authtok_size); + if (authtok_data == NULL) { + D(("malloc failed.")); + return ENOMEM; + } + memcpy(authtok_data, pi->pam_authtok, pi->pam_authtok_size); + + pam_status = pam_set_data(pamh, PAM_SSS_AUTHOK_DATA, authtok_data, + free_exp_data); + if (pam_status != PAM_SUCCESS) { + free(authtok_data); + D(("pam_set_data failed.")); + return EIO; + } + + return 0; +} + +static int get_authtok_for_password_change(pam_handle_t *pamh, + struct pam_items *pi, + uint32_t flags, + int pam_flags) +{ + int ret; + const int *exp_data = NULL; + ret = pam_get_data(pamh, PWEXP_FLAG, (const void **) &exp_data); + if (ret != PAM_SUCCESS) { + exp_data = NULL; + } + + if (pam_flags & PAM_PRELIM_CHECK) { + if (!(flags & PAM_CLI_FLAGS_ALLOW_CHAUTHTOK_BY_ROOT) && getuid() == 0 && !exp_data ) + return PAM_SUCCESS; + + 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, true, + _("First Factor (Current Password): "), + _("Second Factor (optional): ")); + } else { + ret = prompt_2fa(pamh, pi, false, + _("First Factor (Current Password): "), + _("Second Factor: ")); + } + } else if ((flags & PAM_CLI_FLAGS_USE_FIRST_PASS) + && check_authtok_data(pamh, pi) != 0) { + if (pi->pamstack_oldauthtok == NULL) { + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; + pi->pam_authtok = NULL; + pi->pam_authtok_size = 0; + } else { + pi->pam_authtok = strdup(pi->pamstack_oldauthtok); + if (pi->pam_authtok == NULL) { + D(("strdup failed")); + return PAM_BUF_ERR; + } + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD; + pi->pam_authtok_size = strlen(pi->pam_authtok); + } + ret = PAM_SUCCESS; + } else { + ret = prompt_password(pamh, pi, _("Current Password: ")); + } + if (ret != PAM_SUCCESS) { + D(("failed to get credentials from user")); + return ret; + } + + ret = pam_set_item(pamh, PAM_OLDAUTHTOK, pi->pam_authtok); + if (ret != PAM_SUCCESS) { + D(("Failed to set PAM_OLDAUTHTOK [%s], " + "oldauthtok may not be available", + pam_strerror(pamh,ret))); + return ret; + } + + if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_2FA) { + ret = keep_authtok_data(pamh, pi); + if (ret != 0) { + D(("Failed to store authtok data to pam handle. Password " + "change might fail.")); + } + } + + return PAM_SUCCESS; + } + + if (check_authtok_data(pamh, pi) != 0) { + if (pi->pamstack_oldauthtok == NULL) { + if (getuid() != 0) { + D(("no password found for chauthtok")); + return PAM_BUF_ERR; + } else { + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; + pi->pam_authtok = NULL; + pi->pam_authtok_size = 0; + } + } else { + pi->pam_authtok = strdup(pi->pamstack_oldauthtok); + if (pi->pam_authtok == NULL) { + D(("strdup failed")); + return PAM_BUF_ERR; + } + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD; + pi->pam_authtok_size = strlen(pi->pam_authtok); + } + } + + if (flags & PAM_CLI_FLAGS_USE_AUTHTOK) { + pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_PASSWORD; + pi->pam_newauthtok = strdup(pi->pamstack_authtok); + if (pi->pam_newauthtok == NULL) { + D(("option use_authtok set, but no new password found")); + return PAM_BUF_ERR; + } + pi->pam_newauthtok_size = strlen(pi->pam_newauthtok); + } else { + ret = prompt_new_password(pamh, pi); + if (ret != PAM_SUCCESS) { + D(("failed to get new password from user")); + return ret; + } + + if (flags & PAM_CLI_FLAGS_FORWARD_PASS) { + ret = pam_set_item(pamh, PAM_AUTHTOK, pi->pam_newauthtok); + if (ret != PAM_SUCCESS) { + D(("Failed to set PAM_AUTHTOK [%s], " + "oldauthtok may not be available", + pam_strerror(pamh,ret))); + } + } + } + + return PAM_SUCCESS; +} + +#define SC_ENTER_LABEL_FMT "Please insert smart card labeled\n %s" +#define SC_ENTER_FMT "Please insert smart card" + +static int check_login_token_name(pam_handle_t *pamh, struct pam_items *pi, + int retries, bool quiet_mode) +{ + int ret; + int pam_status; + char *login_token_name; + char *prompt = NULL; + uint32_t orig_flags = pi->flags; + + login_token_name = getenv("PKCS11_LOGIN_TOKEN_NAME"); + if (login_token_name == NULL + && !(pi->flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH)) { + return PAM_SUCCESS; + } + + if (login_token_name == NULL) { + ret = asprintf(&prompt, SC_ENTER_FMT); + } else { + ret = asprintf(&prompt, SC_ENTER_LABEL_FMT, login_token_name); + } + if (ret == -1) { + return ENOMEM; + } + + pi->flags |= PAM_CLI_FLAGS_REQUIRE_CERT_AUTH; + + /* TODO: check multiple cert case */ + while (pi->cert_list == NULL || pi->cert_list->token_name == NULL + || (login_token_name != NULL + && strcmp(login_token_name, + pi->cert_list->token_name) != 0)) { + + free_cert_list(pi->cert_list); + pi->cert_list = NULL; + if (retries < 0) { + ret = PAM_AUTHINFO_UNAVAIL; + goto done; + } + retries--; + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, prompt, NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + goto done; + } + + pam_status = send_and_receive(pamh, pi, SSS_PAM_PREAUTH, quiet_mode); + if (pam_status != PAM_SUCCESS) { + D(("send_and_receive returned [%d] during pre-auth", pam_status)); + /* + * Since we are waiting for the right Smartcard to be inserted errors + * can be ignored here. + */ + } + } + + ret = PAM_SUCCESS; + +done: + + pi->flags = orig_flags; + free(prompt); + + return ret; +} + +static int pam_sss(enum sss_cli_command task, pam_handle_t *pamh, + int pam_flags, int argc, const char **argv) +{ + int ret; + int pam_status; + struct pam_items pi = { 0 }; + uint32_t flags = 0; + const int *exp_data; + int *pw_exp_data; + bool retry = false; + bool quiet_mode = false; + int retries = 0; + const char *domains = NULL; + + bindtextdomain(PACKAGE, LOCALEDIR); + + D(("Hello pam_sssd: %#x", task)); + + eval_argv(pamh, argc, argv, &flags, &retries, &quiet_mode, &domains); + + /* Fail all authentication on misconfigured domains= parameter. The admin + * probably wanted to restrict authentication, so it's safer to fail */ + if (domains && strcmp(domains, "") == 0) { + return PAM_SYSTEM_ERR; + } + + pi.requested_domains = domains; + + ret = get_pam_items(pamh, flags, &pi); + if (ret != PAM_SUCCESS) { + D(("get items returned error: %s", pam_strerror(pamh,ret))); + if ((flags & PAM_CLI_FLAGS_TRY_CERT_AUTH) + || (flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) ) { + return PAM_AUTHINFO_UNAVAIL; + } + if (flags & PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER && ret == PAM_USER_UNKNOWN) { + ret = PAM_IGNORE; + } + if (flags & PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL + && ret == PAM_AUTHINFO_UNAVAIL) { + ret = PAM_IGNORE; + } + return ret; + } + + do { + retry = false; + + switch(task) { + case SSS_PAM_AUTHENTICATE: + /* + * Only do preauth if + * - PAM_CLI_FLAGS_USE_FIRST_PASS is not set + * - no password is on the stack or PAM_CLI_FLAGS_PROMPT_ALWAYS is set + * - preauth indicator file exists. + */ + if ( !(flags & PAM_CLI_FLAGS_USE_FIRST_PASS) + && (pi.pam_authtok == NULL + || (flags & PAM_CLI_FLAGS_PROMPT_ALWAYS)) + && access(PAM_PREAUTH_INDICATOR, F_OK) == 0) { + + if (flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) { + /* Do not use PAM_CLI_FLAGS_REQUIRE_CERT_AUTH in the first + * SSS_PAM_PREAUTH run. In case a card is already inserted + * we do not have to prompt to insert a card. */ + pi.flags &= ~PAM_CLI_FLAGS_REQUIRE_CERT_AUTH; + pi.flags |= PAM_CLI_FLAGS_TRY_CERT_AUTH; + } + + pam_status = send_and_receive(pamh, &pi, SSS_PAM_PREAUTH, + quiet_mode); + + pi.flags = flags; + if (pam_status != PAM_SUCCESS) { + D(("send_and_receive returned [%d] during pre-auth", + pam_status)); + /* + * Since we are only interested in the result message + * and will always use password authentication + * as a fallback (except for gdm-smartcard), + * errors can be ignored here. + */ + } + } + + if (flags & PAM_CLI_FLAGS_TRY_CERT_AUTH + && pi.cert_list == NULL) { + D(("No certificates for authentication available.")); + overwrite_and_free_pam_items(&pi); + return PAM_AUTHINFO_UNAVAIL; + } + + if (SERVICE_IS_GDM_SMARTCARD(&pi) + || (flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH)) { + ret = check_login_token_name(pamh, &pi, retries, + quiet_mode); + if (ret != PAM_SUCCESS) { + D(("check_login_token_name failed.\n")); + } + } + + if (pi.passkey_prompt_pin != NULL && pi.passkey_devinfo == NULL) { + ret = passkey_get_current_device_info(pamh, &pi, quiet_mode); + if (ret != PAM_SUCCESS) { + D(("passkey_get_current_device_info failed.\n")); + overwrite_and_free_pam_items(&pi); + return ret; + } + } + + ret = get_authtok_for_authentication(pamh, &pi, flags); + if (ret != PAM_SUCCESS) { + D(("failed to get authentication token: %s", + pam_strerror(pamh, ret))); + overwrite_and_free_pam_items(&pi); + return ret; + } + break; + case SSS_PAM_CHAUTHTOK: + /* + * Even if we only want to change the (long term) password + * there are cases where more than the password is needed to + * get the needed privileges in a backend to change the + * password. + * + * E.g. with mandatory 2-factor authentication we have to ask + * not only for the current password but for the second + * factor, e.g. the one-time token value, as well. + * + * The means the preauth step has to be done here as well but + * only if + * - PAM_PRELIM_CHECK is set + * - PAM_CLI_FLAGS_USE_FIRST_PASS is not set + * - no password is on the stack or PAM_CLI_FLAGS_PROMPT_ALWAYS is set + * - preauth indicator file exists. + */ + if ( (pam_flags & PAM_PRELIM_CHECK) + && !(flags & PAM_CLI_FLAGS_USE_FIRST_PASS) + && (pi.pam_authtok == NULL + || (flags & PAM_CLI_FLAGS_PROMPT_ALWAYS)) + && access(PAM_PREAUTH_INDICATOR, F_OK) == 0) { + pam_status = send_and_receive(pamh, &pi, SSS_PAM_PREAUTH, + quiet_mode); + if (pam_status != PAM_SUCCESS) { + D(("send_and_receive returned [%d] during pre-auth", + pam_status)); + /* + * Since we are only interested in the result message + * and will always use password authentication + * as a fallback, errors can be ignored here. + */ + } + } + + ret = get_authtok_for_password_change(pamh, &pi, flags, pam_flags); + if (ret != PAM_SUCCESS) { + D(("failed to get tokens for password change: %s", + pam_strerror(pamh, ret))); + overwrite_and_free_pam_items(&pi); + return ret; + } + + if (pam_flags & PAM_PRELIM_CHECK) { + if (pi.pam_authtok_type == SSS_AUTHTOK_TYPE_2FA) { + /* We cannot validate the credentials with an OTP + * token value during PAM_PRELIM_CHECK because it + * would be invalid for the actual password change. So + * we are done. */ + + overwrite_and_free_pam_items(&pi); + return PAM_SUCCESS; + } + task = SSS_PAM_CHAUTHTOK_PRELIM; + } + break; + case SSS_PAM_ACCT_MGMT: + case SSS_PAM_SETCRED: + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_CLOSE_SESSION: + break; + default: + D(("Illegal task [%#x]", task)); + overwrite_and_free_pam_items(&pi); + return PAM_SYSTEM_ERR; + } + + pam_status = send_and_receive(pamh, &pi, task, quiet_mode); + + if (flags & PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER + && pam_status == PAM_USER_UNKNOWN) { + pam_status = PAM_IGNORE; + } + if (flags & PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL + && pam_status == PAM_AUTHINFO_UNAVAIL) { + pam_status = PAM_IGNORE; + } + + switch (task) { + case SSS_PAM_AUTHENTICATE: + /* We allow sssd to send the return code PAM_NEW_AUTHTOK_REQD during + * authentication, see sss_cli.h for details */ + if (pam_status == PAM_NEW_AUTHTOK_REQD) { + D(("Authtoken expired, trying to change it")); + + pw_exp_data = malloc(sizeof(int)); + if (pw_exp_data == NULL) { + D(("malloc failed.")); + pam_status = PAM_BUF_ERR; + break; + } + *pw_exp_data = 1; + + pam_status = pam_set_data(pamh, PWEXP_FLAG, pw_exp_data, + free_exp_data); + if (pam_status != PAM_SUCCESS) { + D(("pam_set_data failed.")); + } + } + break; + case SSS_PAM_ACCT_MGMT: + if (pam_status == PAM_SUCCESS && + pam_get_data(pamh, PWEXP_FLAG, (const void **) &exp_data) == + PAM_SUCCESS) { + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, + _("Password expired. Change your password now."), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + } + pam_status = PAM_NEW_AUTHTOK_REQD; + } + break; + case SSS_PAM_CHAUTHTOK: + if (pam_status != PAM_SUCCESS && pam_status != PAM_USER_UNKNOWN) { + ret = pam_set_item(pamh, PAM_AUTHTOK, NULL); + if (ret != PAM_SUCCESS) { + D(("Failed to unset PAM_AUTHTOK [%s]", + pam_strerror(pamh,ret))); + } + ret = pam_set_item(pamh, PAM_OLDAUTHTOK, NULL); + if (ret != PAM_SUCCESS) { + D(("Failed to unset PAM_OLDAUTHTOK [%s]", + pam_strerror(pamh,ret))); + } + } + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + if (pam_status == PAM_PERM_DENIED && pi.pam_authtok_size == 0 && + getuid() == 0 && + pam_get_data(pamh, PWEXP_FLAG, (const void **) &exp_data) != + PAM_SUCCESS) { + + ret = select_pw_reset_message(pamh, &pi); + if (ret != 0) { + D(("select_pw_reset_message failed.\n")); + } + } + default: + /* nothing to do */ + break; + } + + overwrite_and_free_pam_items(&pi); + + D(("retries [%d].", retries)); + + if (pam_status != PAM_SUCCESS && + (task == SSS_PAM_AUTHENTICATE || task == SSS_PAM_CHAUTHTOK_PRELIM) && + retries > 0) { + retry = true; + retries--; + + flags &= ~PAM_CLI_FLAGS_USE_FIRST_PASS; + ret = pam_set_item(pamh, PAM_AUTHTOK, NULL); + if (ret != PAM_SUCCESS) { + D(("Failed to unset PAM_AUTHTOK [%s]", + pam_strerror(pamh,ret))); + } + ret = pam_set_item(pamh, PAM_OLDAUTHTOK, NULL); + if (ret != PAM_SUCCESS) { + D(("Failed to unset PAM_OLDAUTHTOK [%s]", + pam_strerror(pamh,ret))); + } + } + } while(retry); + + return pam_status; +} + +PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, + const char **argv ) +{ + return pam_sss(SSS_PAM_AUTHENTICATE, pamh, flags, argc, argv); +} + + +PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, + const char **argv ) +{ + return pam_sss(SSS_PAM_SETCRED, pamh, flags, argc, argv); +} + +PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, + const char **argv ) +{ + return pam_sss(SSS_PAM_ACCT_MGMT, pamh, flags, argc, argv); +} + +PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, + const char **argv ) +{ + return pam_sss(SSS_PAM_CHAUTHTOK, pamh, flags, argc, argv); +} + +PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, + const char **argv ) +{ + return pam_sss(SSS_PAM_OPEN_SESSION, pamh, flags, argc, argv); +} + +PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, + const char **argv ) +{ + return pam_sss(SSS_PAM_CLOSE_SESSION, pamh, flags, argc, argv); +} + + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_sssd_modstruct ={ + "pam_sssd", + pam_sm_authenticate, + pam_sm_setcred, + pam_sm_acct_mgmt, + pam_sm_open_session, + pam_sm_close_session, + pam_sm_chauthtok +}; + +#endif diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h index d3570f505a1..817a61bb266 100644 --- a/src/sss_client/sss_cli.h +++ b/src/sss_client/sss_cli.h @@ -1,814 +1,824 @@ -/* - SSSD - - Client Interface for NSS and PAM. - - Authors: - Simo Sorce - - Copyright (C) Red Hat, Inc 2007 - - 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 . -*/ - -#ifndef _SSSCLI_H -#define _SSSCLI_H - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "shared/safealign.h" - -#ifndef HAVE_ERRNO_T -#define HAVE_ERRNO_T -typedef int errno_t; -#else -#include -#endif - -#ifndef EOK -#define EOK 0 -#endif - -#ifndef NETDB_INTERNAL -#define NETDB_INTERNAL (-1) -#endif - -#define SSS_NSS_PROTOCOL_VERSION 1 -#define SSS_PAM_PROTOCOL_VERSION 3 -#define SSS_SUDO_PROTOCOL_VERSION 1 -#define SSS_AUTOFS_PROTOCOL_VERSION 1 -#define SSS_SSH_PROTOCOL_VERSION 0 -#define SSS_PAC_PROTOCOL_VERSION 1 - -#ifdef LOGIN_NAME_MAX -#define SSS_NAME_MAX LOGIN_NAME_MAX -#else -#define SSS_NAME_MAX 256 -#endif - -/** - * @defgroup sss_cli_command SSS client commands - * @{ - */ - -/** The allowed commands an SSS client can send to the SSSD */ - -enum sss_cli_command { -/* null */ - SSS_CLI_NULL = 0x0000, - -/* version */ - SSS_GET_VERSION = 0x0001, - -/* passwd */ - - SSS_NSS_GETPWNAM = 0x0011, - SSS_NSS_GETPWUID = 0x0012, - SSS_NSS_SETPWENT = 0x0013, - SSS_NSS_GETPWENT = 0x0014, - SSS_NSS_ENDPWENT = 0x0015, - - SSS_NSS_GETPWNAM_EX = 0x0019, - SSS_NSS_GETPWUID_EX = 0x001A, - -/* group */ - - SSS_NSS_GETGRNAM = 0x0021, - SSS_NSS_GETGRGID = 0x0022, - SSS_NSS_SETGRENT = 0x0023, - SSS_NSS_GETGRENT = 0x0024, - SSS_NSS_ENDGRENT = 0x0025, - SSS_NSS_INITGR = 0x0026, - - SSS_NSS_GETGRNAM_EX = 0x0029, - SSS_NSS_GETGRGID_EX = 0x002A, - SSS_NSS_INITGR_EX = 0x002E, - -#if 0 -/* aliases */ - - SSS_NSS_GETALIASBYNAME = 0x0031, - SSS_NSS_GETALIASBYPORT = 0x0032, - SSS_NSS_SETALIASENT = 0x0033, - SSS_NSS_GETALIASENT = 0x0034, - SSS_NSS_ENDALIASENT = 0x0035, - -/* ethers */ - - SSS_NSS_GETHOSTTON = 0x0041, - SSS_NSS_GETNTOHOST = 0x0042, - SSS_NSS_SETETHERENT = 0x0043, - SSS_NSS_GETETHERENT = 0x0044, - SSS_NSS_ENDETHERENT = 0x0045, -#endif - -/* hosts */ - - SSS_NSS_GETHOSTBYNAME = 0x0051, - SSS_NSS_GETHOSTBYNAME2 = 0x0052, - SSS_NSS_GETHOSTBYADDR = 0x0053, - SSS_NSS_SETHOSTENT = 0x0054, - SSS_NSS_GETHOSTENT = 0x0055, - SSS_NSS_ENDHOSTENT = 0x0056, - -/* netgroup */ - - SSS_NSS_SETNETGRENT = 0x0061, - SSS_NSS_GETNETGRENT = 0x0062, - SSS_NSS_ENDNETGRENT = 0x0063, - -/* networks */ - - SSS_NSS_GETNETBYNAME = 0x0071, - SSS_NSS_GETNETBYADDR = 0x0072, - SSS_NSS_SETNETENT = 0x0073, - SSS_NSS_GETNETENT = 0x0074, - SSS_NSS_ENDNETENT = 0x0075, - -#if 0 -/* protocols */ - - SSS_NSS_GETPROTOBYNAME = 0x0081, - SSS_NSS_GETPROTOBYNUM = 0x0082, - SSS_NSS_SETPROTOENT = 0x0083, - SSS_NSS_GETPROTOENT = 0x0084, - SSS_NSS_ENDPROTOENT = 0x0085, - -/* rpc */ - - SSS_NSS_GETRPCBYNAME = 0x0091, - SSS_NSS_GETRPCBYNUM = 0x0092, - SSS_NSS_SETRPCENT = 0x0093, - SSS_NSS_GETRPCENT = 0x0094, - SSS_NSS_ENDRPCENT = 0x0095, -#endif - -/* services */ - - SSS_NSS_GETSERVBYNAME = 0x00A1, - SSS_NSS_GETSERVBYPORT = 0x00A2, - SSS_NSS_SETSERVENT = 0x00A3, - SSS_NSS_GETSERVENT = 0x00A4, - SSS_NSS_ENDSERVENT = 0x00A5, - -#if 0 -/* shadow */ - - SSS_NSS_GETSPNAM = 0x00B1, - SSS_NSS_GETSPUID = 0x00B2, - SSS_NSS_SETSPENT = 0x00B3, - SSS_NSS_GETSPENT = 0x00B4, - SSS_NSS_ENDSPENT = 0x00B5, -#endif - -/* SUDO */ - SSS_SUDO_GET_SUDORULES = 0x00C1, - SSS_SUDO_GET_DEFAULTS = 0x00C2, - -/* autofs */ - SSS_AUTOFS_SETAUTOMNTENT = 0x00D1, - SSS_AUTOFS_GETAUTOMNTENT = 0x00D2, - SSS_AUTOFS_GETAUTOMNTBYNAME = 0x00D3, - SSS_AUTOFS_ENDAUTOMNTENT = 0x00D4, - -/* SSH */ - SSS_SSH_GET_USER_PUBKEYS = 0x00E1, - SSS_SSH_GET_HOST_PUBKEYS = 0x00E2, - -/* PAM related calls */ - SSS_PAM_AUTHENTICATE = 0x00F1, /**< see pam_sm_authenticate(3) for - * details. - * - * Additionally we allow sssd to send - * the return code PAM_NEW_AUTHTOK_REQD - * during authentication if the - * authentication was successful but - * the authentication token is expired. - * To meet the standards of libpam we - * return PAM_SUCCESS for - * authentication and set a flag so - * that the account management module - * can return PAM_NEW_AUTHTOK_REQD if - * sssd return success for account - * management. We do this to reduce the - * communication with external servers, - * because there are cases, e.g. - * Kerberos authentication, where the - * information that the password is - * expired is already available during - * authentication. */ - SSS_PAM_SETCRED = 0x00F2, /**< see pam_sm_setcred(3) for - * details */ - SSS_PAM_ACCT_MGMT = 0x00F3, /**< see pam_sm_acct_mgmt(3) for - * details */ - SSS_PAM_OPEN_SESSION = 0x00F4, /**< see pam_sm_open_session(3) for - * details */ - SSS_PAM_CLOSE_SESSION = 0x00F5, /**< see pam_sm_close_session(3) for - *details */ - SSS_PAM_CHAUTHTOK = 0x00F6, /**< second run of the password change - * operation where the PAM_UPDATE_AUTHTOK - * flag is set and the real change may - * happen, see pam_sm_chauthtok(3) for - * details */ - SSS_PAM_CHAUTHTOK_PRELIM = 0x00F7, /**< first run of the password change - * operation where the PAM_PRELIM_CHECK - * flag is set, see pam_sm_chauthtok(3) - * for details */ - SSS_CMD_RENEW = 0x00F8, /**< Renew a credential with a limited - * lifetime, e.g. a Kerberos Ticket - * Granting Ticket (TGT) */ - SSS_PAM_PREAUTH = 0x00F9, /**< Request which can be run before - * an authentication request to find - * out which authentication methods - * are available for the given user. */ - SSS_GSSAPI_INIT = 0x00FA, /**< Initialize GSSAPI authentication. */ - SSS_GSSAPI_SEC_CTX = 0x00FB, /**< Establish GSSAPI security ctx. */ - -/* PAC responder calls */ - SSS_PAC_ADD_PAC_USER = 0x0101, - -/* ID-SID mapping calls */ -SSS_NSS_GETSIDBYNAME = 0x0111, /**< Takes a zero terminated fully qualified - name and returns the zero terminated - string representation of the SID of the - object with the given name. */ -SSS_NSS_GETSIDBYID = 0x0112, /**< Takes an unsigned 32bit integer (POSIX ID) - and returns the zero terminated string - representation of the SID of the object - with the given ID. */ -SSS_NSS_GETNAMEBYSID = 0x0113, /**< Takes the zero terminated string - representation of a SID and returns the - zero terminated fully qualified name of - the related object. */ -SSS_NSS_GETIDBYSID = 0x0114, /**< Takes the zero terminated string - representation of a SID and returns and - returns the POSIX ID of the related object - as unsigned 32bit integer value and - another unsigned 32bit integer value - indicating the type (unknown, user, group, - both) of the object. */ -SSS_NSS_GETORIGBYNAME = 0x0115, /**< Takes a zero terminated fully qualified - name and returns a list of zero - terminated strings with key-value pairs - where the first string is the key and - second the value. Hence the list should - have an even number of strings, if not - the whole list is invalid. */ -SSS_NSS_GETNAMEBYCERT = 0x0116, /**< Takes the zero terminated string - of the base64 encoded DER representation - of a X509 certificate and returns the zero - terminated fully qualified name of the - related object. */ -SSS_NSS_GETLISTBYCERT = 0x0117, /**< Takes the zero terminated string - of the base64 encoded DER representation - of a X509 certificate and returns a list - of zero terminated fully qualified names - of the related objects. */ -SSS_NSS_GETSIDBYUID = 0x0118, /**< Takes an unsigned 32bit integer (POSIX UID) - and return the zero terminated string - representation of the SID of the object - with the given UID. */ -SSS_NSS_GETSIDBYGID = 0x0119, /**< Takes an unsigned 32bit integer (POSIX GID) - and return the zero terminated string - representation of the SID of the object - with the given UID. */ -SSS_NSS_GETORIGBYUSERNAME = 0x011A, /**< Takes a zero terminated fully qualified - user name and returns a list of zero - terminated strings with key-value pairs - where the first string is the key and - second the value. Hence the list should - have an even number of strings, if not - the whole list is invalid. */ -SSS_NSS_GETORIGBYGROUPNAME = 0x011B, /**< Takes a zero terminated fully qualified - group name and returns a list of zero - terminated strings with key-value pairs - where the first string is the key and - second the value. Hence the list should - have an even number of strings, if not - the whole list is invalid. */ -SSS_NSS_GETSIDBYUSERNAME = 0x011C, /**< Takes a zero terminated fully qualified - name and returns the zero terminated - string representation of the SID of the - user with the given name. */ -SSS_NSS_GETSIDBYGROUPNAME = 0x011D, /**< Takes a zero terminated fully qualified - name and returns the zero terminated - string representation of the SID of the - group with the given name. */ - - -/* subid */ - SSS_NSS_GET_SUBID_RANGES = 0x0130, /**< Requests both subuid and subgid ranges - defined for a user. */ -}; - -/** - * @} - */ /* end of group sss_cli_command */ - - -/** - * @defgroup sss_pam SSSD and PAM - * - * SSSD offers authentication and authorization via PAM - * - * The SSSD provides a PAM client modules pam_sss which can be called from the - * PAM stack of the operation system. pam_sss will collect all the data about - * the user from the PAM stack and sends them via a socket to the PAM - * responder of the SSSD. The PAM responder selects the appropriate backend - * and forwards the data via D-BUS to the backend. The backend preforms the - * requested operation and sends the result expressed by a PAM return value - * and optional additional information back to the PAM responder. Finally the - * PAM responder forwards the response back to the client. - * - * @{ - */ - -/** - * @} - */ /* end of group sss_pam */ - -/** - * @defgroup sss_authtok_type Authentication Tokens - * @ingroup sss_pam - * - * To indicate to the components of the SSSD how to handle the authentication - * token the client sends the type of the authentication token to the SSSD. - * - * @{ - */ - -/** The different types of authentication tokens */ - -enum sss_authtok_type { - SSS_AUTHTOK_TYPE_EMPTY = 0x0000, /**< No authentication token - * available */ - SSS_AUTHTOK_TYPE_PASSWORD = 0x0001, /**< Authentication token is a - * password, it may or may no contain - * a trailing \\0 */ - SSS_AUTHTOK_TYPE_CCFILE = 0x0002, /**< Authentication token is a path to - * a Kerberos credential cache file, - * it may or may no contain - * a trailing \\0 */ - SSS_AUTHTOK_TYPE_2FA = 0x0003, /**< Authentication token has two - * factors, they may or may no contain - * a trailing \\0 */ - SSS_AUTHTOK_TYPE_SC_PIN = 0x0004, /**< Authentication token is a Smart - * Card PIN, it may or may no contain - * a trailing \\0 */ - SSS_AUTHTOK_TYPE_SC_KEYPAD = 0x0005, /**< Authentication token indicates - * 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 */ - SSS_AUTHTOK_TYPE_OAUTH2 = 0x0007, /**< Authentication token is a - * oauth2 token for presented - * challenge that is acquired from - * Kerberos. It may or may no - * contain a trailing \\0 */ - SSS_AUTHTOK_TYPE_PASSKEY = 0x0008, /**< Authentication token is a Passkey - * PIN, it may or may not contain - * a trailing \\0 */ - SSS_AUTHTOK_TYPE_PASSKEY_KRB = 0x0009, /**< Authentication token contains - * Passkey data used for Kerberos - * pre-authentication */ - SSS_AUTHTOK_TYPE_PASSKEY_REPLY = 0x0010, /**< Authentication token contains - * Passkey reply data presented as - * a kerberos challenge answer */ - SSS_AUTHTOK_TYPE_PAM_STACKED = 0x0011, /**< Authentication token contains - * either 2FA_SINGLE or PASSWORD - * via PAM use_first_pass */ -}; - -/** - * @} - */ /* end of group sss_authtok_type */ - -#define SSS_START_OF_PAM_REQUEST 0x4d415049 -#define SSS_END_OF_PAM_REQUEST 0x4950414d - -#define PAM_PREAUTH_INDICATOR PUBCONF_PATH"/pam_preauth_available" - -enum pam_item_type { - SSS_PAM_ITEM_EMPTY = 0x0000, - SSS_PAM_ITEM_USER, - SSS_PAM_ITEM_SERVICE, - SSS_PAM_ITEM_TTY, - SSS_PAM_ITEM_RUSER, - SSS_PAM_ITEM_RHOST, - SSS_PAM_ITEM_AUTHTOK, - SSS_PAM_ITEM_NEWAUTHTOK, - SSS_PAM_ITEM_CLI_LOCALE, - SSS_PAM_ITEM_CLI_PID, - SSS_PAM_ITEM_CHILD_PID, - SSS_PAM_ITEM_REQUESTED_DOMAINS, - SSS_PAM_ITEM_FLAGS, -}; - -#define PAM_CLI_FLAGS_USE_FIRST_PASS (1 << 0) -#define PAM_CLI_FLAGS_FORWARD_PASS (1 << 1) -#define PAM_CLI_FLAGS_USE_AUTHTOK (1 << 2) -#define PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER (1 << 3) -#define PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL (1 << 4) -#define PAM_CLI_FLAGS_USE_2FA (1 << 5) -#define PAM_CLI_FLAGS_ALLOW_MISSING_NAME (1 << 6) -#define PAM_CLI_FLAGS_PROMPT_ALWAYS (1 << 7) -#define PAM_CLI_FLAGS_TRY_CERT_AUTH (1 << 8) -#define PAM_CLI_FLAGS_REQUIRE_CERT_AUTH (1 << 9) -#define PAM_CLI_FLAGS_ALLOW_CHAUTHTOK_BY_ROOT (1 << 10) - -#define SSS_NSS_MAX_ENTRIES 256 -#define SSS_NSS_HEADER_SIZE (sizeof(uint32_t) * 4) -struct sss_cli_req_data { - size_t len; - const void *data; -}; - -/* this is in milliseconds, wait up to 300 seconds */ -#define SSS_CLI_SOCKET_TIMEOUT 300000 - -enum sss_status { - SSS_STATUS_TRYAGAIN, - SSS_STATUS_UNAVAIL, - SSS_STATUS_SUCCESS -}; - -/** - * @defgroup sss_pam_cli Responses to the PAM client - * @ingroup sss_pam - * @{ - */ - -/** - * @defgroup response_type Messages from the server - * @ingroup sss_pam_cli - * - * SSSD can send different kind of information back to the client. - * A response from the SSSD can contain 0 or more messages. Each message - * contains a type tag and the size of the message data, both are unsigned - * 32-bit integer values, followed be the message specific data. - * - * If the message is generated by a backend it is send back to the PAM - * responder via a D-BUS message in an array of D-BUS structs. The struct - * consists of a DBUS_TYPE_UINT32 for the tag and a DBUS_TYPE_ARRAY to hold - * the message. - * - * Examples: - * - #SSS_PAM_ENV_ITEM, - uint32_t | uint32_t | uint8_t[4] - ----------|----------|------------ - 0x03 | 0x04 | a=b\\0 - * @{ - */ - -/** Types of different messages */ - -enum response_type { - SSS_PAM_SYSTEM_INFO = 0x01, /**< Message for the system log. - * @param String, zero terminated. */ - SSS_PAM_DOMAIN_NAME, /**< Name of the domain the user belongs too. - * This messages is generated by the PAM responder. - * @param String, zero terminated, with the domain - * name. */ - SSS_PAM_ENV_ITEM, /**< Set and environment variable with pam_putenv(3). - * @param String, zero terminated, of the form - * name=value. See pam_putenv(3) for details. */ - SSS_ENV_ITEM, /**< Set and environment variable with putenv(3). - * @param String, zero terminated, of the form - * name=value. See putenv(3) for details. */ - SSS_ALL_ENV_ITEM, /**< Set and environment variable with putenv(3) and - * pam_putenv(3). - * @param String, zero terminated, of the form - * name=value. See putenv(3) and pam_putenv(3) for - * details. */ - SSS_PAM_USER_INFO, /**< A message which should be displayed to the user. - * @param User info message, see #user_info_type - * for details. */ - SSS_PAM_TEXT_MSG, /**< A plain text message which should be displayed to - * the user. This should only be used in the case where - * it is not possible to use SSS_PAM_USER_INFO. - * @param A zero terminated string. */ - SSS_PAM_OTP_INFO, /**< A message which optionally may contain the name - * of the vendor, the ID of an OTP token and a - * challenge. - * @param Three zero terminated strings, if one of the - * strings is missing the message will contain only - * an empty string (\0) for that component. */ - SSS_PAM_CERT_INFO, /**< A message indicating that Smartcard/certificate - * based authentication is available and contains - * details about the found Smartcard. - * @param user name, zero terminated - * @param token name, zero terminated - * @param PKCS#11 module name, zero terminated - * @param key id, zero terminated */ - SSS_OTP, /**< Indicates that the authtok was a OTP, so don't - * cache it. There is no message. - * @param None. */ - SSS_PASSWORD_PROMPTING, /**< Indicates that password prompting is possible. - * This might be used together with - * SSS_PAM_OTP_INFO to determine the type of - * prompting. There is no message. - * @param None. */ - SSS_CERT_AUTH_PROMPTING, /**< Indicates that on the server side - * Smartcard/certificate based authentication is - * available for the selected account. This might - * be used together with other prompting options - * to determine the type of prompting. - * @param None. */ - 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. */ - SSS_CHILD_KEEP_ALIVE, /**< Indicates that the child process is kept alived - * and further communication must be done with the - * same child. The message is the pid of the child - * process. */ - SSS_PAM_OAUTH2_INFO, /**< A message which contains the oauth2 - * parameters for the user. - * @param Three zero terminated strings: - * - verification_uri - * - verification_uri_complete - * - user_code - */ - SSS_PAM_PASSKEY_INFO, /**< Indicates that passkey authentication is available. - * including a parameter string which dictates whether - * prompting for PIN is needed. - * @param - * - prompt_pin - */ - SSS_PAM_PASSKEY_KRB_INFO, /**< A message containing the passkey parameters - * for the user. The key is the cryptographic challenge - * used as the key to the passkey hash table entry. - * @param - * - user verification (string) - * - key (string) - */ -}; - -/** - * @defgroup user_info_type User info messages - * @ingroup response_type - * - * To achieve a consistent user experience and to facilitate - * internationalization all messages show to the user are generate by the PAM - * client and not by the SSSD server components. To indicate what message the - * client should display to the user SSSD can send a #SSS_PAM_USER_INFO message - * where the data part contains one of the following tags as an unsigned - * 32-bit integer value and optional data. - * - * Examples: - * - #SSS_PAM_USER_INFO_OFFLINE_CHPASS - * uint32_t | uint32_t | uint32_t - * ----------|----------|---------- - * 0x06 | 0x04 | 0x03 - * - * - #SSS_PAM_USER_INFO_CHPASS_ERROR - * uint32_t | uint32_t | uint32_t | uint32_t | uint8_t[3] - * ----------|----------|----------|----------|------------ - * 0x06 | 0x0B | 0x04 | 0x03 | abc - * @{ - */ - -/** Different types of user messages */ - -enum user_info_type { - SSS_PAM_USER_INFO_OFFLINE_AUTH = 0x01, /**< Inform the user that the - * authentication happened offline. - * This message is generated by the - * PAM responder. - * @param Time when the cached - * password will expire in seconds - * since the UNIX Epoch as returned - * by time(2) as int64_t. A value - * of zero indicates that the - * cached password will never - * expire. */ - SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED, /**< Tell the user how low a new - * authentication is delayed. This - * message is generated by the PAM - * responder. - * @param Time when an - * authentication is allowed again - * in seconds since the UNIX Epoch - * as returned by time(2) as - * int64_t. */ - SSS_PAM_USER_INFO_OFFLINE_CHPASS, /**< * Tell the user that it is not - * possible to change the password while - * the system is offline. This message - * is generated by the PAM responder. */ - SSS_PAM_USER_INFO_OTP_CHPASS, /**< Tell the user that he needs to kinit - * or login and logout to get a TGT after - * an OTP password change */ - SSS_PAM_USER_INFO_CHPASS_ERROR, /**< Tell the user that a password change - * failed and optionally give a reason. - * @param Size of the message as unsigned - * 32-bit integer value. A value of 0 - * indicates that no message is following. - * @param String with the specified - * length. */ - - SSS_PAM_USER_INFO_GRACE_LOGIN, /**< Warn the user that the password is - * expired and inform about the remaining - * number of grace logins. - * @param The number of remaining grace - * logins as uint32_t */ - SSS_PAM_USER_INFO_EXPIRE_WARN, /**< Warn the user that the password will - * expire soon. - * @param Number of seconds before the - * user's password will expire. */ - - SSS_PAM_USER_INFO_ACCOUNT_EXPIRED, /**< Tell the user that the account - * has expired and optionally give - * a reason. - * @param Size of the message as - * unsigned 32-bit integer value. A - * value of 0 indicates that no message - * is following. @param String with the - * specified length. */ - - SSS_PAM_USER_INFO_PIN_LOCKED, /**< Tell the user that the PIN is locked */ - SSS_PAM_USER_INFO_NO_KRB_TGT, /**< Tell the user that Kerberos local/offline - auth was performed, therefore no TGT - is granted */ -}; -/** - * @} - */ /* end of group user_info_type */ - -/** - * @} - */ /* end of group response_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_PASSKEY, - 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); -const char *pc_get_passkey_inter_prompt(struct prompt_config *pc); -const char *pc_get_passkey_touch_prompt(struct prompt_config *pc); -errno_t pc_list_add_passkey(struct prompt_config ***pc_list, - const char *inter_prompt, - const char *touch_prompt); -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 -}; - -enum sss_cli_error_codes { - ESSS_SSS_CLI_ERROR_START = 0x1000, - ESSS_BAD_SOCKET, - ESSS_BAD_CRED_MSG, - ESSS_SERVER_NOT_TRUSTED, - ESSS_NO_SOCKET, - ESSS_SOCKET_STAT_ERROR, - - ESS_SSS_CLI_ERROR_MAX -}; - -const char *ssscli_err2string(int err); - -enum sss_status sss_cli_make_request_with_checks(enum sss_cli_command cmd, - struct sss_cli_req_data *rd, - int timeout, - uint8_t **repbuf, size_t *replen, - int *errnop, - const char *socket_name, - bool check_server_creds, - bool allow_custom_errors); - -enum nss_status sss_nss_make_request(enum sss_cli_command cmd, - struct sss_cli_req_data *rd, - uint8_t **repbuf, size_t *replen, - int *errnop); - -enum nss_status sss_nss_make_request_timeout(enum sss_cli_command cmd, - struct sss_cli_req_data *rd, - int timeout, - uint8_t **repbuf, size_t *replen, - int *errnop); - -int sss_pam_make_request(enum sss_cli_command cmd, - struct sss_cli_req_data *rd, - uint8_t **repbuf, size_t *replen, - int *errnop); - -void sss_cli_close_socket(void); - -/* Checks access to the PAC responder and opens the socket, if available. - * Required for processes like krb5_child that need to open the socket - * before dropping privs. - */ -int sss_pac_check_and_open(void); - -int sss_pac_make_request(enum sss_cli_command cmd, - struct sss_cli_req_data *rd, - uint8_t **repbuf, size_t *replen, - int *errnop); - -int sss_pac_make_request_with_lock(enum sss_cli_command cmd, - struct sss_cli_req_data *rd, - uint8_t **repbuf, size_t *replen, - int *errnop); - -#if 0 - -/* GETSPNAM Request: - * - * 0-X: string with name - * - * Replies: - * - * 0-3: 32bit unsigned number of results - * 4-7: 32bit unsigned (reserved/padding) - * For each result: - * 0-7: 64bit unsigned with Date of last change - * 8-15: 64bit unsigned with Min #days between changes - * 16-23: 64bit unsigned with Max #days between changes - * 24-31: 64bit unsigned with #days before pwd expires - * 32-39: 64bit unsigned with #days after pwd expires until account is disabled - * 40-47: 64bit unsigned with expiration date in days since 1970-01-01 - * 48-55: 64bit unsigned (flags/reserved) - * 56-X: sequence of 2, 0 terminated, strings (name, pwd) 64bit padded - */ -#endif - -/* Return strlen(str) or maxlen, whichever is shorter - * Returns EINVAL if str is NULL, EFBIG if str is longer than maxlen - * _len will return the result - */ -errno_t sss_strnlen(const char *str, size_t maxlen, size_t *len); - -void sss_nss_lock(void); -void sss_nss_unlock(void); -void sss_pam_lock(void); -void sss_pam_unlock(void); -void sss_nss_mc_lock(void); -void sss_nss_mc_unlock(void); -void sss_pac_lock(void); -void sss_pac_unlock(void); - -errno_t sss_readrep_copy_string(const char *in, - size_t *offset, - size_t *slen, - size_t *dlen, - char **out, - size_t *size); - -enum pam_gssapi_cmd { - PAM_GSSAPI_GET_NAME, - PAM_GSSAPI_INIT, - PAM_GSSAPI_SENTINEL -}; - -#endif /* _SSSCLI_H */ +/* + SSSD + + Client Interface for NSS and PAM. + + Authors: + Simo Sorce + + Copyright (C) Red Hat, Inc 2007 + + 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 . +*/ + +#ifndef _SSSCLI_H +#define _SSSCLI_H + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "shared/safealign.h" + +#ifndef HAVE_ERRNO_T +#define HAVE_ERRNO_T +typedef int errno_t; +#else +#include +#endif + +#ifndef EOK +#define EOK 0 +#endif + +#ifndef NETDB_INTERNAL +#define NETDB_INTERNAL (-1) +#endif + +#define SSS_NSS_PROTOCOL_VERSION 1 +#define SSS_PAM_PROTOCOL_VERSION 3 +#define SSS_SUDO_PROTOCOL_VERSION 1 +#define SSS_AUTOFS_PROTOCOL_VERSION 1 +#define SSS_SSH_PROTOCOL_VERSION 0 +#define SSS_PAC_PROTOCOL_VERSION 1 + +#ifdef LOGIN_NAME_MAX +#define SSS_NAME_MAX LOGIN_NAME_MAX +#else +#define SSS_NAME_MAX 256 +#endif + +/** + * @defgroup sss_cli_command SSS client commands + * @{ + */ + +/** The allowed commands an SSS client can send to the SSSD */ + +enum sss_cli_command { +/* null */ + SSS_CLI_NULL = 0x0000, + +/* version */ + SSS_GET_VERSION = 0x0001, + +/* passwd */ + + SSS_NSS_GETPWNAM = 0x0011, + SSS_NSS_GETPWUID = 0x0012, + SSS_NSS_SETPWENT = 0x0013, + SSS_NSS_GETPWENT = 0x0014, + SSS_NSS_ENDPWENT = 0x0015, + + SSS_NSS_GETPWNAM_EX = 0x0019, + SSS_NSS_GETPWUID_EX = 0x001A, + +/* group */ + + SSS_NSS_GETGRNAM = 0x0021, + SSS_NSS_GETGRGID = 0x0022, + SSS_NSS_SETGRENT = 0x0023, + SSS_NSS_GETGRENT = 0x0024, + SSS_NSS_ENDGRENT = 0x0025, + SSS_NSS_INITGR = 0x0026, + + SSS_NSS_GETGRNAM_EX = 0x0029, + SSS_NSS_GETGRGID_EX = 0x002A, + SSS_NSS_INITGR_EX = 0x002E, + +#if 0 +/* aliases */ + + SSS_NSS_GETALIASBYNAME = 0x0031, + SSS_NSS_GETALIASBYPORT = 0x0032, + SSS_NSS_SETALIASENT = 0x0033, + SSS_NSS_GETALIASENT = 0x0034, + SSS_NSS_ENDALIASENT = 0x0035, + +/* ethers */ + + SSS_NSS_GETHOSTTON = 0x0041, + SSS_NSS_GETNTOHOST = 0x0042, + SSS_NSS_SETETHERENT = 0x0043, + SSS_NSS_GETETHERENT = 0x0044, + SSS_NSS_ENDETHERENT = 0x0045, +#endif + +/* hosts */ + + SSS_NSS_GETHOSTBYNAME = 0x0051, + SSS_NSS_GETHOSTBYNAME2 = 0x0052, + SSS_NSS_GETHOSTBYADDR = 0x0053, + SSS_NSS_SETHOSTENT = 0x0054, + SSS_NSS_GETHOSTENT = 0x0055, + SSS_NSS_ENDHOSTENT = 0x0056, + +/* netgroup */ + + SSS_NSS_SETNETGRENT = 0x0061, + SSS_NSS_GETNETGRENT = 0x0062, + SSS_NSS_ENDNETGRENT = 0x0063, + +/* networks */ + + SSS_NSS_GETNETBYNAME = 0x0071, + SSS_NSS_GETNETBYADDR = 0x0072, + SSS_NSS_SETNETENT = 0x0073, + SSS_NSS_GETNETENT = 0x0074, + SSS_NSS_ENDNETENT = 0x0075, + +#if 0 +/* protocols */ + + SSS_NSS_GETPROTOBYNAME = 0x0081, + SSS_NSS_GETPROTOBYNUM = 0x0082, + SSS_NSS_SETPROTOENT = 0x0083, + SSS_NSS_GETPROTOENT = 0x0084, + SSS_NSS_ENDPROTOENT = 0x0085, + +/* rpc */ + + SSS_NSS_GETRPCBYNAME = 0x0091, + SSS_NSS_GETRPCBYNUM = 0x0092, + SSS_NSS_SETRPCENT = 0x0093, + SSS_NSS_GETRPCENT = 0x0094, + SSS_NSS_ENDRPCENT = 0x0095, +#endif + +/* services */ + + SSS_NSS_GETSERVBYNAME = 0x00A1, + SSS_NSS_GETSERVBYPORT = 0x00A2, + SSS_NSS_SETSERVENT = 0x00A3, + SSS_NSS_GETSERVENT = 0x00A4, + SSS_NSS_ENDSERVENT = 0x00A5, + +#if 0 +/* shadow */ + + SSS_NSS_GETSPNAM = 0x00B1, + SSS_NSS_GETSPUID = 0x00B2, + SSS_NSS_SETSPENT = 0x00B3, + SSS_NSS_GETSPENT = 0x00B4, + SSS_NSS_ENDSPENT = 0x00B5, +#endif + +/* SUDO */ + SSS_SUDO_GET_SUDORULES = 0x00C1, + SSS_SUDO_GET_DEFAULTS = 0x00C2, + +/* autofs */ + SSS_AUTOFS_SETAUTOMNTENT = 0x00D1, + SSS_AUTOFS_GETAUTOMNTENT = 0x00D2, + SSS_AUTOFS_GETAUTOMNTBYNAME = 0x00D3, + SSS_AUTOFS_ENDAUTOMNTENT = 0x00D4, + +/* SSH */ + SSS_SSH_GET_USER_PUBKEYS = 0x00E1, + SSS_SSH_GET_HOST_PUBKEYS = 0x00E2, + +/* PAM related calls */ + SSS_PAM_AUTHENTICATE = 0x00F1, /**< see pam_sm_authenticate(3) for + * details. + * + * Additionally we allow sssd to send + * the return code PAM_NEW_AUTHTOK_REQD + * during authentication if the + * authentication was successful but + * the authentication token is expired. + * To meet the standards of libpam we + * return PAM_SUCCESS for + * authentication and set a flag so + * that the account management module + * can return PAM_NEW_AUTHTOK_REQD if + * sssd return success for account + * management. We do this to reduce the + * communication with external servers, + * because there are cases, e.g. + * Kerberos authentication, where the + * information that the password is + * expired is already available during + * authentication. */ + SSS_PAM_SETCRED = 0x00F2, /**< see pam_sm_setcred(3) for + * details */ + SSS_PAM_ACCT_MGMT = 0x00F3, /**< see pam_sm_acct_mgmt(3) for + * details */ + SSS_PAM_OPEN_SESSION = 0x00F4, /**< see pam_sm_open_session(3) for + * details */ + SSS_PAM_CLOSE_SESSION = 0x00F5, /**< see pam_sm_close_session(3) for + *details */ + SSS_PAM_CHAUTHTOK = 0x00F6, /**< second run of the password change + * operation where the PAM_UPDATE_AUTHTOK + * flag is set and the real change may + * happen, see pam_sm_chauthtok(3) for + * details */ + SSS_PAM_CHAUTHTOK_PRELIM = 0x00F7, /**< first run of the password change + * operation where the PAM_PRELIM_CHECK + * flag is set, see pam_sm_chauthtok(3) + * for details */ + SSS_CMD_RENEW = 0x00F8, /**< Renew a credential with a limited + * lifetime, e.g. a Kerberos Ticket + * Granting Ticket (TGT) */ + SSS_PAM_PREAUTH = 0x00F9, /**< Request which can be run before + * an authentication request to find + * out which authentication methods + * are available for the given user. */ + SSS_GSSAPI_INIT = 0x00FA, /**< Initialize GSSAPI authentication. */ + SSS_GSSAPI_SEC_CTX = 0x00FB, /**< Establish GSSAPI security ctx. */ + SSS_PAM_PASSKEY_PREAUTH = 0x00FC, /**< could be called to request device + * information of device supporting + * passkey credential. + * typically, the command returns + * SSS_PAM_PASSKEY_DEVINFO */ +/* PAC responder calls */ + SSS_PAC_ADD_PAC_USER = 0x0101, + +/* ID-SID mapping calls */ +SSS_NSS_GETSIDBYNAME = 0x0111, /**< Takes a zero terminated fully qualified + name and returns the zero terminated + string representation of the SID of the + object with the given name. */ +SSS_NSS_GETSIDBYID = 0x0112, /**< Takes an unsigned 32bit integer (POSIX ID) + and returns the zero terminated string + representation of the SID of the object + with the given ID. */ +SSS_NSS_GETNAMEBYSID = 0x0113, /**< Takes the zero terminated string + representation of a SID and returns the + zero terminated fully qualified name of + the related object. */ +SSS_NSS_GETIDBYSID = 0x0114, /**< Takes the zero terminated string + representation of a SID and returns and + returns the POSIX ID of the related object + as unsigned 32bit integer value and + another unsigned 32bit integer value + indicating the type (unknown, user, group, + both) of the object. */ +SSS_NSS_GETORIGBYNAME = 0x0115, /**< Takes a zero terminated fully qualified + name and returns a list of zero + terminated strings with key-value pairs + where the first string is the key and + second the value. Hence the list should + have an even number of strings, if not + the whole list is invalid. */ +SSS_NSS_GETNAMEBYCERT = 0x0116, /**< Takes the zero terminated string + of the base64 encoded DER representation + of a X509 certificate and returns the zero + terminated fully qualified name of the + related object. */ +SSS_NSS_GETLISTBYCERT = 0x0117, /**< Takes the zero terminated string + of the base64 encoded DER representation + of a X509 certificate and returns a list + of zero terminated fully qualified names + of the related objects. */ +SSS_NSS_GETSIDBYUID = 0x0118, /**< Takes an unsigned 32bit integer (POSIX UID) + and return the zero terminated string + representation of the SID of the object + with the given UID. */ +SSS_NSS_GETSIDBYGID = 0x0119, /**< Takes an unsigned 32bit integer (POSIX GID) + and return the zero terminated string + representation of the SID of the object + with the given UID. */ +SSS_NSS_GETORIGBYUSERNAME = 0x011A, /**< Takes a zero terminated fully qualified + user name and returns a list of zero + terminated strings with key-value pairs + where the first string is the key and + second the value. Hence the list should + have an even number of strings, if not + the whole list is invalid. */ +SSS_NSS_GETORIGBYGROUPNAME = 0x011B, /**< Takes a zero terminated fully qualified + group name and returns a list of zero + terminated strings with key-value pairs + where the first string is the key and + second the value. Hence the list should + have an even number of strings, if not + the whole list is invalid. */ +SSS_NSS_GETSIDBYUSERNAME = 0x011C, /**< Takes a zero terminated fully qualified + name and returns the zero terminated + string representation of the SID of the + user with the given name. */ +SSS_NSS_GETSIDBYGROUPNAME = 0x011D, /**< Takes a zero terminated fully qualified + name and returns the zero terminated + string representation of the SID of the + group with the given name. */ + + +/* subid */ + SSS_NSS_GET_SUBID_RANGES = 0x0130, /**< Requests both subuid and subgid ranges + defined for a user. */ +}; + +/** + * @} + */ /* end of group sss_cli_command */ + + +/** + * @defgroup sss_pam SSSD and PAM + * + * SSSD offers authentication and authorization via PAM + * + * The SSSD provides a PAM client modules pam_sss which can be called from the + * PAM stack of the operation system. pam_sss will collect all the data about + * the user from the PAM stack and sends them via a socket to the PAM + * responder of the SSSD. The PAM responder selects the appropriate backend + * and forwards the data via D-BUS to the backend. The backend preforms the + * requested operation and sends the result expressed by a PAM return value + * and optional additional information back to the PAM responder. Finally the + * PAM responder forwards the response back to the client. + * + * @{ + */ + +/** + * @} + */ /* end of group sss_pam */ + +/** + * @defgroup sss_authtok_type Authentication Tokens + * @ingroup sss_pam + * + * To indicate to the components of the SSSD how to handle the authentication + * token the client sends the type of the authentication token to the SSSD. + * + * @{ + */ + +/** The different types of authentication tokens */ + +enum sss_authtok_type { + SSS_AUTHTOK_TYPE_EMPTY = 0x0000, /**< No authentication token + * available */ + SSS_AUTHTOK_TYPE_PASSWORD = 0x0001, /**< Authentication token is a + * password, it may or may no contain + * a trailing \\0 */ + SSS_AUTHTOK_TYPE_CCFILE = 0x0002, /**< Authentication token is a path to + * a Kerberos credential cache file, + * it may or may no contain + * a trailing \\0 */ + SSS_AUTHTOK_TYPE_2FA = 0x0003, /**< Authentication token has two + * factors, they may or may no contain + * a trailing \\0 */ + SSS_AUTHTOK_TYPE_SC_PIN = 0x0004, /**< Authentication token is a Smart + * Card PIN, it may or may no contain + * a trailing \\0 */ + SSS_AUTHTOK_TYPE_SC_KEYPAD = 0x0005, /**< Authentication token indicates + * 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 */ + SSS_AUTHTOK_TYPE_OAUTH2 = 0x0007, /**< Authentication token is a + * oauth2 token for presented + * challenge that is acquired from + * Kerberos. It may or may no + * contain a trailing \\0 */ + SSS_AUTHTOK_TYPE_PASSKEY = 0x0008, /**< Authentication token is a Passkey + * PIN, it may or may not contain + * a trailing \\0 */ + SSS_AUTHTOK_TYPE_PASSKEY_KRB = 0x0009, /**< Authentication token contains + * Passkey data used for Kerberos + * pre-authentication */ + SSS_AUTHTOK_TYPE_PASSKEY_REPLY = 0x0010, /**< Authentication token contains + * Passkey reply data presented as + * a kerberos challenge answer */ + SSS_AUTHTOK_TYPE_PAM_STACKED = 0x0011, /**< Authentication token contains + * either 2FA_SINGLE or PASSWORD + * via PAM use_first_pass */ +}; + +/** + * @} + */ /* end of group sss_authtok_type */ + +#define SSS_START_OF_PAM_REQUEST 0x4d415049 +#define SSS_END_OF_PAM_REQUEST 0x4950414d + +#define PAM_PREAUTH_INDICATOR PUBCONF_PATH"/pam_preauth_available" + +enum pam_item_type { + SSS_PAM_ITEM_EMPTY = 0x0000, + SSS_PAM_ITEM_USER, + SSS_PAM_ITEM_SERVICE, + SSS_PAM_ITEM_TTY, + SSS_PAM_ITEM_RUSER, + SSS_PAM_ITEM_RHOST, + SSS_PAM_ITEM_AUTHTOK, + SSS_PAM_ITEM_NEWAUTHTOK, + SSS_PAM_ITEM_CLI_LOCALE, + SSS_PAM_ITEM_CLI_PID, + SSS_PAM_ITEM_CHILD_PID, + SSS_PAM_ITEM_REQUESTED_DOMAINS, + SSS_PAM_ITEM_FLAGS, +}; + +#define PAM_CLI_FLAGS_USE_FIRST_PASS (1 << 0) +#define PAM_CLI_FLAGS_FORWARD_PASS (1 << 1) +#define PAM_CLI_FLAGS_USE_AUTHTOK (1 << 2) +#define PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER (1 << 3) +#define PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL (1 << 4) +#define PAM_CLI_FLAGS_USE_2FA (1 << 5) +#define PAM_CLI_FLAGS_ALLOW_MISSING_NAME (1 << 6) +#define PAM_CLI_FLAGS_PROMPT_ALWAYS (1 << 7) +#define PAM_CLI_FLAGS_TRY_CERT_AUTH (1 << 8) +#define PAM_CLI_FLAGS_REQUIRE_CERT_AUTH (1 << 9) +#define PAM_CLI_FLAGS_ALLOW_CHAUTHTOK_BY_ROOT (1 << 10) +#define PAM_CLI_FLAGS_REQUIRE_PASSKEY_CACHED_DEVINFO (1 << 11) + +#define SSS_NSS_MAX_ENTRIES 256 +#define SSS_NSS_HEADER_SIZE (sizeof(uint32_t) * 4) +struct sss_cli_req_data { + size_t len; + const void *data; +}; + +/* this is in milliseconds, wait up to 300 seconds */ +#define SSS_CLI_SOCKET_TIMEOUT 300000 + +enum sss_status { + SSS_STATUS_TRYAGAIN, + SSS_STATUS_UNAVAIL, + SSS_STATUS_SUCCESS +}; + +/** + * @defgroup sss_pam_cli Responses to the PAM client + * @ingroup sss_pam + * @{ + */ + +/** + * @defgroup response_type Messages from the server + * @ingroup sss_pam_cli + * + * SSSD can send different kind of information back to the client. + * A response from the SSSD can contain 0 or more messages. Each message + * contains a type tag and the size of the message data, both are unsigned + * 32-bit integer values, followed be the message specific data. + * + * If the message is generated by a backend it is send back to the PAM + * responder via a D-BUS message in an array of D-BUS structs. The struct + * consists of a DBUS_TYPE_UINT32 for the tag and a DBUS_TYPE_ARRAY to hold + * the message. + * + * Examples: + * - #SSS_PAM_ENV_ITEM, + uint32_t | uint32_t | uint8_t[4] + ----------|----------|------------ + 0x03 | 0x04 | a=b\\0 + * @{ + */ + +/** Types of different messages */ + +enum response_type { + SSS_PAM_SYSTEM_INFO = 0x01, /**< Message for the system log. + * @param String, zero terminated. */ + SSS_PAM_DOMAIN_NAME, /**< Name of the domain the user belongs too. + * This messages is generated by the PAM responder. + * @param String, zero terminated, with the domain + * name. */ + SSS_PAM_ENV_ITEM, /**< Set and environment variable with pam_putenv(3). + * @param String, zero terminated, of the form + * name=value. See pam_putenv(3) for details. */ + SSS_ENV_ITEM, /**< Set and environment variable with putenv(3). + * @param String, zero terminated, of the form + * name=value. See putenv(3) for details. */ + SSS_ALL_ENV_ITEM, /**< Set and environment variable with putenv(3) and + * pam_putenv(3). + * @param String, zero terminated, of the form + * name=value. See putenv(3) and pam_putenv(3) for + * details. */ + SSS_PAM_USER_INFO, /**< A message which should be displayed to the user. + * @param User info message, see #user_info_type + * for details. */ + SSS_PAM_TEXT_MSG, /**< A plain text message which should be displayed to + * the user. This should only be used in the case where + * it is not possible to use SSS_PAM_USER_INFO. + * @param A zero terminated string. */ + SSS_PAM_OTP_INFO, /**< A message which optionally may contain the name + * of the vendor, the ID of an OTP token and a + * challenge. + * @param Three zero terminated strings, if one of the + * strings is missing the message will contain only + * an empty string (\0) for that component. */ + SSS_PAM_CERT_INFO, /**< A message indicating that Smartcard/certificate + * based authentication is available and contains + * details about the found Smartcard. + * @param user name, zero terminated + * @param token name, zero terminated + * @param PKCS#11 module name, zero terminated + * @param key id, zero terminated */ + SSS_OTP, /**< Indicates that the authtok was a OTP, so don't + * cache it. There is no message. + * @param None. */ + SSS_PASSWORD_PROMPTING, /**< Indicates that password prompting is possible. + * This might be used together with + * SSS_PAM_OTP_INFO to determine the type of + * prompting. There is no message. + * @param None. */ + SSS_CERT_AUTH_PROMPTING, /**< Indicates that on the server side + * Smartcard/certificate based authentication is + * available for the selected account. This might + * be used together with other prompting options + * to determine the type of prompting. + * @param None. */ + 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. */ + SSS_CHILD_KEEP_ALIVE, /**< Indicates that the child process is kept alived + * and further communication must be done with the + * same child. The message is the pid of the child + * process. */ + SSS_PAM_OAUTH2_INFO, /**< A message which contains the oauth2 + * parameters for the user. + * @param Three zero terminated strings: + * - verification_uri + * - verification_uri_complete + * - user_code + */ + SSS_PAM_PASSKEY_INFO, /**< Indicates that passkey authentication is available. + * including a parameter string which dictates whether + * prompting for PIN is needed. + * @param + * - prompt_pin + */ + SSS_PAM_PASSKEY_DEVINFO, /**< Indicates a passkey device information + * including a parameter string + * @param + * - device information string + */ + SSS_PAM_PASSKEY_KRB_INFO, /**< A message containing the passkey parameters + * for the user. The key is the cryptographic challenge + * used as the key to the passkey hash table entry. + * @param + * - user verification (string) + * - key (string) + */ +}; + +/** + * @defgroup user_info_type User info messages + * @ingroup response_type + * + * To achieve a consistent user experience and to facilitate + * internationalization all messages show to the user are generate by the PAM + * client and not by the SSSD server components. To indicate what message the + * client should display to the user SSSD can send a #SSS_PAM_USER_INFO message + * where the data part contains one of the following tags as an unsigned + * 32-bit integer value and optional data. + * + * Examples: + * - #SSS_PAM_USER_INFO_OFFLINE_CHPASS + * uint32_t | uint32_t | uint32_t + * ----------|----------|---------- + * 0x06 | 0x04 | 0x03 + * + * - #SSS_PAM_USER_INFO_CHPASS_ERROR + * uint32_t | uint32_t | uint32_t | uint32_t | uint8_t[3] + * ----------|----------|----------|----------|------------ + * 0x06 | 0x0B | 0x04 | 0x03 | abc + * @{ + */ + +/** Different types of user messages */ + +enum user_info_type { + SSS_PAM_USER_INFO_OFFLINE_AUTH = 0x01, /**< Inform the user that the + * authentication happened offline. + * This message is generated by the + * PAM responder. + * @param Time when the cached + * password will expire in seconds + * since the UNIX Epoch as returned + * by time(2) as int64_t. A value + * of zero indicates that the + * cached password will never + * expire. */ + SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED, /**< Tell the user how low a new + * authentication is delayed. This + * message is generated by the PAM + * responder. + * @param Time when an + * authentication is allowed again + * in seconds since the UNIX Epoch + * as returned by time(2) as + * int64_t. */ + SSS_PAM_USER_INFO_OFFLINE_CHPASS, /**< * Tell the user that it is not + * possible to change the password while + * the system is offline. This message + * is generated by the PAM responder. */ + SSS_PAM_USER_INFO_OTP_CHPASS, /**< Tell the user that he needs to kinit + * or login and logout to get a TGT after + * an OTP password change */ + SSS_PAM_USER_INFO_CHPASS_ERROR, /**< Tell the user that a password change + * failed and optionally give a reason. + * @param Size of the message as unsigned + * 32-bit integer value. A value of 0 + * indicates that no message is following. + * @param String with the specified + * length. */ + + SSS_PAM_USER_INFO_GRACE_LOGIN, /**< Warn the user that the password is + * expired and inform about the remaining + * number of grace logins. + * @param The number of remaining grace + * logins as uint32_t */ + SSS_PAM_USER_INFO_EXPIRE_WARN, /**< Warn the user that the password will + * expire soon. + * @param Number of seconds before the + * user's password will expire. */ + + SSS_PAM_USER_INFO_ACCOUNT_EXPIRED, /**< Tell the user that the account + * has expired and optionally give + * a reason. + * @param Size of the message as + * unsigned 32-bit integer value. A + * value of 0 indicates that no message + * is following. @param String with the + * specified length. */ + + SSS_PAM_USER_INFO_PIN_LOCKED, /**< Tell the user that the PIN is locked */ + SSS_PAM_USER_INFO_NO_KRB_TGT, /**< Tell the user that Kerberos local/offline + auth was performed, therefore no TGT + is granted */ +}; +/** + * @} + */ /* end of group user_info_type */ + +/** + * @} + */ /* end of group response_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_PASSKEY, + 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); +const char *pc_get_passkey_inter_prompt(struct prompt_config *pc); +const char *pc_get_passkey_touch_prompt(struct prompt_config *pc); +errno_t pc_list_add_passkey(struct prompt_config ***pc_list, + const char *inter_prompt, + const char *touch_prompt); +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 +}; + +enum sss_cli_error_codes { + ESSS_SSS_CLI_ERROR_START = 0x1000, + ESSS_BAD_SOCKET, + ESSS_BAD_CRED_MSG, + ESSS_SERVER_NOT_TRUSTED, + ESSS_NO_SOCKET, + ESSS_SOCKET_STAT_ERROR, + + ESS_SSS_CLI_ERROR_MAX +}; + +const char *ssscli_err2string(int err); + +enum sss_status sss_cli_make_request_with_checks(enum sss_cli_command cmd, + struct sss_cli_req_data *rd, + int timeout, + uint8_t **repbuf, size_t *replen, + int *errnop, + const char *socket_name, + bool check_server_creds, + bool allow_custom_errors); + +enum nss_status sss_nss_make_request(enum sss_cli_command cmd, + struct sss_cli_req_data *rd, + uint8_t **repbuf, size_t *replen, + int *errnop); + +enum nss_status sss_nss_make_request_timeout(enum sss_cli_command cmd, + struct sss_cli_req_data *rd, + int timeout, + uint8_t **repbuf, size_t *replen, + int *errnop); + +int sss_pam_make_request(enum sss_cli_command cmd, + struct sss_cli_req_data *rd, + uint8_t **repbuf, size_t *replen, + int *errnop); + +void sss_cli_close_socket(void); + +/* Checks access to the PAC responder and opens the socket, if available. + * Required for processes like krb5_child that need to open the socket + * before dropping privs. + */ +int sss_pac_check_and_open(void); + +int sss_pac_make_request(enum sss_cli_command cmd, + struct sss_cli_req_data *rd, + uint8_t **repbuf, size_t *replen, + int *errnop); + +int sss_pac_make_request_with_lock(enum sss_cli_command cmd, + struct sss_cli_req_data *rd, + uint8_t **repbuf, size_t *replen, + int *errnop); + +#if 0 + +/* GETSPNAM Request: + * + * 0-X: string with name + * + * Replies: + * + * 0-3: 32bit unsigned number of results + * 4-7: 32bit unsigned (reserved/padding) + * For each result: + * 0-7: 64bit unsigned with Date of last change + * 8-15: 64bit unsigned with Min #days between changes + * 16-23: 64bit unsigned with Max #days between changes + * 24-31: 64bit unsigned with #days before pwd expires + * 32-39: 64bit unsigned with #days after pwd expires until account is disabled + * 40-47: 64bit unsigned with expiration date in days since 1970-01-01 + * 48-55: 64bit unsigned (flags/reserved) + * 56-X: sequence of 2, 0 terminated, strings (name, pwd) 64bit padded + */ +#endif + +/* Return strlen(str) or maxlen, whichever is shorter + * Returns EINVAL if str is NULL, EFBIG if str is longer than maxlen + * _len will return the result + */ +errno_t sss_strnlen(const char *str, size_t maxlen, size_t *len); + +void sss_nss_lock(void); +void sss_nss_unlock(void); +void sss_pam_lock(void); +void sss_pam_unlock(void); +void sss_nss_mc_lock(void); +void sss_nss_mc_unlock(void); +void sss_pac_lock(void); +void sss_pac_unlock(void); + +errno_t sss_readrep_copy_string(const char *in, + size_t *offset, + size_t *slen, + size_t *dlen, + char **out, + size_t *size); + +enum pam_gssapi_cmd { + PAM_GSSAPI_GET_NAME, + PAM_GSSAPI_INIT, + PAM_GSSAPI_SENTINEL +}; + +#endif /* _SSSCLI_H */ From 7e1a8ad2162b31cb02658e62e3480e8d832a4dcd Mon Sep 17 00:00:00 2001 From: Philippe Smadja Date: Fri, 13 Jun 2025 17:28:31 +0200 Subject: [PATCH 3/8] fix file formatting --- src/passkey_child/passkey_child.c | 224 +- src/passkey_child/passkey_child.h | 2 +- src/passkey_child/passkey_child_assert.c | 1129 ++- src/passkey_child/passkey_child_common.c | 1946 ++--- src/passkey_child/passkey_child_credentials.c | 1380 ++-- src/passkey_child/passkey_child_devices.c | 492 +- src/responder/pam/pamsrv_cmd.c | 6450 ++++++++-------- src/responder/pam/pamsrv_passkey.c | 3757 +++++---- src/responder/pam/pamsrv_passkey.h | 204 +- src/sss_client/pam_message.h | 162 +- src/sss_client/pam_sss.c | 6869 ++++++++--------- src/sss_client/sss_cli.h | 1648 ++-- 12 files changed, 12126 insertions(+), 12137 deletions(-) diff --git a/src/passkey_child/passkey_child.c b/src/passkey_child/passkey_child.c index 3ca35dd02a7..dda20b47cd9 100644 --- a/src/passkey_child/passkey_child.c +++ b/src/passkey_child/passkey_child.c @@ -1,112 +1,112 @@ -/* - SSSD - - Helper child to commmunicate with passkey devices - - Authors: - Iker Pedrosa - - Copyright (C) 2022 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 . -*/ - -#include -#include - -#include "util/debug.h" -#include "util/util.h" - -#include "passkey_child.h" - -int main(int argc, const char *argv[]) -{ - TALLOC_CTX *main_ctx = NULL; - struct passkey_data data; - int init_flags = 0; - errno_t ret = EOK; - - main_ctx = talloc_new(NULL); - if (main_ctx == NULL) { - ERROR("talloc_new() failed.\n"); - talloc_free(discard_const(debug_prg_name)); - ret = ENOMEM; - goto done; - } - - ret = parse_arguments(main_ctx, argc, argv, &data); - if (ret != EOK) { - ERROR("Error parsing argument(s).\n"); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "passkey_child started.\n"); - talloc_steal(main_ctx, debug_prg_name); - - ret = check_arguments(&data); - if (ret != EOK) { - ERROR("Invalid argument(s).\n"); - goto done; - } - - init_flags = (int)data.debug_libfido2 | FIDO_DISABLE_U2F_FALLBACK; - fido_init(init_flags); - - if (data.action == ACTION_REGISTER) { - ret = register_key(&data); - if (ret != EOK) { - ERROR("Error registering key.\n"); - goto done; - } - } else if (data.action == ACTION_AUTHENTICATE) { - ret = authenticate(&data); - if (ret == EOK) { - PRINT("Authentication success.\n"); - goto done; - } else { - ERROR("Authentication error.\n"); - goto done; - } - } else if (data.action == ACTION_GET_ASSERT) { - ret = get_assert_data(&data); - if (ret != EOK) { - ERROR("Error getting assertion data.\n"); - goto done; - } - } else if (data.action == ACTION_GET_DEVINFO) { - ret = get_device_info(&data); - if (ret != EOK) { - ERROR("Error getting device information.\n"); - goto done; - } - } else if (data.action == ACTION_VERIFY_ASSERT) { - ret = verify_assert_data(&data); - if (ret == EOK) { - PRINT("Verification success.\n"); - goto done; - } else { - ERROR("Verification error.\n"); - goto done; - } - } - -done: - talloc_free(main_ctx); - - if (ret != EOK) { - return EXIT_FAILURE; - } else { - return EXIT_SUCCESS; - } -} +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa + + Copyright (C) 2022 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 . +*/ + +#include +#include + +#include "util/debug.h" +#include "util/util.h" + +#include "passkey_child.h" + +int main(int argc, const char *argv[]) +{ + TALLOC_CTX *main_ctx = NULL; + struct passkey_data data; + int init_flags = 0; + errno_t ret = EOK; + + main_ctx = talloc_new(NULL); + if (main_ctx == NULL) { + ERROR("talloc_new() failed.\n"); + talloc_free(discard_const(debug_prg_name)); + ret = ENOMEM; + goto done; + } + + ret = parse_arguments(main_ctx, argc, argv, &data); + if (ret != EOK) { + ERROR("Error parsing argument(s).\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "passkey_child started.\n"); + talloc_steal(main_ctx, debug_prg_name); + + ret = check_arguments(&data); + if (ret != EOK) { + ERROR("Invalid argument(s).\n"); + goto done; + } + + init_flags = (int)data.debug_libfido2 | FIDO_DISABLE_U2F_FALLBACK; + fido_init(init_flags); + + if (data.action == ACTION_REGISTER) { + ret = register_key(&data); + if (ret != EOK) { + ERROR("Error registering key.\n"); + goto done; + } + } else if (data.action == ACTION_AUTHENTICATE) { + ret = authenticate(&data); + if (ret == EOK) { + PRINT("Authentication success.\n"); + goto done; + } else { + ERROR("Authentication error.\n"); + goto done; + } + } else if (data.action == ACTION_GET_ASSERT) { + ret = get_assert_data(&data); + if (ret != EOK) { + ERROR("Error getting assertion data.\n"); + goto done; + } + } else if (data.action == ACTION_GET_DEVINFO) { + ret = get_device_info(&data); + if (ret != EOK) { + ERROR("Error getting device information.\n"); + goto done; + } + } else if (data.action == ACTION_VERIFY_ASSERT) { + ret = verify_assert_data(&data); + if (ret == EOK) { + PRINT("Verification success.\n"); + goto done; + } else { + ERROR("Verification error.\n"); + goto done; + } + } + +done: + talloc_free(main_ctx); + + if (ret != EOK) { + return EXIT_FAILURE; + } else { + return EXIT_SUCCESS; + } +} diff --git a/src/passkey_child/passkey_child.h b/src/passkey_child/passkey_child.h index e0d35397770..278077abc94 100644 --- a/src/passkey_child/passkey_child.h +++ b/src/passkey_child/passkey_child.h @@ -549,7 +549,7 @@ get_assert_data(struct passkey_data *data); * @param[in] data passkey data * * @return 0 if a device is available - * create file /var/run/passkey-pinonly or passkey-pinuv + * create file /var/run/passkey-pinonly or passkey-pinuv * error code otherwise. */ errno_t diff --git a/src/passkey_child/passkey_child_assert.c b/src/passkey_child/passkey_child_assert.c index 55056c61ab5..92ef894f380 100644 --- a/src/passkey_child/passkey_child_assert.c +++ b/src/passkey_child/passkey_child_assert.c @@ -1,566 +1,563 @@ -/* - SSSD - - Helper child to commmunicate with passkey devices - - Authors: - Iker Pedrosa - - Copyright (C) 2022 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 . -*/ - -#include -#include -#include -#include -#include -#include -#include - -#include "util/crypto/sss_crypto.h" -#include "util/debug.h" -#include "util/util.h" - -#include "passkey_child.h" - -errno_t -set_assert_client_data_hash(const struct passkey_data *data, - fido_assert_t *_assert) -{ - TALLOC_CTX *tmp_ctx = NULL; - unsigned char cdh[32]; - unsigned char *crypto_challenge = NULL; - size_t crypto_challenge_len; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - return ENOMEM; - } - - if (data->action == ACTION_AUTHENTICATE) { - ret = sss_generate_csprng_buffer(cdh, sizeof(cdh)); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "sss_generate_csprng_buffer failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - ret = fido_assert_set_clientdata_hash(_assert, cdh, sizeof(cdh)); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_clientdata_hash failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - } else if (data->action == ACTION_GET_ASSERT) { - crypto_challenge = sss_base64_decode(tmp_ctx, data->crypto_challenge, - &crypto_challenge_len); - if (crypto_challenge == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "failed to decode client data hash.\n"); - ret = ENOMEM; - goto done; - } - - if (crypto_challenge_len != 32) { - DEBUG(SSSDBG_OP_FAILURE, - "cryptographic-challenge length [%ld] must be 32.\n", - crypto_challenge_len); - ret = EINVAL; - goto done; - } - - ret = fido_assert_set_clientdata_hash(_assert, crypto_challenge, - crypto_challenge_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_clientdata_hash failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - } - else { - memset(cdh, 0, sizeof(cdh)); - ret = fido_assert_set_clientdata_hash(_assert, cdh, sizeof(cdh)); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_clientdata_hash failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - } - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -set_assert_options(fido_opt_t up, fido_opt_t uv, fido_assert_t *_assert) -{ - errno_t ret; - - ret = fido_assert_set_up(_assert, up); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_up failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - ret = fido_assert_set_uv(_assert, uv); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_uv failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - -done: - return ret; -} - -errno_t -get_assert_auth_data_signature(TALLOC_CTX *mem_ctx, fido_assert_t *assert, - const char **_auth_data, - const char **_signature) -{ - TALLOC_CTX *tmp_ctx = NULL; - const unsigned char *auth_data; - const unsigned char *signature; - const char *b64_auth_data; - const char *b64_signature; - size_t auth_data_len; - size_t signature_len; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - return ENOMEM; - } - - auth_data = fido_assert_authdata_ptr(assert, 0); - if (auth_data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_authdata_ptr failed.\n"); - ret = ENOMEM; - goto done; - } - - auth_data_len = fido_assert_authdata_len(assert, 0); - if (auth_data_len == 0) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_authdata_len failed.\n"); - ret = ENOMEM; - goto done; - } - - b64_auth_data = sss_base64_encode(tmp_ctx, auth_data, auth_data_len); - if (b64_auth_data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "failed to encode authenticator data.\n"); - ret = ENOMEM; - goto done; - } - - signature = fido_assert_sig_ptr(assert, 0); - if (signature == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_sig_ptr failed.\n"); - ret = ENOMEM; - goto done; - } - - signature_len = fido_assert_sig_len(assert, 0); - if (signature_len == 0) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_sig_len failed.\n"); - ret = ENOMEM; - goto done; - } - - b64_signature = sss_base64_encode(tmp_ctx, signature, signature_len); - if (b64_signature == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "failed to encode signature.\n"); - ret = ENOMEM; - goto done; - } - - *_auth_data = talloc_steal(mem_ctx, b64_auth_data); - *_signature = talloc_steal(mem_ctx, b64_signature); - ret = EOK; - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -set_assert_auth_data_signature(const struct passkey_data *data, - fido_assert_t *_assert) -{ - TALLOC_CTX *tmp_ctx = NULL; - const unsigned char *auth_data = NULL; - const unsigned char *signature = NULL; - size_t auth_data_len; - size_t signature_len; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - ret = fido_assert_set_count(_assert, 1); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_count failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - auth_data = sss_base64_decode(tmp_ctx, data->auth_data, &auth_data_len); - if (auth_data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "failed to decode authenticator data.\n"); - ret = ENOMEM; - goto done; - } - - ret = fido_assert_set_authdata(_assert, 0, auth_data, auth_data_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_authdata failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - signature = sss_base64_decode(tmp_ctx, data->signature, &signature_len); - if (signature == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "failed to decode signature.\n"); - ret = ENOMEM; - goto done; - } - - ret = fido_assert_set_sig(_assert, 0, signature, signature_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_sig failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -prepare_assert(const struct passkey_data *data, int index, - fido_assert_t *_assert) -{ - TALLOC_CTX *tmp_ctx = NULL; - unsigned char *key_handle; - size_t key_handle_len; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - ret = fido_assert_set_rp(_assert, data->domain); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_rp failed [%d]: %s.\n", ret, fido_strerr(ret)); - goto done; - } - - key_handle = sss_base64_decode(tmp_ctx, data->key_handle_list[index], - &key_handle_len); - if (key_handle == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to decode key handle.\n"); - ret = ENOMEM; - goto done; - } - - ret = fido_assert_allow_cred(_assert, key_handle, key_handle_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_allow_cred failed [%d]: %s.\n", ret, fido_strerr(ret)); - goto done; - } - - ret = set_assert_options(FIDO_OPT_FALSE, FIDO_OPT_OMIT, _assert); - if (ret != FIDO_OK) { - goto done; - } - - ret = set_assert_client_data_hash(data, _assert); - if (ret != EOK) { - goto done; - } - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -reset_public_key(struct pk_data_t *_pk_data) -{ - if (_pk_data->type == COSE_ES256) { - es256_pk_free((es256_pk_t **) &_pk_data->public_key); - } else if (_pk_data->type == COSE_RS256) { - rs256_pk_free((rs256_pk_t **) &_pk_data->public_key); - } else if (_pk_data->type == COSE_EDDSA) { - eddsa_pk_free((eddsa_pk_t **) &_pk_data->public_key); - } - - return EOK; -} -#define DOPIN "/var/run/passkey-dopin" - -static -errno_t enable_dopin() -{ - int fd = creat (DOPIN, (mode_t)0000); - DEBUG(SSSDBG_TRACE_LIBS, "enable_dopin file [%s] fd=[%d], errno: [%d].\n", - DOPIN , fd, (fd == -1)? errno : 0); - close(fd); - - return EOK; -} - -static -errno_t disable_dopin() -{ - int ret = remove(DOPIN); - DEBUG(SSSDBG_TRACE_LIBS, "disable_dopin [%d]\n", ret); - return EOK; -} -#undef DOPIN - -errno_t -request_assert(struct passkey_data *data, fido_dev_t *dev, - fido_assert_t *_assert) -{ - TALLOC_CTX *tmp_ctx = NULL; - char *pin = NULL; - bool has_pin; - bool has_uv; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - return ENOMEM; - } - - has_pin = fido_dev_has_pin(dev); - has_uv = fido_dev_has_uv(dev); - - if (has_pin == true && data->user_verification != FIDO_OPT_FALSE) { - ret = passkey_recv_pin(tmp_ctx, STDIN_FILENO, &pin); - if (ret != EOK) { - DEBUG(SSSDBG_TRACE_FUNC, "PIN is missing: try UV\n"); - ret = FIDO_ERR_PIN_REQUIRED; - /* and try UV that will reset the ret code. - */ - } else { - /* DEBUG(SSSDBG_TRACE_FUNC, "Proceed with PIN [%s]\n", pin); */ - /* ------------- */ - - ret = fido_dev_get_assert(dev, _assert, pin); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert PIN failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - DEBUG(SSSDBG_TRACE_FUNC, "fido_dev_get_assert PIN succeeded.\n"); - - ret = fido_assert_set_uv(_assert, data->user_verification); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_uv failed [%d]: %s.\n", - ret, fido_strerr(ret)); - } - /* error or not: finished */ - goto done; - } - } - - if (has_uv == true && data->user_verification != FIDO_OPT_FALSE) { - - ret = fido_dev_get_assert(dev, _assert, NULL); - if (ret != FIDO_OK && has_pin == true) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_dev_get_assert UV failed [%d]: %s. " - "User shall fall back to PIN authentication.\n", - ret, fido_strerr(ret)); - } else if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert UV failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } else { - DEBUG(SSSDBG_TRACE_FUNC, "fido_dev_get_assert UV succeeded.\n"); - } - /* error not: finished */ - } - -done: - if (pin != NULL) { - sss_erase_mem_securely(pin, strlen(pin)); - } - talloc_free(tmp_ctx); - - if (ret == FIDO_OK && has_uv == true) { - /* PIN or UV has been OK */ - disable_dopin(); - } else if (ret == FIDO_ERR_PIN_REQUIRED || ret == FIDO_ERR_UV_INVALID || - ret == FIDO_ERR_PIN_INVALID || ret == FIDO_ERR_UV_BLOCKED) { - enable_dopin(); - } - return ret; -} - -/* - * - * errno_t - * request_assert(struct passkey_data *data, fido_dev_t *dev, - * fido_assert_t *_assert) - * { - * TALLOC_CTX *tmp_ctx = NULL; - * char *pin = NULL; - * bool has_pin; - * bool has_uv; - * errno_t ret; - * - * tmp_ctx = talloc_new(NULL); - * if (tmp_ctx == NULL) { - * DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - * return ENOMEM; - * } - * - * has_pin = fido_dev_has_pin(dev); - * has_uv = fido_dev_has_uv(dev); - * if (has_uv == true && data->user_verification != FIDO_OPT_FALSE) { - * ret = fido_dev_get_assert(dev, _assert, NULL); - * if (ret != FIDO_OK && has_pin == true) { - * DEBUG(SSSDBG_OP_FAILURE, - * "fido_dev_get_assert failed [%d]: %s. " - * "Falling back to PIN authentication.\n", - * ret, fido_strerr(ret)); - * } else if (ret != FIDO_OK) { - * DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert failed [%d]: %s.\n", - * ret, fido_strerr(ret)); - * goto done; - * } else { - * DEBUG(SSSDBG_TRACE_FUNC, "fido_dev_get_assert succeeded.\n"); - * goto done; - * } - * } - * - * if (has_pin == true && data->user_verification != FIDO_OPT_FALSE) { - * ret = passkey_recv_pin(tmp_ctx, STDIN_FILENO, &pin); - * if (ret != EOK) { - * goto done; - * } - * } - * - * ret = fido_deev_get_assert(dev, _assert, pin); - * if (ret != FIDO_OK) { - * DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert failed [%d]: %s.\n", - * ret, fido_strerr(ret)); - * goto done; - * } - * - * ret = fido_assert_set_uv(_assert, data->user_verification); - * if (ret != FIDO_OK) { - * DEBUG(SSSDBG_OP_FAILURE, - * "fido_assert_set_uv failed [%d]: %s.\n", - * ret, fido_strerr(ret)); - * goto done; - * } - * - * done: - * if (pin != NULL) { - * sss_erase_mem_securely(pin, strlen(pin)); - * } - * talloc_free(tmp_ctx); - * - * return ret; - * } - */ - -errno_t -verify_assert(struct pk_data_t *pk_data, fido_assert_t *assert) -{ - errno_t ret; - - ret = fido_assert_verify(assert, 0, pk_data->type, pk_data->public_key); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_assert_verify failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - -done: - return ret; -} - -void -print_assert_data(const char *key_handle, const char *crypto_challenge, - const char *auth_data, const char *signature) -{ - json_t *passkey = NULL; - char* string = NULL; - - /* Kerberos expects the user_id field, thus it cannot be removed and there - * is nothing to set so it's an empty string. - */ - passkey = json_pack("{s:s*, s:s*, s:s*, s:s*, s:s*}", - "credential_id", key_handle, - "cryptographic_challenge", crypto_challenge, - "authenticator_data", auth_data, - "assertion_signature", signature, - "user_id", ""); - if (passkey == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to create passkey object.\n"); - goto done; - } - - string = json_dumps(passkey, 0); - if (string == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "json_dumps() failed.\n"); - goto done; - } - - puts(string); - free(string); - -done: - json_decref(passkey); - - return; -} +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa + + Copyright (C) 2022 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "util/crypto/sss_crypto.h" +#include "util/debug.h" +#include "util/util.h" + +#include "passkey_child.h" + +errno_t +set_assert_client_data_hash(const struct passkey_data *data, + fido_assert_t *_assert) +{ + TALLOC_CTX *tmp_ctx = NULL; + unsigned char cdh[32]; + unsigned char *crypto_challenge = NULL; + size_t crypto_challenge_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + if (data->action == ACTION_AUTHENTICATE) { + ret = sss_generate_csprng_buffer(cdh, sizeof(cdh)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_generate_csprng_buffer failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + ret = fido_assert_set_clientdata_hash(_assert, cdh, sizeof(cdh)); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_clientdata_hash failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } else if (data->action == ACTION_GET_ASSERT) { + crypto_challenge = sss_base64_decode(tmp_ctx, data->crypto_challenge, + &crypto_challenge_len); + if (crypto_challenge == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to decode client data hash.\n"); + ret = ENOMEM; + goto done; + } + + if (crypto_challenge_len != 32) { + DEBUG(SSSDBG_OP_FAILURE, + "cryptographic-challenge length [%ld] must be 32.\n", + crypto_challenge_len); + ret = EINVAL; + goto done; + } + + ret = fido_assert_set_clientdata_hash(_assert, crypto_challenge, + crypto_challenge_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_clientdata_hash failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } + else { + memset(cdh, 0, sizeof(cdh)); + ret = fido_assert_set_clientdata_hash(_assert, cdh, sizeof(cdh)); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_clientdata_hash failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +set_assert_options(fido_opt_t up, fido_opt_t uv, fido_assert_t *_assert) +{ + errno_t ret; + + ret = fido_assert_set_up(_assert, up); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_up failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + ret = fido_assert_set_uv(_assert, uv); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_uv failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + +done: + return ret; +} + +errno_t +get_assert_auth_data_signature(TALLOC_CTX *mem_ctx, fido_assert_t *assert, + const char **_auth_data, + const char **_signature) +{ + TALLOC_CTX *tmp_ctx = NULL; + const unsigned char *auth_data; + const unsigned char *signature; + const char *b64_auth_data; + const char *b64_signature; + size_t auth_data_len; + size_t signature_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + auth_data = fido_assert_authdata_ptr(assert, 0); + if (auth_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_authdata_ptr failed.\n"); + ret = ENOMEM; + goto done; + } + + auth_data_len = fido_assert_authdata_len(assert, 0); + if (auth_data_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_authdata_len failed.\n"); + ret = ENOMEM; + goto done; + } + + b64_auth_data = sss_base64_encode(tmp_ctx, auth_data, auth_data_len); + if (b64_auth_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to encode authenticator data.\n"); + ret = ENOMEM; + goto done; + } + + signature = fido_assert_sig_ptr(assert, 0); + if (signature == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_sig_ptr failed.\n"); + ret = ENOMEM; + goto done; + } + + signature_len = fido_assert_sig_len(assert, 0); + if (signature_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_sig_len failed.\n"); + ret = ENOMEM; + goto done; + } + + b64_signature = sss_base64_encode(tmp_ctx, signature, signature_len); + if (b64_signature == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to encode signature.\n"); + ret = ENOMEM; + goto done; + } + + *_auth_data = talloc_steal(mem_ctx, b64_auth_data); + *_signature = talloc_steal(mem_ctx, b64_signature); + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +set_assert_auth_data_signature(const struct passkey_data *data, + fido_assert_t *_assert) +{ + TALLOC_CTX *tmp_ctx = NULL; + const unsigned char *auth_data = NULL; + const unsigned char *signature = NULL; + size_t auth_data_len; + size_t signature_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = fido_assert_set_count(_assert, 1); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_count failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + auth_data = sss_base64_decode(tmp_ctx, data->auth_data, &auth_data_len); + if (auth_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to decode authenticator data.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_assert_set_authdata(_assert, 0, auth_data, auth_data_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_authdata failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + signature = sss_base64_decode(tmp_ctx, data->signature, &signature_len); + if (signature == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to decode signature.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_assert_set_sig(_assert, 0, signature, signature_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_sig failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +prepare_assert(const struct passkey_data *data, int index, + fido_assert_t *_assert) +{ + TALLOC_CTX *tmp_ctx = NULL; + unsigned char *key_handle; + size_t key_handle_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = fido_assert_set_rp(_assert, data->domain); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_rp failed [%d]: %s.\n", ret, fido_strerr(ret)); + goto done; + } + + key_handle = sss_base64_decode(tmp_ctx, data->key_handle_list[index], + &key_handle_len); + if (key_handle == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to decode key handle.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_assert_allow_cred(_assert, key_handle, key_handle_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_allow_cred failed [%d]: %s.\n", ret, fido_strerr(ret)); + goto done; + } + + ret = set_assert_options(FIDO_OPT_FALSE, FIDO_OPT_OMIT, _assert); + if (ret != FIDO_OK) { + goto done; + } + + ret = set_assert_client_data_hash(data, _assert); + if (ret != EOK) { + goto done; + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +reset_public_key(struct pk_data_t *_pk_data) +{ + if (_pk_data->type == COSE_ES256) { + es256_pk_free((es256_pk_t **) &_pk_data->public_key); + } else if (_pk_data->type == COSE_RS256) { + rs256_pk_free((rs256_pk_t **) &_pk_data->public_key); + } else if (_pk_data->type == COSE_EDDSA) { + eddsa_pk_free((eddsa_pk_t **) &_pk_data->public_key); + } + + return EOK; +} +#define DOPIN "/var/run/passkey-dopin" + +static +errno_t enable_dopin() +{ + int fd = creat (DOPIN, (mode_t)0000); + DEBUG(SSSDBG_TRACE_LIBS, "enable_dopin file [%s] fd=[%d], errno: [%d].\n", + DOPIN , fd, (fd == -1)? errno : 0); + close(fd); + return EOK; +} + +static +errno_t disable_dopin() +{ + int ret = remove(DOPIN); + DEBUG(SSSDBG_TRACE_LIBS, "disable_dopin [%d]\n", ret); + return EOK; +} +#undef DOPIN + +errno_t +request_assert(struct passkey_data *data, fido_dev_t *dev, + fido_assert_t *_assert) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *pin = NULL; + bool has_pin; + bool has_uv; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + has_pin = fido_dev_has_pin(dev); + has_uv = fido_dev_has_uv(dev); + + if (has_pin == true && data->user_verification != FIDO_OPT_FALSE) { + ret = passkey_recv_pin(tmp_ctx, STDIN_FILENO, &pin); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "PIN is missing: try UV\n"); + ret = FIDO_ERR_PIN_REQUIRED; + /* and try UV that will reset the ret code. + */ + } else { + /* DEBUG(SSSDBG_TRACE_FUNC, "Proceed with PIN [%s]\n", pin); */ + /* ------------- */ + ret = fido_dev_get_assert(dev, _assert, pin); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert PIN failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "fido_dev_get_assert PIN succeeded.\n"); + + ret = fido_assert_set_uv(_assert, data->user_verification); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_uv failed [%d]: %s.\n", + ret, fido_strerr(ret)); + } + /* error or not: finished */ + goto done; + } + } + + if (has_uv == true && data->user_verification != FIDO_OPT_FALSE) { + ret = fido_dev_get_assert(dev, _assert, NULL); + if (ret != FIDO_OK && has_pin == true) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_dev_get_assert UV failed [%d]: %s. " + "User shall fall back to PIN authentication.\n", + ret, fido_strerr(ret)); + } else if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert UV failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "fido_dev_get_assert UV succeeded.\n"); + } + /* error not: finished */ + } + +done: + if (pin != NULL) { + sss_erase_mem_securely(pin, strlen(pin)); + } + talloc_free(tmp_ctx); + + if (ret == FIDO_OK && has_uv == true) { + /* PIN or UV has been OK */ + disable_dopin(); + } else if (ret == FIDO_ERR_PIN_REQUIRED || ret == FIDO_ERR_UV_INVALID || + ret == FIDO_ERR_PIN_INVALID || ret == FIDO_ERR_UV_BLOCKED) { + enable_dopin(); + } + return ret; +} + +/* + * + * errno_t + * request_assert(struct passkey_data *data, fido_dev_t *dev, + * fido_assert_t *_assert) + * { + * TALLOC_CTX *tmp_ctx = NULL; + * char *pin = NULL; + * bool has_pin; + * bool has_uv; + * errno_t ret; + * + * tmp_ctx = talloc_new(NULL); + * if (tmp_ctx == NULL) { + * DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + * return ENOMEM; + * } + * + * has_pin = fido_dev_has_pin(dev); + * has_uv = fido_dev_has_uv(dev); + * if (has_uv == true && data->user_verification != FIDO_OPT_FALSE) { + * ret = fido_dev_get_assert(dev, _assert, NULL); + * if (ret != FIDO_OK && has_pin == true) { + * DEBUG(SSSDBG_OP_FAILURE, + * "fido_dev_get_assert failed [%d]: %s. " + * "Falling back to PIN authentication.\n", + * ret, fido_strerr(ret)); + * } else if (ret != FIDO_OK) { + * DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert failed [%d]: %s.\n", + * ret, fido_strerr(ret)); + * goto done; + * } else { + * DEBUG(SSSDBG_TRACE_FUNC, "fido_dev_get_assert succeeded.\n"); + * goto done; + * } + * } + * + * if (has_pin == true && data->user_verification != FIDO_OPT_FALSE) { + * ret = passkey_recv_pin(tmp_ctx, STDIN_FILENO, &pin); + * if (ret != EOK) { + * goto done; + * } + * } + * + * ret = fido_deev_get_assert(dev, _assert, pin); + * if (ret != FIDO_OK) { + * DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert failed [%d]: %s.\n", + * ret, fido_strerr(ret)); + * goto done; + * } + * + * ret = fido_assert_set_uv(_assert, data->user_verification); + * if (ret != FIDO_OK) { + * DEBUG(SSSDBG_OP_FAILURE, + * "fido_assert_set_uv failed [%d]: %s.\n", + * ret, fido_strerr(ret)); + * goto done; + * } + * + * done: + * if (pin != NULL) { + * sss_erase_mem_securely(pin, strlen(pin)); + * } + * talloc_free(tmp_ctx); + * + * return ret; + * } + */ + +errno_t +verify_assert(struct pk_data_t *pk_data, fido_assert_t *assert) +{ + errno_t ret; + + ret = fido_assert_verify(assert, 0, pk_data->type, pk_data->public_key); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_assert_verify failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + +done: + return ret; +} + +void +print_assert_data(const char *key_handle, const char *crypto_challenge, + const char *auth_data, const char *signature) +{ + json_t *passkey = NULL; + char* string = NULL; + + /* Kerberos expects the user_id field, thus it cannot be removed and there + * is nothing to set so it's an empty string. + */ + passkey = json_pack("{s:s*, s:s*, s:s*, s:s*, s:s*}", + "credential_id", key_handle, + "cryptographic_challenge", crypto_challenge, + "authenticator_data", auth_data, + "assertion_signature", signature, + "user_id", ""); + if (passkey == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create passkey object.\n"); + goto done; + } + + string = json_dumps(passkey, 0); + if (string == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "json_dumps() failed.\n"); + goto done; + } + + puts(string); + free(string); + +done: + json_decref(passkey); + + return; +} diff --git a/src/passkey_child/passkey_child_common.c b/src/passkey_child/passkey_child_common.c index 83eeeb5d821..ef54ad935ba 100644 --- a/src/passkey_child/passkey_child_common.c +++ b/src/passkey_child/passkey_child_common.c @@ -1,973 +1,973 @@ -/* - SSSD - - Helper child to commmunicate with passkey devices - - Authors: - Iker Pedrosa - - Copyright (C) 2022 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 . -*/ - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include "util/crypto/sss_crypto.h" -#include "util/debug.h" -#include "util/util.h" -#include "util/crypto/sss_crypto.h" -#include "util/sss_prctl.h" - -#include "passkey_child.h" - -#if OPENSSL_VERSION_NUMBER >= 0x30000000 -#define get_id(x) EVP_PKEY_get_base_id((x)) -#else -#define get_id(x) EVP_PKEY_base_id((x)) -#endif /* OPENSSL_VERSION_NUMBER */ - -errno_t -cose_str_to_int(const char *type, int *out) -{ - if (strcasecmp(type, "es256") == 0) { - *out = COSE_ES256; - } else if (strcasecmp(type, "rs256") == 0) { - *out = COSE_RS256; - } else if (strcasecmp(type, "eddsa") == 0) { - *out = COSE_EDDSA; - } else { - *out = 0; - return ERR_INVALID_CRED_TYPE; - } - - return EOK; -} - -static errno_t -cred_type_str_to_enum(const char *type, enum credential_type *out) -{ - if (strcasecmp(type, "server-side") == 0) { - *out = CRED_SERVER_SIDE; - } else if (strcasecmp(type, "discoverable") == 0) { - *out = CRED_DISCOVERABLE; - } else { - *out = 0; - return ERR_INVALID_CRED_TYPE; - } - - return EOK; -} - -static errno_t -parse_public_keys_and_handlers(TALLOC_CTX *mem_ctx, - const char *public_keys, - const char *key_handles, - struct passkey_data *_data) -{ - TALLOC_CTX *tmp_ctx = NULL; - char **pk_list = NULL; - char **kh_list = NULL; - int pk_num = 0; - int kh_num = 0; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ERROR("talloc_new() failed\n"); - return ENOMEM; - } - - ret = split_on_separator(tmp_ctx, public_keys, ',', true, true, &pk_list, &pk_num); - if (ret != EOK && _data->action == ACTION_AUTHENTICATE) { - ERROR("Incorrectly formatted public keys.\n"); - goto done; - } - - ret = split_on_separator(tmp_ctx, key_handles, ',', true, true, &kh_list, &kh_num); - if (ret != EOK) { - ERROR("Incorrectly formatted public keys.\n"); - goto done; - } - - if (_data->action == ACTION_AUTHENTICATE && pk_num != kh_num) { - ERROR("The number of public keys and key handles don't match.\n"); - goto done; - } - - _data->public_key_list = talloc_steal(mem_ctx, pk_list); - _data->key_handle_list = talloc_steal(mem_ctx, kh_list); - _data->public_key_size = pk_num; - _data->key_handle_size = kh_num; - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -parse_arguments(TALLOC_CTX *mem_ctx, int argc, const char *argv[], - struct passkey_data *data) -{ - int opt; - int dumpable = 1; - int backtrace = 1; - int debug_fd = -1; - char *user_verification = NULL; - char *public_keys = NULL; - char *key_handles = NULL; - const char *opt_logger = NULL; - const char *type = NULL; - const char *cred_type = NULL; - poptContext pc; - errno_t ret; - - /* Set defaults */ - data->action = ACTION_NONE; - data->shortname = NULL; - data->domain = NULL; - data->public_key_list = NULL; - data->key_handle_list = NULL; - data->public_key_size = 0; - data->key_handle_size = 0; - data->crypto_challenge = NULL; - data->auth_data = NULL; - data->signature = NULL; - data->type = COSE_ES256; - data->user_verification = FIDO_OPT_OMIT; - data->cred_type = CRED_SERVER_SIDE; - data->user_id = NULL; - data->mapping_file = NULL; - data->quiet = false; - data->debug_libfido2 = false; - - struct poptOption long_options[] = { - POPT_AUTOHELP - SSSD_DEBUG_OPTS - {"dumpable", 0, POPT_ARG_INT, &dumpable, 0, - _("Allow core dumps"), NULL }, - {"backtrace", 0, POPT_ARG_INT, &backtrace, 0, - _("Enable debug backtrace"), NULL }, - {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0, - _("An open file descriptor for the debug logs"), NULL}, - SSSD_LOGGER_OPTS - {"register", 0, POPT_ARG_NONE, NULL, 'r', - _("Register a passkey for a user"), NULL }, - {"authenticate", 0, POPT_ARG_NONE, NULL, 'a', - _("Authenticate a user with a passkey"), NULL }, - {"get-assert", 0, POPT_ARG_NONE, NULL, 'g', - _("Obtain assertion data"), NULL }, - {"get-device-info", 0, POPT_ARG_NONE, NULL, 'i', - _("Obtain device information"), NULL }, - {"verify-assert", 0, POPT_ARG_NONE, NULL, 'v', - _("Verify assertion data"), NULL }, - {"username", 0, POPT_ARG_STRING, &data->shortname, 0, - _("Shortname"), NULL }, - {"domain", 0, POPT_ARG_STRING, &data->domain, 0, - _("Domain"), NULL}, - {"public-key", 0, POPT_ARG_STRING, &public_keys, 0, - _("Public key"), NULL }, - {"key-handle", 0, POPT_ARG_STRING, &key_handles, 0, - _("Key handle"), NULL}, - {"cryptographic-challenge", 0, POPT_ARG_STRING, - &data->crypto_challenge, 0, - _("Cryptographic challenge"), NULL}, - {"auth-data", 0, POPT_ARG_STRING, &data->auth_data, 0, - _("Authenticator data"), NULL}, - {"signature", 0, POPT_ARG_STRING, &data->signature, 0, - _("Signature"), NULL}, - {"type", 0, POPT_ARG_STRING, &type, 0, - _("COSE type to use"), "es256|rs256|eddsa"}, - {"user-verification", 0, POPT_ARG_STRING, &user_verification, 0, - _("Require user-verification"), "true|false"}, - {"cred-type", 0, POPT_ARG_STRING, &cred_type, 0, - _("Credential type"), "server-side|discoverable"}, - {"output-file", 0, POPT_ARG_STRING, &data->mapping_file, 0, - _("Write key mapping data to file"), NULL}, - {"quiet", 0, POPT_ARG_NONE, NULL, 'q', - _("Supress prompts"), NULL}, - {"debug-libfido2", 0, POPT_ARG_NONE, NULL, 'd', - _("Enable debug in libfido2 library"), NULL}, - SSSD_LOGGER_OPTS - POPT_TABLEEND - }; - - /* 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) { - case 'r': - if (data->action != ACTION_NONE - && data->action != ACTION_REGISTER) { - fprintf(stderr, "\nActions are mutually exclusive and should" \ - " be used only once.\n\n"); - poptPrintUsage(pc, stderr, 0); - ret = EINVAL; - goto done; - } - data->action = ACTION_REGISTER; - break; - case 'a': - if (data->action != ACTION_NONE - && data->action != ACTION_AUTHENTICATE) { - fprintf(stderr, "\nActions are mutually exclusive and should" \ - " be used only once.\n\n"); - poptPrintUsage(pc, stderr, 0); - ret = EINVAL; - goto done; - } - data->action = ACTION_AUTHENTICATE; - break; - case 'g': - if (data->action != ACTION_NONE - && data->action != ACTION_GET_ASSERT) { - fprintf(stderr, "\nActions are mutually exclusive and should" \ - " be used only once.\n\n"); - poptPrintUsage(pc, stderr, 0); - ret = EINVAL; - goto done; - } - data->action = ACTION_GET_ASSERT; - break; - case 'i': - if (data->action != ACTION_NONE - && data->action != ACTION_GET_DEVINFO) { - fprintf(stderr, "\nActions are mutually exclusive and should" \ - " be used only once.\n\n"); - poptPrintUsage(pc, stderr, 0); - ret = EINVAL; - goto done; - } - data->action = ACTION_GET_DEVINFO; - break; - case 'v': - if (data->action != ACTION_NONE - && data->action != ACTION_VERIFY_ASSERT) { - fprintf(stderr, "\nActions are mutually exclusive and should" \ - " be used only once.\n\n"); - poptPrintUsage(pc, stderr, 0); - ret = EINVAL; - goto done; - } - data->action = ACTION_VERIFY_ASSERT; - break; - case 'q': - data->quiet = true; - break; - case 'd': - data->debug_libfido2 = true; - break; - default: - fprintf(stderr, "\nInvalid option %s: %s\n\n", - poptBadOption(pc, 0), poptStrerror(opt)); - poptPrintUsage(pc, stderr, 0); - ret = EINVAL; - goto done; - } - } - - poptFreeContext(pc); - - sss_prctl_set_dumpable((dumpable == 0) ? 0 : 1); - - if (user_verification != NULL) { - if (strcmp(user_verification, "true") == 0) { - data->user_verification = FIDO_OPT_TRUE; - } else if (strcmp(user_verification, "false") == 0) { - data->user_verification = FIDO_OPT_FALSE; - } else if (user_verification != NULL) { - ERROR("[%s] is not a valid user-verification value.\n", - user_verification); - ret = EINVAL; - goto done; - } - } - - if (type != NULL) { - ret = cose_str_to_int(type, &data->type); - if (ret != EOK) { - ERROR("[%s] is not a valid COSE type (es256, rs256 or eddsa).\n", - type); - goto done; - } - } - - if (public_keys != NULL || key_handles != NULL) { - ret = parse_public_keys_and_handlers(mem_ctx, public_keys, key_handles, - data); - if (ret != EOK) { - goto done; - } - } - - if (cred_type != NULL) { - ret = cred_type_str_to_enum(cred_type, &data->cred_type); - if (ret != EOK) { - ERROR("[%s] is not a valid credential type (server-side or" - " discoverable).\n", - cred_type); - goto done; - } - } - - debug_prg_name = talloc_asprintf(NULL, "passkey_child[%d]", getpid()); - if (debug_prg_name == NULL) { - ERROR("talloc_asprintf failed.\n"); - ret = ENOMEM; - goto done; - } - - if (debug_fd != -1) { - opt_logger = sss_logger_str[FILES_LOGGER]; - ret = set_debug_file_from_fd(debug_fd); - if (ret != EOK) { - opt_logger = sss_logger_str[STDERR_LOGGER]; - ERROR("set_debug_file_from_fd failed.\n"); - } - } - - DEBUG_INIT(debug_level, opt_logger); - sss_set_debug_backtrace_enable((backtrace == 0) ? false : true); - - ret = EOK; - -done: - return ret; -} - -errno_t -check_arguments(const struct passkey_data *data) -{ - errno_t ret = EOK; - - DEBUG(SSSDBG_TRACE_FUNC, "Argument values after parsing\n"); - DEBUG(SSSDBG_TRACE_FUNC, "action: %d\n", data->action); - DEBUG(SSSDBG_TRACE_FUNC, "shortname: %s, domain: %s\n", - data->shortname, data->domain); - DEBUG(SSSDBG_TRACE_FUNC, "Number of key handles %d\n", - data->key_handle_size); - for (int i = 0; i < data->key_handle_size; i++) { - DEBUG(SSSDBG_TRACE_FUNC, "key %d, key_handle: %s\n", - i + 1, data->key_handle_list[i]); - } - DEBUG(SSSDBG_TRACE_FUNC, "Number of public keys %d\n", - data->public_key_size); - for (int i = 0; i < data->public_key_size; i++) { - DEBUG(SSSDBG_TRACE_FUNC, "key %d, public_key: %s\n", - i + 1, data->public_key_list[i]); - } - DEBUG(SSSDBG_TRACE_FUNC, "cryptographic-challenge: %s\n", - data->crypto_challenge); - DEBUG(SSSDBG_TRACE_FUNC, "auth-data: %s\n", - data->auth_data); - DEBUG(SSSDBG_TRACE_FUNC, "signature: %s\n", - data->signature); - DEBUG(SSSDBG_TRACE_FUNC, "type: %d\n", data->type); - DEBUG(SSSDBG_TRACE_FUNC, "user_verification: %d\n", - data->user_verification); - DEBUG(SSSDBG_TRACE_FUNC, "cred_type: %d\n", - data->cred_type); - DEBUG(SSSDBG_TRACE_FUNC, "Mapping file: %s\n", data->mapping_file); - DEBUG(SSSDBG_TRACE_FUNC, "debug_libfido2: %d\n", data->debug_libfido2); - - if (data->action == ACTION_NONE) { - DEBUG(SSSDBG_OP_FAILURE, "No action set.\n"); - ret = ERR_INPUT_PARSE; - goto done; - } - - if (data->action == ACTION_REGISTER - && (data->shortname == NULL || data->domain == NULL)) { - DEBUG(SSSDBG_OP_FAILURE, "Too few arguments for register action.\n"); - ret = ERR_INPUT_PARSE; - goto done; - } - - if (data->action == ACTION_AUTHENTICATE - && (data->domain == NULL || data->public_key_list == NULL - || data->key_handle_list == NULL)) { - DEBUG(SSSDBG_OP_FAILURE, - "Too few arguments for authenticate action.\n"); - ret = ERR_INPUT_PARSE; - goto done; - } - - if (data->action == ACTION_GET_DEVINFO - && (data->domain == NULL || data->key_handle_list == NULL)) { - DEBUG(SSSDBG_OP_FAILURE, - "Too few arguments for get-device-info action.\n"); - ret = ERR_INPUT_PARSE; - goto done; - } - - if (data->action == ACTION_GET_ASSERT - && (data->domain == NULL || data->key_handle_list == NULL - || data->crypto_challenge == NULL)) { - DEBUG(SSSDBG_OP_FAILURE, - "Too few arguments for get-assert action.\n"); - ret = ERR_INPUT_PARSE; - goto done; - } - - if (data->action == ACTION_VERIFY_ASSERT - && (data->domain == NULL || data->public_key_list == NULL - || data->key_handle_list == NULL || data->crypto_challenge == NULL - || data->auth_data == NULL || data->signature == NULL)) { - DEBUG(SSSDBG_OP_FAILURE, - "Too few arguments for verify-assert action.\n"); - ret = ERR_INPUT_PARSE; - goto done; - } - -done: - return ret; -} - -errno_t -register_key(struct passkey_data *data) -{ - TALLOC_CTX *tmp_ctx = NULL; - fido_cred_t *cred = NULL; - fido_dev_t *dev = NULL; - fido_dev_info_t *dev_list = NULL; - size_t dev_number = 0; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - return ENOMEM; - } - - data->user_id = talloc_array(tmp_ctx, unsigned char, USER_ID_SIZE); - if (data->user_id == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_array() failed.\n"); - ret = ENOMEM; - goto done; - } - - cred = fido_cred_new(); - if (cred == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_new failed.\n"); - ret = ENOMEM; - goto done; - } - - dev_list = fido_dev_info_new(DEVLIST_SIZE); - if (dev_list == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = list_devices(dev_list, &dev_number); - if (ret != EOK) { - goto done; - } - - ret = select_device(data->action, dev_list, dev_number, NULL, &dev); - if (ret != EOK) { - goto done; - } - - ret = prepare_credentials(data, dev, cred); - if (ret != EOK) { - goto done; - } - - ret = generate_credentials(data, dev, cred); - if (ret != EOK) { - ERROR("A problem occurred while generating the credentials.\n"); - goto done; - } - - ret = verify_credentials(cred); - if (ret != EOK) { - goto done; - } - - ret = print_credentials(data, cred); - if (ret != EOK) { - goto done; - } - - ret = EOK; - -done: - fido_cred_free(&cred); - fido_dev_info_free(&dev_list, dev_number); - if (dev != NULL) { - fido_dev_close(dev); - } - fido_dev_free(&dev); - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -public_key_to_base64(TALLOC_CTX *mem_ctx, const struct passkey_data *data, - const unsigned char *public_key, size_t pk_len, - char **_pem_key) -{ - EVP_PKEY *evp_pkey = NULL; - unsigned char *pub = NULL; - char *pem_key = NULL; - unsigned long err; - errno_t ret; - - if (_pem_key == NULL) { - ret = EINVAL; - goto done; - } - - switch (data->type) { - case COSE_ES256: - ret = es256_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); - break; - case COSE_RS256: - ret = rs256_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); - break; - case COSE_EDDSA: - ret = eddsa_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); - break; - default: - DEBUG(SSSDBG_OP_FAILURE, "Invalid key type.\n"); - ret = EINVAL; - break; - } - - if (ret != EOK) { - goto done; - } - - ret = i2d_PUBKEY(evp_pkey, &pub); - if (ret < 1) { - err = ERR_get_error(); - DEBUG(SSSDBG_OP_FAILURE, "i2d_PUBKEY failed [%lu][%s].\n", - err, ERR_error_string(err, NULL)); - ret = EIO; - goto done; - } - - pem_key = sss_base64_encode(mem_ctx, pub, ret); - if (pem_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "sss_base64_encode failed.\n"); - ret = ENOMEM; - goto done; - } - - *_pem_key = pem_key; - ret = EOK; - -done: - free(pub); - - if (evp_pkey != NULL) { - EVP_PKEY_free(evp_pkey); - } - - return ret; -} - -errno_t -select_authenticator(struct passkey_data *data, fido_dev_t **_dev, - fido_assert_t **_assert, int *_index) -{ - fido_dev_info_t *dev_list = NULL; - fido_dev_t *dev = NULL; - size_t dev_list_len = 0; - fido_assert_t *assert = NULL; - int index = 0; - errno_t ret; - - dev_list = fido_dev_info_new(DEVLIST_SIZE); - if (dev_list == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_new failed.\n"); - ret = ENOMEM; - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Checking for devices.\n"); - ret = list_devices(dev_list, &dev_list_len); - if (ret != EOK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "%d key handles provided.\n", - data->key_handle_size); - - while (index < data->key_handle_size) { - DEBUG(SSSDBG_TRACE_FUNC, - "Preparing assert request data with key handle %d.\n", index + 1); - - assert = fido_assert_new(); - if (assert == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_assert_new failed.\n"); - ret = ENOMEM; - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Preparing assert request data.\n"); - ret = prepare_assert(data, index, assert); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Selecting device.\n"); - ret = select_device(data->action, dev_list, dev_list_len, assert, &dev); - if (ret == EOK) { - /* Key handle found in device */ - /* update device current configuration - */ - bool has_pin = fido_dev_has_pin(dev); - bool has_uv = fido_dev_has_uv (dev); - int fd = -1; - if (has_pin && has_uv) { - fd = creat("/var/run/passkey-pinuv", 0000); - if (fd < 0) - DEBUG(SSSDBG_TRACE_FUNC, "rror creat pinuv errno = %d\n", errno); - close(fd); - } - else { - (void)remove ("/var/run/passkey-pinuv"); - if (has_pin) { - fd = creat("/var/run/passkey-pinonly", 0000); - if (fd < 0) - DEBUG(SSSDBG_TRACE_FUNC, "eror creat pinonly errno = %d\n", errno); - close (fd); - } else { - /* no pin and no uv; */ - (void)remove ("/var/run/passkey-pinonly"); - fido_dev_close(dev); - dev = NULL; - ret = FIDO_ERR_NOTFOUND; - } - } - break; - } - - if (dev != NULL) { - fido_dev_close(dev); - } - fido_dev_free(&dev); - fido_assert_free(&assert); - index++; - } - - *_dev = dev; - *_assert = assert; - *_index = index; - -done: - if (ret != EOK) { - fido_assert_free(&assert); - } - fido_dev_info_free(&dev_list, dev_list_len); - - return ret; -} - -errno_t -public_key_to_libfido2(const char *pem_public_key, struct pk_data_t *_pk_data) -{ - TALLOC_CTX *tmp_ctx = NULL; - const unsigned char *public_key = NULL; - size_t pk_len; - const EVP_PKEY *evp_pkey = NULL; - int base_id; - unsigned long err; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - public_key = sss_base64_decode(tmp_ctx, pem_public_key, &pk_len); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to decode public key.\n"); - ret = ENOMEM; - goto done; - } - - evp_pkey = d2i_PUBKEY(NULL, &public_key, pk_len); - if (evp_pkey == NULL) { - err = ERR_get_error(); - DEBUG(SSSDBG_OP_FAILURE, "d2i_pubkey failed [%lu][%s].\n", - err, ERR_error_string(err, NULL)); - ret = EIO; - goto done; - } - - base_id = get_id(evp_pkey); - if (base_id == EVP_PKEY_EC) { - _pk_data->type = COSE_ES256; - ret = evp_pkey_to_es256_pubkey(evp_pkey, _pk_data); - } else if (base_id == EVP_PKEY_RSA) { - _pk_data->type = COSE_RS256; - ret = evp_pkey_to_rs256_pubkey(evp_pkey, _pk_data); - } else if (base_id == EVP_PKEY_ED25519) { - _pk_data->type = COSE_EDDSA; - ret = evp_pkey_to_eddsa_pubkey(evp_pkey, _pk_data); - } else { - DEBUG(SSSDBG_OP_FAILURE, - "Unrecognized key type.\n"); - ret = EINVAL; - } - if (ret != EOK) { - goto done; - } - - ret = EOK; - -done: - if (evp_pkey != NULL) { - EVP_PKEY_free(discard_const(evp_pkey)); - } - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -authenticate(struct passkey_data *data) -{ - TALLOC_CTX *tmp_ctx = NULL; - fido_assert_t *assert = NULL; - fido_dev_t *dev = NULL; - struct pk_data_t pk_data = { 0 }; - int index; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ERROR("talloc_new() failed\n"); - return ENOMEM; - } - - ret = select_authenticator(data, &dev, &assert, &index); - if (ret != EOK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Comparing the device and policy options.\n"); - ret = get_device_options(dev, data); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert options.\n"); - ret = set_assert_options(FIDO_OPT_TRUE, data->user_verification, assert); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to reset assert options.\n"); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert client data.\n"); - ret = set_assert_client_data_hash(data, assert); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to reset client data hash.\n"); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Decoding public key.\n"); - ret = public_key_to_libfido2(data->public_key_list[index], &pk_data); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Getting assert.\n"); - ret = request_assert(data, dev, assert); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Verifying assert.\n"); - ret = verify_assert(&pk_data, assert); - if (ret != FIDO_OK) { - goto done; - } - - ret = FIDO_OK; - -done: - reset_public_key(&pk_data); - if (dev != NULL) { - fido_dev_close(dev); - } - fido_dev_free(&dev); - fido_assert_free(&assert); - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -get_assert_data(struct passkey_data *data) -{ - TALLOC_CTX *tmp_ctx = NULL; - fido_dev_t *dev = NULL; - fido_assert_t *assert = NULL; - const char *auth_data = NULL; - const char *signature = NULL; - int index; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - return ENOMEM; - } - - ret = select_authenticator(data, &dev, &assert, &index); - if (ret != EOK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Comparing the device and policy options.\n"); - ret = get_device_options(dev, data); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert options.\n"); - ret = set_assert_options(FIDO_OPT_TRUE, data->user_verification, assert); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to reset assert options.\n"); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Getting assert.\n"); - ret = request_assert(data, dev, assert); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Getting authentication data and signature.\n"); - ret = get_assert_auth_data_signature(tmp_ctx, assert, &auth_data, - &signature); - if (ret != EOK) { - goto done; - } - - print_assert_data(data->key_handle_list[index], data->crypto_challenge, - auth_data, signature); - -done: - if (dev != NULL) { - fido_dev_close(dev); - } - fido_dev_free(&dev); - fido_assert_free(&assert); - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -get_device_info(struct passkey_data *data) -{ - TALLOC_CTX *tmp_ctx = NULL; - fido_dev_t *dev = NULL; - fido_assert_t *assert = NULL; - int index; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - return ENOMEM; - } - - ret = select_authenticator(data, &dev, &assert, &index); - if (ret != EOK) { - goto done; - } - /* unused */ - fido_assert_free(&assert); - - done: - if (dev != NULL) { - fido_dev_close(dev); - } - fido_dev_free(&dev); - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -verify_assert_data(struct passkey_data *data) -{ - fido_assert_t *assert = NULL; - struct pk_data_t pk_data = { 0 }; - errno_t ret; - - assert = fido_assert_new(); - if (assert == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_assert_new failed.\n"); - ret = ENOMEM; - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Preparing assert data.\n"); - ret = prepare_assert(data, 0, assert); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, - "Preparing assert authenticator data and signature.\n"); - ret = set_assert_auth_data_signature(data, assert); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Decoding public key.\n"); - ret = public_key_to_libfido2(data->public_key_list[0], &pk_data); - if (ret != FIDO_OK) { - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Verifying assert.\n"); - ret = verify_assert(&pk_data, assert); - if (ret != FIDO_OK) { - goto done; - } - - ret = FIDO_OK; - -done: - reset_public_key(&pk_data); - fido_assert_free(&assert); - - return ret; -} +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa + + Copyright (C) 2022 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 . +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "util/crypto/sss_crypto.h" +#include "util/debug.h" +#include "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "util/sss_prctl.h" + +#include "passkey_child.h" + +#if OPENSSL_VERSION_NUMBER >= 0x30000000 +#define get_id(x) EVP_PKEY_get_base_id((x)) +#else +#define get_id(x) EVP_PKEY_base_id((x)) +#endif /* OPENSSL_VERSION_NUMBER */ + +errno_t +cose_str_to_int(const char *type, int *out) +{ + if (strcasecmp(type, "es256") == 0) { + *out = COSE_ES256; + } else if (strcasecmp(type, "rs256") == 0) { + *out = COSE_RS256; + } else if (strcasecmp(type, "eddsa") == 0) { + *out = COSE_EDDSA; + } else { + *out = 0; + return ERR_INVALID_CRED_TYPE; + } + + return EOK; +} + +static errno_t +cred_type_str_to_enum(const char *type, enum credential_type *out) +{ + if (strcasecmp(type, "server-side") == 0) { + *out = CRED_SERVER_SIDE; + } else if (strcasecmp(type, "discoverable") == 0) { + *out = CRED_DISCOVERABLE; + } else { + *out = 0; + return ERR_INVALID_CRED_TYPE; + } + + return EOK; +} + +static errno_t +parse_public_keys_and_handlers(TALLOC_CTX *mem_ctx, + const char *public_keys, + const char *key_handles, + struct passkey_data *_data) +{ + TALLOC_CTX *tmp_ctx = NULL; + char **pk_list = NULL; + char **kh_list = NULL; + int pk_num = 0; + int kh_num = 0; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ERROR("talloc_new() failed\n"); + return ENOMEM; + } + + ret = split_on_separator(tmp_ctx, public_keys, ',', true, true, &pk_list, &pk_num); + if (ret != EOK && _data->action == ACTION_AUTHENTICATE) { + ERROR("Incorrectly formatted public keys.\n"); + goto done; + } + + ret = split_on_separator(tmp_ctx, key_handles, ',', true, true, &kh_list, &kh_num); + if (ret != EOK) { + ERROR("Incorrectly formatted public keys.\n"); + goto done; + } + + if (_data->action == ACTION_AUTHENTICATE && pk_num != kh_num) { + ERROR("The number of public keys and key handles don't match.\n"); + goto done; + } + + _data->public_key_list = talloc_steal(mem_ctx, pk_list); + _data->key_handle_list = talloc_steal(mem_ctx, kh_list); + _data->public_key_size = pk_num; + _data->key_handle_size = kh_num; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +parse_arguments(TALLOC_CTX *mem_ctx, int argc, const char *argv[], + struct passkey_data *data) +{ + int opt; + int dumpable = 1; + int backtrace = 1; + int debug_fd = -1; + char *user_verification = NULL; + char *public_keys = NULL; + char *key_handles = NULL; + const char *opt_logger = NULL; + const char *type = NULL; + const char *cred_type = NULL; + poptContext pc; + errno_t ret; + + /* Set defaults */ + data->action = ACTION_NONE; + data->shortname = NULL; + data->domain = NULL; + data->public_key_list = NULL; + data->key_handle_list = NULL; + data->public_key_size = 0; + data->key_handle_size = 0; + data->crypto_challenge = NULL; + data->auth_data = NULL; + data->signature = NULL; + data->type = COSE_ES256; + data->user_verification = FIDO_OPT_OMIT; + data->cred_type = CRED_SERVER_SIDE; + data->user_id = NULL; + data->mapping_file = NULL; + data->quiet = false; + data->debug_libfido2 = false; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_DEBUG_OPTS + {"dumpable", 0, POPT_ARG_INT, &dumpable, 0, + _("Allow core dumps"), NULL }, + {"backtrace", 0, POPT_ARG_INT, &backtrace, 0, + _("Enable debug backtrace"), NULL }, + {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0, + _("An open file descriptor for the debug logs"), NULL}, + SSSD_LOGGER_OPTS + {"register", 0, POPT_ARG_NONE, NULL, 'r', + _("Register a passkey for a user"), NULL }, + {"authenticate", 0, POPT_ARG_NONE, NULL, 'a', + _("Authenticate a user with a passkey"), NULL }, + {"get-assert", 0, POPT_ARG_NONE, NULL, 'g', + _("Obtain assertion data"), NULL }, + {"get-device-info", 0, POPT_ARG_NONE, NULL, 'i', + _("Obtain device information"), NULL }, + {"verify-assert", 0, POPT_ARG_NONE, NULL, 'v', + _("Verify assertion data"), NULL }, + {"username", 0, POPT_ARG_STRING, &data->shortname, 0, + _("Shortname"), NULL }, + {"domain", 0, POPT_ARG_STRING, &data->domain, 0, + _("Domain"), NULL}, + {"public-key", 0, POPT_ARG_STRING, &public_keys, 0, + _("Public key"), NULL }, + {"key-handle", 0, POPT_ARG_STRING, &key_handles, 0, + _("Key handle"), NULL}, + {"cryptographic-challenge", 0, POPT_ARG_STRING, + &data->crypto_challenge, 0, + _("Cryptographic challenge"), NULL}, + {"auth-data", 0, POPT_ARG_STRING, &data->auth_data, 0, + _("Authenticator data"), NULL}, + {"signature", 0, POPT_ARG_STRING, &data->signature, 0, + _("Signature"), NULL}, + {"type", 0, POPT_ARG_STRING, &type, 0, + _("COSE type to use"), "es256|rs256|eddsa"}, + {"user-verification", 0, POPT_ARG_STRING, &user_verification, 0, + _("Require user-verification"), "true|false"}, + {"cred-type", 0, POPT_ARG_STRING, &cred_type, 0, + _("Credential type"), "server-side|discoverable"}, + {"output-file", 0, POPT_ARG_STRING, &data->mapping_file, 0, + _("Write key mapping data to file"), NULL}, + {"quiet", 0, POPT_ARG_NONE, NULL, 'q', + _("Supress prompts"), NULL}, + {"debug-libfido2", 0, POPT_ARG_NONE, NULL, 'd', + _("Enable debug in libfido2 library"), NULL}, + SSSD_LOGGER_OPTS + POPT_TABLEEND + }; + + /* 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) { + case 'r': + if (data->action != ACTION_NONE + && data->action != ACTION_REGISTER) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_REGISTER; + break; + case 'a': + if (data->action != ACTION_NONE + && data->action != ACTION_AUTHENTICATE) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_AUTHENTICATE; + break; + case 'g': + if (data->action != ACTION_NONE + && data->action != ACTION_GET_ASSERT) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_GET_ASSERT; + break; + case 'i': + if (data->action != ACTION_NONE + && data->action != ACTION_GET_DEVINFO) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_GET_DEVINFO; + break; + case 'v': + if (data->action != ACTION_NONE + && data->action != ACTION_VERIFY_ASSERT) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_VERIFY_ASSERT; + break; + case 'q': + data->quiet = true; + break; + case 'd': + data->debug_libfido2 = true; + break; + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + } + + poptFreeContext(pc); + + sss_prctl_set_dumpable((dumpable == 0) ? 0 : 1); + + if (user_verification != NULL) { + if (strcmp(user_verification, "true") == 0) { + data->user_verification = FIDO_OPT_TRUE; + } else if (strcmp(user_verification, "false") == 0) { + data->user_verification = FIDO_OPT_FALSE; + } else if (user_verification != NULL) { + ERROR("[%s] is not a valid user-verification value.\n", + user_verification); + ret = EINVAL; + goto done; + } + } + + if (type != NULL) { + ret = cose_str_to_int(type, &data->type); + if (ret != EOK) { + ERROR("[%s] is not a valid COSE type (es256, rs256 or eddsa).\n", + type); + goto done; + } + } + + if (public_keys != NULL || key_handles != NULL) { + ret = parse_public_keys_and_handlers(mem_ctx, public_keys, key_handles, + data); + if (ret != EOK) { + goto done; + } + } + + if (cred_type != NULL) { + ret = cred_type_str_to_enum(cred_type, &data->cred_type); + if (ret != EOK) { + ERROR("[%s] is not a valid credential type (server-side or" + " discoverable).\n", + cred_type); + goto done; + } + } + + debug_prg_name = talloc_asprintf(NULL, "passkey_child[%d]", getpid()); + if (debug_prg_name == NULL) { + ERROR("talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + if (debug_fd != -1) { + opt_logger = sss_logger_str[FILES_LOGGER]; + ret = set_debug_file_from_fd(debug_fd); + if (ret != EOK) { + opt_logger = sss_logger_str[STDERR_LOGGER]; + ERROR("set_debug_file_from_fd failed.\n"); + } + } + + DEBUG_INIT(debug_level, opt_logger); + sss_set_debug_backtrace_enable((backtrace == 0) ? false : true); + + ret = EOK; + +done: + return ret; +} + +errno_t +check_arguments(const struct passkey_data *data) +{ + errno_t ret = EOK; + + DEBUG(SSSDBG_TRACE_FUNC, "Argument values after parsing\n"); + DEBUG(SSSDBG_TRACE_FUNC, "action: %d\n", data->action); + DEBUG(SSSDBG_TRACE_FUNC, "shortname: %s, domain: %s\n", + data->shortname, data->domain); + DEBUG(SSSDBG_TRACE_FUNC, "Number of key handles %d\n", + data->key_handle_size); + for (int i = 0; i < data->key_handle_size; i++) { + DEBUG(SSSDBG_TRACE_FUNC, "key %d, key_handle: %s\n", + i + 1, data->key_handle_list[i]); + } + DEBUG(SSSDBG_TRACE_FUNC, "Number of public keys %d\n", + data->public_key_size); + for (int i = 0; i < data->public_key_size; i++) { + DEBUG(SSSDBG_TRACE_FUNC, "key %d, public_key: %s\n", + i + 1, data->public_key_list[i]); + } + DEBUG(SSSDBG_TRACE_FUNC, "cryptographic-challenge: %s\n", + data->crypto_challenge); + DEBUG(SSSDBG_TRACE_FUNC, "auth-data: %s\n", + data->auth_data); + DEBUG(SSSDBG_TRACE_FUNC, "signature: %s\n", + data->signature); + DEBUG(SSSDBG_TRACE_FUNC, "type: %d\n", data->type); + DEBUG(SSSDBG_TRACE_FUNC, "user_verification: %d\n", + data->user_verification); + DEBUG(SSSDBG_TRACE_FUNC, "cred_type: %d\n", + data->cred_type); + DEBUG(SSSDBG_TRACE_FUNC, "Mapping file: %s\n", data->mapping_file); + DEBUG(SSSDBG_TRACE_FUNC, "debug_libfido2: %d\n", data->debug_libfido2); + + if (data->action == ACTION_NONE) { + DEBUG(SSSDBG_OP_FAILURE, "No action set.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + + if (data->action == ACTION_REGISTER + && (data->shortname == NULL || data->domain == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, "Too few arguments for register action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + + if (data->action == ACTION_AUTHENTICATE + && (data->domain == NULL || data->public_key_list == NULL + || data->key_handle_list == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, + "Too few arguments for authenticate action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + + if (data->action == ACTION_GET_DEVINFO + && (data->domain == NULL || data->key_handle_list == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, + "Too few arguments for get-device-info action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + + if (data->action == ACTION_GET_ASSERT + && (data->domain == NULL || data->key_handle_list == NULL + || data->crypto_challenge == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, + "Too few arguments for get-assert action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + + if (data->action == ACTION_VERIFY_ASSERT + && (data->domain == NULL || data->public_key_list == NULL + || data->key_handle_list == NULL || data->crypto_challenge == NULL + || data->auth_data == NULL || data->signature == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, + "Too few arguments for verify-assert action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + +done: + return ret; +} + +errno_t +register_key(struct passkey_data *data) +{ + TALLOC_CTX *tmp_ctx = NULL; + fido_cred_t *cred = NULL; + fido_dev_t *dev = NULL; + fido_dev_info_t *dev_list = NULL; + size_t dev_number = 0; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + data->user_id = talloc_array(tmp_ctx, unsigned char, USER_ID_SIZE); + if (data->user_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array() failed.\n"); + ret = ENOMEM; + goto done; + } + + cred = fido_cred_new(); + if (cred == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_new failed.\n"); + ret = ENOMEM; + goto done; + } + + dev_list = fido_dev_info_new(DEVLIST_SIZE); + if (dev_list == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = list_devices(dev_list, &dev_number); + if (ret != EOK) { + goto done; + } + + ret = select_device(data->action, dev_list, dev_number, NULL, &dev); + if (ret != EOK) { + goto done; + } + + ret = prepare_credentials(data, dev, cred); + if (ret != EOK) { + goto done; + } + + ret = generate_credentials(data, dev, cred); + if (ret != EOK) { + ERROR("A problem occurred while generating the credentials.\n"); + goto done; + } + + ret = verify_credentials(cred); + if (ret != EOK) { + goto done; + } + + ret = print_credentials(data, cred); + if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + fido_cred_free(&cred); + fido_dev_info_free(&dev_list, dev_number); + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +public_key_to_base64(TALLOC_CTX *mem_ctx, const struct passkey_data *data, + const unsigned char *public_key, size_t pk_len, + char **_pem_key) +{ + EVP_PKEY *evp_pkey = NULL; + unsigned char *pub = NULL; + char *pem_key = NULL; + unsigned long err; + errno_t ret; + + if (_pem_key == NULL) { + ret = EINVAL; + goto done; + } + + switch (data->type) { + case COSE_ES256: + ret = es256_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); + break; + case COSE_RS256: + ret = rs256_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); + break; + case COSE_EDDSA: + ret = eddsa_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Invalid key type.\n"); + ret = EINVAL; + break; + } + + if (ret != EOK) { + goto done; + } + + ret = i2d_PUBKEY(evp_pkey, &pub); + if (ret < 1) { + err = ERR_get_error(); + DEBUG(SSSDBG_OP_FAILURE, "i2d_PUBKEY failed [%lu][%s].\n", + err, ERR_error_string(err, NULL)); + ret = EIO; + goto done; + } + + pem_key = sss_base64_encode(mem_ctx, pub, ret); + if (pem_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_encode failed.\n"); + ret = ENOMEM; + goto done; + } + + *_pem_key = pem_key; + ret = EOK; + +done: + free(pub); + + if (evp_pkey != NULL) { + EVP_PKEY_free(evp_pkey); + } + + return ret; +} + +errno_t +select_authenticator(struct passkey_data *data, fido_dev_t **_dev, + fido_assert_t **_assert, int *_index) +{ + fido_dev_info_t *dev_list = NULL; + fido_dev_t *dev = NULL; + size_t dev_list_len = 0; + fido_assert_t *assert = NULL; + int index = 0; + errno_t ret; + + dev_list = fido_dev_info_new(DEVLIST_SIZE); + if (dev_list == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_new failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Checking for devices.\n"); + ret = list_devices(dev_list, &dev_list_len); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "%d key handles provided.\n", + data->key_handle_size); + + while (index < data->key_handle_size) { + DEBUG(SSSDBG_TRACE_FUNC, + "Preparing assert request data with key handle %d.\n", index + 1); + + assert = fido_assert_new(); + if (assert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_assert_new failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Preparing assert request data.\n"); + ret = prepare_assert(data, index, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Selecting device.\n"); + ret = select_device(data->action, dev_list, dev_list_len, assert, &dev); + if (ret == EOK) { + /* Key handle found in device */ + /* update device current configuration + */ + bool has_pin = fido_dev_has_pin(dev); + bool has_uv = fido_dev_has_uv (dev); + int fd = -1; + if (has_pin && has_uv) { + fd = creat("/var/run/passkey-pinuv", 0000); + if (fd < 0) + DEBUG(SSSDBG_TRACE_FUNC, "error creat pinuv errno = %d\n", errno); + close(fd); + } + else { + (void)remove ("/var/run/passkey-pinuv"); + if (has_pin) { + fd = creat("/var/run/passkey-pinonly", 0000); + if (fd < 0) + DEBUG(SSSDBG_TRACE_FUNC, "eror creat pinonly errno = %d\n", errno); + close (fd); + } else { + /* no pin and no uv; */ + (void)remove ("/var/run/passkey-pinonly"); + fido_dev_close(dev); + dev = NULL; + ret = FIDO_ERR_NOTFOUND; + } + } + break; + } + + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + fido_assert_free(&assert); + index++; + } + + *_dev = dev; + *_assert = assert; + *_index = index; + +done: + if (ret != EOK) { + fido_assert_free(&assert); + } + fido_dev_info_free(&dev_list, dev_list_len); + + return ret; +} + +errno_t +public_key_to_libfido2(const char *pem_public_key, struct pk_data_t *_pk_data) +{ + TALLOC_CTX *tmp_ctx = NULL; + const unsigned char *public_key = NULL; + size_t pk_len; + const EVP_PKEY *evp_pkey = NULL; + int base_id; + unsigned long err; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + public_key = sss_base64_decode(tmp_ctx, pem_public_key, &pk_len); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to decode public key.\n"); + ret = ENOMEM; + goto done; + } + + evp_pkey = d2i_PUBKEY(NULL, &public_key, pk_len); + if (evp_pkey == NULL) { + err = ERR_get_error(); + DEBUG(SSSDBG_OP_FAILURE, "d2i_pubkey failed [%lu][%s].\n", + err, ERR_error_string(err, NULL)); + ret = EIO; + goto done; + } + + base_id = get_id(evp_pkey); + if (base_id == EVP_PKEY_EC) { + _pk_data->type = COSE_ES256; + ret = evp_pkey_to_es256_pubkey(evp_pkey, _pk_data); + } else if (base_id == EVP_PKEY_RSA) { + _pk_data->type = COSE_RS256; + ret = evp_pkey_to_rs256_pubkey(evp_pkey, _pk_data); + } else if (base_id == EVP_PKEY_ED25519) { + _pk_data->type = COSE_EDDSA; + ret = evp_pkey_to_eddsa_pubkey(evp_pkey, _pk_data); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Unrecognized key type.\n"); + ret = EINVAL; + } + if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + if (evp_pkey != NULL) { + EVP_PKEY_free(discard_const(evp_pkey)); + } + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +authenticate(struct passkey_data *data) +{ + TALLOC_CTX *tmp_ctx = NULL; + fido_assert_t *assert = NULL; + fido_dev_t *dev = NULL; + struct pk_data_t pk_data = { 0 }; + int index; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ERROR("talloc_new() failed\n"); + return ENOMEM; + } + + ret = select_authenticator(data, &dev, &assert, &index); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Comparing the device and policy options.\n"); + ret = get_device_options(dev, data); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert options.\n"); + ret = set_assert_options(FIDO_OPT_TRUE, data->user_verification, assert); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to reset assert options.\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert client data.\n"); + ret = set_assert_client_data_hash(data, assert); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to reset client data hash.\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Decoding public key.\n"); + ret = public_key_to_libfido2(data->public_key_list[index], &pk_data); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Getting assert.\n"); + ret = request_assert(data, dev, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Verifying assert.\n"); + ret = verify_assert(&pk_data, assert); + if (ret != FIDO_OK) { + goto done; + } + + ret = FIDO_OK; + +done: + reset_public_key(&pk_data); + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + fido_assert_free(&assert); + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +get_assert_data(struct passkey_data *data) +{ + TALLOC_CTX *tmp_ctx = NULL; + fido_dev_t *dev = NULL; + fido_assert_t *assert = NULL; + const char *auth_data = NULL; + const char *signature = NULL; + int index; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + ret = select_authenticator(data, &dev, &assert, &index); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Comparing the device and policy options.\n"); + ret = get_device_options(dev, data); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert options.\n"); + ret = set_assert_options(FIDO_OPT_TRUE, data->user_verification, assert); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to reset assert options.\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Getting assert.\n"); + ret = request_assert(data, dev, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Getting authentication data and signature.\n"); + ret = get_assert_auth_data_signature(tmp_ctx, assert, &auth_data, + &signature); + if (ret != EOK) { + goto done; + } + + print_assert_data(data->key_handle_list[index], data->crypto_challenge, + auth_data, signature); + +done: + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + fido_assert_free(&assert); + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +get_device_info(struct passkey_data *data) +{ + TALLOC_CTX *tmp_ctx = NULL; + fido_dev_t *dev = NULL; + fido_assert_t *assert = NULL; + int index; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + ret = select_authenticator(data, &dev, &assert, &index); + if (ret != EOK) { + goto done; + } + /* unused */ + fido_assert_free(&assert); + + done: + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +verify_assert_data(struct passkey_data *data) +{ + fido_assert_t *assert = NULL; + struct pk_data_t pk_data = { 0 }; + errno_t ret; + + assert = fido_assert_new(); + if (assert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_assert_new failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Preparing assert data.\n"); + ret = prepare_assert(data, 0, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Preparing assert authenticator data and signature.\n"); + ret = set_assert_auth_data_signature(data, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Decoding public key.\n"); + ret = public_key_to_libfido2(data->public_key_list[0], &pk_data); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Verifying assert.\n"); + ret = verify_assert(&pk_data, assert); + if (ret != FIDO_OK) { + goto done; + } + + ret = FIDO_OK; + +done: + reset_public_key(&pk_data); + fido_assert_free(&assert); + + return ret; +} diff --git a/src/passkey_child/passkey_child_credentials.c b/src/passkey_child/passkey_child_credentials.c index 3edce33b452..7aaaf7d6db8 100644 --- a/src/passkey_child/passkey_child_credentials.c +++ b/src/passkey_child/passkey_child_credentials.c @@ -1,690 +1,690 @@ -/* - SSSD - - Helper child to commmunicate with passkey devices - - Authors: - Iker Pedrosa - - Copyright (C) 2022 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 . -*/ - -#include -#include -#include - -#include -#include -#include - -#include "util/crypto/sss_crypto.h" -#include "util/debug.h" -#include "util/util.h" - -#include "passkey_child.h" - -#define IN_BUF_SIZE 1024 - -errno_t -prepare_credentials(struct passkey_data *data, fido_dev_t *dev, - fido_cred_t *cred) -{ - unsigned char cdh[32]; - fido_opt_t rk = FIDO_OPT_OMIT; - bool has_pin; - bool has_uv; - errno_t ret = EOK; - - ret = fido_cred_set_type(cred, data->type); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_type failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - ret = sss_generate_csprng_buffer(cdh, sizeof(cdh)); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "sss_generate_csprng_buffer failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - ret = fido_cred_set_clientdata_hash(cred, cdh, sizeof(cdh)); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_cred_set_clientdata_hash failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Setting Relying Party ID and name to %s.\n", - data->domain); - - ret = fido_cred_set_rp(cred, data->domain, data->domain); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_rp failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - if (data->user_id == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "user_id must be allocated before using it.\n"); - ret = ENOMEM; - goto done; - } - - ret = sss_generate_csprng_buffer(data->user_id, USER_ID_SIZE); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "sss_generate_csprng_buffer failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Setting user: %s.\n", data->shortname); - - ret = fido_cred_set_user(cred, data->user_id, USER_ID_SIZE, - data->shortname, NULL, NULL); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_user failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - if (data->cred_type == CRED_DISCOVERABLE) { - rk = FIDO_OPT_TRUE; - } - - /* Set to FIDO_OPT_OMIT instead of FIDO_OPT_FALSE for compatibility reasons - */ - ret = fido_cred_set_rk(cred, rk); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_rk failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - has_uv = fido_dev_has_uv(dev); - has_pin = fido_dev_has_pin(dev); - if (data->user_verification == FIDO_OPT_TRUE && has_uv == false - && has_pin == false) { - DEBUG(SSSDBG_OP_FAILURE, - "Policy enabled user-verification but there isn't any " - "verification method set.\n"); - ret = EINVAL; - goto done; - } - - if (data->user_verification == FIDO_OPT_FALSE - && (has_uv == true || has_pin == true)) { - DEBUG(SSSDBG_CONF_SETTINGS, - "Policy disabled user-verification but the key settings are " - "enforcing it. Thus, enabling user-verification.\n"); - data->user_verification = FIDO_OPT_TRUE; - } - - if (has_uv == true) { - ret = fido_cred_set_uv(cred, FIDO_OPT_TRUE); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_uv failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - } - -done: - return ret; -} - -errno_t -passkey_recv_pin(TALLOC_CTX *mem_ctx, int fd, char **_pin) -{ - uint8_t buf[IN_BUF_SIZE]; - ssize_t len; - errno_t ret; - char *str; - - errno = 0; - len = sss_atomic_read_s(fd, buf, IN_BUF_SIZE); - if (len == -1) { - ret = errno; - ret = (ret == 0) ? EINVAL: ret; - DEBUG(SSSDBG_CRIT_FAILURE, - "read failed [%d][%s].\n", ret, strerror(ret)); - return ret; - } - - if (len == 0 || *buf == '\0') { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing PIN.\n"); - return EINVAL; - } - - str = talloc_strndup(mem_ctx, (char *) buf, len); - if (str == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n"); - return ENOMEM; - } - - if (strlen(str) != len) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Input contains additional data, only PIN expected.\n"); - talloc_free(str); - return EINVAL; - } - - /* see FIX no PIN in sss_cli.c */ - if ((strncasecmp(str, "null", len) == 0) || - (strncasecmp(str, "NULL", len) == 0)) { - DEBUG(SSSDBG_CRIT_FAILURE, - "%s PIN is received - return EINVAL\n", str); - talloc_free(str); - return EINVAL; - } - - *_pin = str; - - return EOK; -} - -ssize_t -read_pin(char **pin) -{ - char *line_ptr = NULL; - struct termios old, new; - size_t line_len = 0; - ssize_t bytes_read; - ssize_t ret; - - ret = tcgetattr(STDIN_FILENO, &old); - if (ret != 0) { - DEBUG(SSSDBG_OP_FAILURE, - "Unable to get the parameters associated with stdin [%d]: %s.\n", - errno, sss_strerror(errno)); - goto done; - } - new = old; - new.c_lflag &= ~ECHO; - ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &new); - if (ret != 0) { - DEBUG(SSSDBG_OP_FAILURE, "Unable to turn echoing off [%d]: %s.\n", - errno, sss_strerror(errno)); - goto done; - } - - PRINT("Enter PIN:\n"); - fflush(stdout); - bytes_read = getline(&line_ptr, &line_len, stdin); - if (bytes_read == -1) { - DEBUG(SSSDBG_OP_FAILURE, "getline failed [%d]: %s.\n", - errno, sss_strerror(errno)); - } else { - /* Remove the end of line '\n' character */ - line_ptr[--bytes_read] = '\0'; - } - PRINT("\n"); - - ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &old); - if (ret != 0) { - DEBUG(SSSDBG_OP_FAILURE, - "Unable to restore parameters associated with stdin [%d]: %s.\n", - errno, sss_strerror(errno)); - goto done; - } - - ret = bytes_read; - *pin = line_ptr; - -done: - return ret; -} - -errno_t -generate_credentials(struct passkey_data *data, fido_dev_t *dev, - fido_cred_t *cred) -{ - TALLOC_CTX *tmp_ctx = NULL; - char *pin = NULL; - char *tmp_pin = NULL; - bool has_pin; - ssize_t pin_len = 0; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); - return ENOMEM; - } - - has_pin = fido_dev_has_pin(dev); - if (has_pin == true) { - if (data->quiet == true) { - ret = passkey_recv_pin(tmp_ctx, STDIN_FILENO, &pin); - if (ret != EOK) { - goto done; - } - } else { - pin_len = read_pin(&tmp_pin); - if (pin_len == -1) { - ret = ERR_INPUT_PARSE; - goto done; - } - pin = talloc_strdup(tmp_ctx, tmp_pin); - sss_erase_mem_securely(tmp_pin, pin_len); - free(tmp_pin); - } - } - - if (data->quiet == false) { - PRINT("Please touch the device.\n"); - fflush(stdout); - } - ret = fido_dev_make_cred(dev, cred, pin); - sss_erase_mem_securely(pin, pin_len); - - if (ret != FIDO_OK) { - if (ret == FIDO_ERR_PIN_INVALID) { - ERROR("Invalid PIN.\n"); - } - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_make_cred failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - if (has_pin == true) { - ret = fido_cred_set_uv(cred, FIDO_OPT_TRUE); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_uv failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - } - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -verify_credentials(const fido_cred_t *const cred) -{ - errno_t ret; - - if (fido_cred_x5c_ptr(cred) != NULL) { - ret = fido_cred_verify(cred); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_verify failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - } else { - DEBUG(SSSDBG_TRACE_FUNC, - "Attestation certificate missing. " - "Falling back to self attestation.\n"); - ret = fido_cred_verify_self(cred); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_cred_verify_self failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - } - -done: - return ret; -} - -errno_t -print_credentials(const struct passkey_data *data, - const fido_cred_t *const cred) -{ - TALLOC_CTX *tmp_ctx = NULL; - const unsigned char *cred_id = NULL; - const unsigned char *public_key = NULL; - const char *b64_cred_id = NULL; - char *pem_key = NULL; - size_t cred_id_len; - size_t user_key_len; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - cred_id = fido_cred_id_ptr(cred); - if (cred_id == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_id_ptr failed.\n"); - ret = ERR_CREDS_INVALID; - goto done; - } - - cred_id_len = fido_cred_id_len(cred); - if (cred_id_len == 0) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_id_len returned 0.\n"); - ret = ERR_CREDS_INVALID; - goto done; - } - - b64_cred_id = sss_base64_encode(tmp_ctx, cred_id, cred_id_len); - if (b64_cred_id == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "failed to encode key handle.\n"); - ret = ENOMEM; - goto done; - } - - public_key = fido_cred_pubkey_ptr(cred); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_pubkey_ptr failed.\n"); - ret = ERR_CREDS_INVALID; - goto done; - } - - user_key_len = fido_cred_pubkey_len(cred); - if (user_key_len == 0) { - DEBUG(SSSDBG_OP_FAILURE, "fido_cred_pubkey_len returned 0.\n"); - ret = ERR_CREDS_INVALID; - goto done; - } - - ret = public_key_to_base64(tmp_ctx, data, public_key, user_key_len, - &pem_key); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "failed to format public key to b64 [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - PRINT("passkey:%s,%s\n", b64_cred_id, pem_key); - if (data->mapping_file != NULL) { - print_credentials_to_file(data, b64_cred_id, pem_key); - } - ret = EOK; - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t -print_credentials_to_file(const struct passkey_data *data, - const char *b64_cred_id, - const char *pem_key) -{ - TALLOC_CTX *tmp_ctx = NULL; - char *mapping_data = NULL; - int mapping_data_len = 0; - int fd = -1; - ssize_t written = 0; - errno_t ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - mapping_data = talloc_asprintf(tmp_ctx, "passkey:%s,%s", - b64_cred_id, pem_key); - if (mapping_data == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); - ret = ENOMEM; - goto done; - } - mapping_data_len = strlen(mapping_data); - - fd = open(data->mapping_file, O_WRONLY|O_CREAT, 0640); - if (fd == -1) { - ret = errno; - DEBUG(SSSDBG_OP_FAILURE, - "open() failed [%d][%s]\n", ret, strerror(ret)); - ret = EIO; - goto done; - } - - errno = 0; - written = sss_atomic_write_s(fd, mapping_data, mapping_data_len); - if (written == -1) { - ret = errno; - DEBUG(SSSDBG_OP_FAILURE, - "Write failed [%d][%s].\n", ret, strerror(ret)); - goto done; - } - - if (written != mapping_data_len) { - DEBUG(SSSDBG_OP_FAILURE, - "Write error, wrote [%zd] bytes, expected [%d]\n", - written, mapping_data_len); - ret = EIO; - goto done; - } - - ret = EOK; - -done: - if (fd != -1) { - if (close(fd) == -1) { - DEBUG(SSSDBG_OP_FAILURE, - "Close failed [%s].\n", strerror(errno)); - } - } - talloc_free(tmp_ctx); - - return ret; -} - -int -es256_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *es256_key, - size_t es256_key_len, EVP_PKEY **_evp_pkey) -{ - EVP_PKEY *evp_pkey = NULL; - es256_pk_t *public_key = NULL; - errno_t ret; - - public_key = es256_pk_new(); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "es256_pk_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = es256_pk_from_ptr(public_key, es256_key, es256_key_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "es256_pk_from_ptr failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - evp_pkey = es256_pk_to_EVP_PKEY(public_key); - if (evp_pkey == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "es256_pk_to_EVP_PKEY failed.\n"); - ret = ENOMEM; - goto done; - } - - *_evp_pkey = evp_pkey; - ret = EOK; - -done: - es256_pk_free(&public_key); - - return ret; -} - -int -rs256_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *rs256_key, - size_t rs256_key_len, EVP_PKEY **_evp_pkey) -{ - EVP_PKEY *evp_pkey = NULL; - rs256_pk_t *public_key = NULL; - errno_t ret; - - public_key = rs256_pk_new(); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = rs256_pk_from_ptr(public_key, rs256_key, rs256_key_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_from_ptr failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - evp_pkey = rs256_pk_to_EVP_PKEY(public_key); - if (evp_pkey == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_to_EVP_PKEY failed.\n"); - ret = ENOMEM; - goto done; - } - - *_evp_pkey = evp_pkey; - ret = EOK; - -done: - rs256_pk_free(&public_key); - - return ret; -} - -int -eddsa_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *eddsa_key, - size_t eddsa_key_len, EVP_PKEY **_evp_pkey) -{ - EVP_PKEY *evp_pkey = NULL; - eddsa_pk_t *public_key = NULL; - errno_t ret; - - public_key = eddsa_pk_new(); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = eddsa_pk_from_ptr(public_key, eddsa_key, eddsa_key_len); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_from_ptr failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - evp_pkey = eddsa_pk_to_EVP_PKEY(public_key); - if (evp_pkey == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_to_EVP_PKEY failed.\n"); - ret = ENOMEM; - goto done; - } - - *_evp_pkey = evp_pkey; - ret = EOK; - -done: - eddsa_pk_free(&public_key); - - return ret; -} - -errno_t -evp_pkey_to_es256_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) -{ - void *public_key = NULL; - errno_t ret; - - public_key = es256_pk_new(); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "es256_pk_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = es256_pk_from_EVP_PKEY(public_key, evp_pkey); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "es256_pk_from_EVP_PKEY failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - _pk_data->public_key = public_key; - ret = EOK; - -done: - return ret; -} - -errno_t -evp_pkey_to_rs256_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) -{ - void *public_key = NULL; - errno_t ret; - - public_key = rs256_pk_new(); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = rs256_pk_from_EVP_PKEY(public_key, evp_pkey); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "rs256_pk_from_EVP_PKEY failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - _pk_data->public_key = public_key; - ret = EOK; - -done: - return ret; -} - -errno_t -evp_pkey_to_eddsa_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) -{ - void *public_key = NULL; - errno_t ret; - - public_key = eddsa_pk_new(); - if (public_key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_new failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = eddsa_pk_from_EVP_PKEY(public_key, evp_pkey); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "eddsa_pk_from_EVP_PKEY failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - _pk_data->public_key = public_key; - ret = EOK; - -done: - return ret; -} +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa + + Copyright (C) 2022 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 . +*/ + +#include +#include +#include + +#include +#include +#include + +#include "util/crypto/sss_crypto.h" +#include "util/debug.h" +#include "util/util.h" + +#include "passkey_child.h" + +#define IN_BUF_SIZE 1024 + +errno_t +prepare_credentials(struct passkey_data *data, fido_dev_t *dev, + fido_cred_t *cred) +{ + unsigned char cdh[32]; + fido_opt_t rk = FIDO_OPT_OMIT; + bool has_pin; + bool has_uv; + errno_t ret = EOK; + + ret = fido_cred_set_type(cred, data->type); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_type failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + ret = sss_generate_csprng_buffer(cdh, sizeof(cdh)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_generate_csprng_buffer failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + ret = fido_cred_set_clientdata_hash(cred, cdh, sizeof(cdh)); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_cred_set_clientdata_hash failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Setting Relying Party ID and name to %s.\n", + data->domain); + + ret = fido_cred_set_rp(cred, data->domain, data->domain); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_rp failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + if (data->user_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "user_id must be allocated before using it.\n"); + ret = ENOMEM; + goto done; + } + + ret = sss_generate_csprng_buffer(data->user_id, USER_ID_SIZE); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_generate_csprng_buffer failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Setting user: %s.\n", data->shortname); + + ret = fido_cred_set_user(cred, data->user_id, USER_ID_SIZE, + data->shortname, NULL, NULL); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_user failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + if (data->cred_type == CRED_DISCOVERABLE) { + rk = FIDO_OPT_TRUE; + } + + /* Set to FIDO_OPT_OMIT instead of FIDO_OPT_FALSE for compatibility reasons + */ + ret = fido_cred_set_rk(cred, rk); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_rk failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + has_uv = fido_dev_has_uv(dev); + has_pin = fido_dev_has_pin(dev); + if (data->user_verification == FIDO_OPT_TRUE && has_uv == false + && has_pin == false) { + DEBUG(SSSDBG_OP_FAILURE, + "Policy enabled user-verification but there isn't any " + "verification method set.\n"); + ret = EINVAL; + goto done; + } + + if (data->user_verification == FIDO_OPT_FALSE + && (has_uv == true || has_pin == true)) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Policy disabled user-verification but the key settings are " + "enforcing it. Thus, enabling user-verification.\n"); + data->user_verification = FIDO_OPT_TRUE; + } + + if (has_uv == true) { + ret = fido_cred_set_uv(cred, FIDO_OPT_TRUE); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_uv failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } + +done: + return ret; +} + +errno_t +passkey_recv_pin(TALLOC_CTX *mem_ctx, int fd, char **_pin) +{ + uint8_t buf[IN_BUF_SIZE]; + ssize_t len; + errno_t ret; + char *str; + + errno = 0; + len = sss_atomic_read_s(fd, buf, IN_BUF_SIZE); + if (len == -1) { + ret = errno; + ret = (ret == 0) ? EINVAL: ret; + DEBUG(SSSDBG_CRIT_FAILURE, + "read failed [%d][%s].\n", ret, strerror(ret)); + return ret; + } + + if (len == 0 || *buf == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing PIN.\n"); + return EINVAL; + } + + str = talloc_strndup(mem_ctx, (char *) buf, len); + if (str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n"); + return ENOMEM; + } + + if (strlen(str) != len) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Input contains additional data, only PIN expected.\n"); + talloc_free(str); + return EINVAL; + } + + /* see FIX no PIN in sss_cli.c */ + if ((strncasecmp(str, "null", len) == 0) || + (strncasecmp(str, "NULL", len) == 0)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "%s PIN is received - return EINVAL\n", str); + talloc_free(str); + return EINVAL; + } + + *_pin = str; + + return EOK; +} + +ssize_t +read_pin(char **pin) +{ + char *line_ptr = NULL; + struct termios old, new; + size_t line_len = 0; + ssize_t bytes_read; + ssize_t ret; + + ret = tcgetattr(STDIN_FILENO, &old); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to get the parameters associated with stdin [%d]: %s.\n", + errno, sss_strerror(errno)); + goto done; + } + new = old; + new.c_lflag &= ~ECHO; + ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &new); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to turn echoing off [%d]: %s.\n", + errno, sss_strerror(errno)); + goto done; + } + + PRINT("Enter PIN:\n"); + fflush(stdout); + bytes_read = getline(&line_ptr, &line_len, stdin); + if (bytes_read == -1) { + DEBUG(SSSDBG_OP_FAILURE, "getline failed [%d]: %s.\n", + errno, sss_strerror(errno)); + } else { + /* Remove the end of line '\n' character */ + line_ptr[--bytes_read] = '\0'; + } + PRINT("\n"); + + ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &old); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to restore parameters associated with stdin [%d]: %s.\n", + errno, sss_strerror(errno)); + goto done; + } + + ret = bytes_read; + *pin = line_ptr; + +done: + return ret; +} + +errno_t +generate_credentials(struct passkey_data *data, fido_dev_t *dev, + fido_cred_t *cred) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *pin = NULL; + char *tmp_pin = NULL; + bool has_pin; + ssize_t pin_len = 0; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + has_pin = fido_dev_has_pin(dev); + if (has_pin == true) { + if (data->quiet == true) { + ret = passkey_recv_pin(tmp_ctx, STDIN_FILENO, &pin); + if (ret != EOK) { + goto done; + } + } else { + pin_len = read_pin(&tmp_pin); + if (pin_len == -1) { + ret = ERR_INPUT_PARSE; + goto done; + } + pin = talloc_strdup(tmp_ctx, tmp_pin); + sss_erase_mem_securely(tmp_pin, pin_len); + free(tmp_pin); + } + } + + if (data->quiet == false) { + PRINT("Please touch the device.\n"); + fflush(stdout); + } + ret = fido_dev_make_cred(dev, cred, pin); + sss_erase_mem_securely(pin, pin_len); + + if (ret != FIDO_OK) { + if (ret == FIDO_ERR_PIN_INVALID) { + ERROR("Invalid PIN.\n"); + } + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_make_cred failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + if (has_pin == true) { + ret = fido_cred_set_uv(cred, FIDO_OPT_TRUE); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_uv failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +verify_credentials(const fido_cred_t *const cred) +{ + errno_t ret; + + if (fido_cred_x5c_ptr(cred) != NULL) { + ret = fido_cred_verify(cred); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_verify failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Attestation certificate missing. " + "Falling back to self attestation.\n"); + ret = fido_cred_verify_self(cred); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_cred_verify_self failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } + +done: + return ret; +} + +errno_t +print_credentials(const struct passkey_data *data, + const fido_cred_t *const cred) +{ + TALLOC_CTX *tmp_ctx = NULL; + const unsigned char *cred_id = NULL; + const unsigned char *public_key = NULL; + const char *b64_cred_id = NULL; + char *pem_key = NULL; + size_t cred_id_len; + size_t user_key_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + cred_id = fido_cred_id_ptr(cred); + if (cred_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_id_ptr failed.\n"); + ret = ERR_CREDS_INVALID; + goto done; + } + + cred_id_len = fido_cred_id_len(cred); + if (cred_id_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_id_len returned 0.\n"); + ret = ERR_CREDS_INVALID; + goto done; + } + + b64_cred_id = sss_base64_encode(tmp_ctx, cred_id, cred_id_len); + if (b64_cred_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to encode key handle.\n"); + ret = ENOMEM; + goto done; + } + + public_key = fido_cred_pubkey_ptr(cred); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_pubkey_ptr failed.\n"); + ret = ERR_CREDS_INVALID; + goto done; + } + + user_key_len = fido_cred_pubkey_len(cred); + if (user_key_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_pubkey_len returned 0.\n"); + ret = ERR_CREDS_INVALID; + goto done; + } + + ret = public_key_to_base64(tmp_ctx, data, public_key, user_key_len, + &pem_key); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "failed to format public key to b64 [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + PRINT("passkey:%s,%s\n", b64_cred_id, pem_key); + if (data->mapping_file != NULL) { + print_credentials_to_file(data, b64_cred_id, pem_key); + } + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +print_credentials_to_file(const struct passkey_data *data, + const char *b64_cred_id, + const char *pem_key) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *mapping_data = NULL; + int mapping_data_len = 0; + int fd = -1; + ssize_t written = 0; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + mapping_data = talloc_asprintf(tmp_ctx, "passkey:%s,%s", + b64_cred_id, pem_key); + if (mapping_data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + mapping_data_len = strlen(mapping_data); + + fd = open(data->mapping_file, O_WRONLY|O_CREAT, 0640); + if (fd == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "open() failed [%d][%s]\n", ret, strerror(ret)); + ret = EIO; + goto done; + } + + errno = 0; + written = sss_atomic_write_s(fd, mapping_data, mapping_data_len); + if (written == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "Write failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + if (written != mapping_data_len) { + DEBUG(SSSDBG_OP_FAILURE, + "Write error, wrote [%zd] bytes, expected [%d]\n", + written, mapping_data_len); + ret = EIO; + goto done; + } + + ret = EOK; + +done: + if (fd != -1) { + if (close(fd) == -1) { + DEBUG(SSSDBG_OP_FAILURE, + "Close failed [%s].\n", strerror(errno)); + } + } + talloc_free(tmp_ctx); + + return ret; +} + +int +es256_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *es256_key, + size_t es256_key_len, EVP_PKEY **_evp_pkey) +{ + EVP_PKEY *evp_pkey = NULL; + es256_pk_t *public_key = NULL; + errno_t ret; + + public_key = es256_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "es256_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = es256_pk_from_ptr(public_key, es256_key, es256_key_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "es256_pk_from_ptr failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + evp_pkey = es256_pk_to_EVP_PKEY(public_key); + if (evp_pkey == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "es256_pk_to_EVP_PKEY failed.\n"); + ret = ENOMEM; + goto done; + } + + *_evp_pkey = evp_pkey; + ret = EOK; + +done: + es256_pk_free(&public_key); + + return ret; +} + +int +rs256_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *rs256_key, + size_t rs256_key_len, EVP_PKEY **_evp_pkey) +{ + EVP_PKEY *evp_pkey = NULL; + rs256_pk_t *public_key = NULL; + errno_t ret; + + public_key = rs256_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = rs256_pk_from_ptr(public_key, rs256_key, rs256_key_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_from_ptr failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + evp_pkey = rs256_pk_to_EVP_PKEY(public_key); + if (evp_pkey == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_to_EVP_PKEY failed.\n"); + ret = ENOMEM; + goto done; + } + + *_evp_pkey = evp_pkey; + ret = EOK; + +done: + rs256_pk_free(&public_key); + + return ret; +} + +int +eddsa_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *eddsa_key, + size_t eddsa_key_len, EVP_PKEY **_evp_pkey) +{ + EVP_PKEY *evp_pkey = NULL; + eddsa_pk_t *public_key = NULL; + errno_t ret; + + public_key = eddsa_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = eddsa_pk_from_ptr(public_key, eddsa_key, eddsa_key_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_from_ptr failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + evp_pkey = eddsa_pk_to_EVP_PKEY(public_key); + if (evp_pkey == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_to_EVP_PKEY failed.\n"); + ret = ENOMEM; + goto done; + } + + *_evp_pkey = evp_pkey; + ret = EOK; + +done: + eddsa_pk_free(&public_key); + + return ret; +} + +errno_t +evp_pkey_to_es256_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) +{ + void *public_key = NULL; + errno_t ret; + + public_key = es256_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "es256_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = es256_pk_from_EVP_PKEY(public_key, evp_pkey); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "es256_pk_from_EVP_PKEY failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + _pk_data->public_key = public_key; + ret = EOK; + +done: + return ret; +} + +errno_t +evp_pkey_to_rs256_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) +{ + void *public_key = NULL; + errno_t ret; + + public_key = rs256_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = rs256_pk_from_EVP_PKEY(public_key, evp_pkey); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "rs256_pk_from_EVP_PKEY failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + _pk_data->public_key = public_key; + ret = EOK; + +done: + return ret; +} + +errno_t +evp_pkey_to_eddsa_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) +{ + void *public_key = NULL; + errno_t ret; + + public_key = eddsa_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = eddsa_pk_from_EVP_PKEY(public_key, evp_pkey); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "eddsa_pk_from_EVP_PKEY failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + _pk_data->public_key = public_key; + ret = EOK; + +done: + return ret; +} diff --git a/src/passkey_child/passkey_child_devices.c b/src/passkey_child/passkey_child_devices.c index 74e0559e49a..2011b12f661 100644 --- a/src/passkey_child/passkey_child_devices.c +++ b/src/passkey_child/passkey_child_devices.c @@ -1,246 +1,246 @@ -/* - SSSD - - Helper child to commmunicate with passkey devices - - Authors: - Iker Pedrosa - - Copyright (C) 2022 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 . -*/ - -#include "util/debug.h" -#include "util/util.h" - -#include "passkey_child.h" - -errno_t -list_devices(fido_dev_info_t *dev_list, size_t *dev_number) -{ - errno_t ret; - - for (int i = 0; i < TIMEOUT; i += FREQUENCY) { - ret = fido_dev_info_manifest(dev_list, DEVLIST_SIZE, dev_number); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "Unable to discover device(s) [%d]: %s.\n", - ret, fido_strerr(ret)); - } - - if ((*dev_number) != 0) { - DEBUG(SSSDBG_TRACE_FUNC, "Device found.\n"); - break; - } - - if (i < (TIMEOUT - 1)) { - DEBUG(SSSDBG_TRACE_FUNC, "No device available, retrying.\n"); - sleep(FREQUENCY); - } - } - - return ret; -} - -errno_t -select_device(enum action_opt action, fido_dev_info_t *dev_list, - size_t dev_list_len, fido_assert_t *assert, - fido_dev_t **_dev) -{ - fido_dev_t *dev = NULL; - const char *path; - const fido_dev_info_t *di = NULL; - errno_t ret; - - if (dev_list_len == 0) { - DEBUG(SSSDBG_OP_FAILURE, "No device found. Aborting.\n"); - ret = ENOENT; - goto done; - } else if (action == ACTION_REGISTER && dev_list_len == 1) { - dev = fido_dev_new(); - if (dev == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_new failed.\n"); - ret = ENOMEM; - goto done; - } - - di = fido_dev_info_ptr(dev_list, 0); - if (di == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_ptr failed.\n"); - ret = ENOMEM; - goto done; - } - - path = fido_dev_info_path(di); - if (path == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_path failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = fido_dev_open(dev, path); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_open failed [%d]: %s.\n", - ret, fido_strerr(ret)); - goto done; - } - - *_dev = dev; - } else if (action == ACTION_REGISTER && dev_list_len > 1) { - DEBUG(SSSDBG_OP_FAILURE, - "Only one device is supported at a time. Aborting.\n"); - fprintf(stderr, "Only one device is supported at a time. Aborting.\n"); - ret = EPERM; - goto done; - } else { - if (assert == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "assert cannot be NULL.\n"); - ret = EINVAL; - goto done; - } - - ret = select_from_multiple_devices(dev_list, dev_list_len, assert, _dev); - if (ret != FIDO_OK) { - goto done; - } - } - - ret = EOK; - -done: - if (ret != EOK) { - if (dev != NULL) { - fido_dev_close(dev); - } - fido_dev_free(&dev); - } - - return ret; -} - -errno_t -select_from_multiple_devices(fido_dev_info_t *dev_list, - size_t dev_list_len, - fido_assert_t *assert, - fido_dev_t **_dev) -{ - fido_dev_t *dev = NULL; - const fido_dev_info_t *di = NULL; - const char *path; - bool is_fido2; - errno_t ret; - - DEBUG(SSSDBG_TRACE_FUNC, - "Working with %ld authenticator(s).\n", dev_list_len); - - for (size_t i = 0; i < dev_list_len; i++) { - dev = fido_dev_new(); - if (dev == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_new failed.\n"); - ret = ENOMEM; - goto done; - } - - di = fido_dev_info_ptr(dev_list, i); - if (di == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_ptr failed.\n"); - ret = ENOMEM; - goto done; - } - - path = fido_dev_info_path(di); - if (path == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_path failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = fido_dev_open(dev, path); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, "fido_dev_open failed [%d]: %s.\n", - ret, fido_strerr(ret)); - } - - is_fido2 = fido_dev_is_fido2(dev); - ret = fido_dev_get_assert(dev, assert, NULL); - if ((is_fido2 == false && ret == FIDO_ERR_USER_PRESENCE_REQUIRED) - || (is_fido2 == true && ret == FIDO_OK)) { - *_dev = dev; - DEBUG(SSSDBG_FUNC_DATA, "Assertion found in passkey %ld.\n", i); - ret = EOK; - goto done; - } - - DEBUG(SSSDBG_FUNC_DATA, "Assertion not found in passkey %ld.\n", i); - - fido_dev_close(dev); - fido_dev_free(&dev); - } - - ret = FIDO_ERR_NOTFOUND; - DEBUG(SSSDBG_OP_FAILURE, "Assertion not found.\n"); - -done: - return ret; -} - -errno_t -get_device_options(fido_dev_t *dev, struct passkey_data *_data) -{ - bool has_pin; - bool has_uv; - bool supports_uv; - errno_t ret; - - has_uv = fido_dev_has_uv(dev); - has_pin = fido_dev_has_pin(dev); - supports_uv = fido_dev_supports_uv(dev); - - if (_data->user_verification == FIDO_OPT_TRUE && has_pin != true - && has_uv != true) { - DEBUG(SSSDBG_OP_FAILURE, - "Policy enabled user-verification but there isn't any " - "verification method set.\n"); - ret = EINVAL; - goto done; - } - - if (_data->user_verification == FIDO_OPT_OMIT - && (has_uv == true || has_pin == true)) { - DEBUG(SSSDBG_CONF_SETTINGS, - "Policy didn't indicate any preference for user-verification " - "but the key settings are enforcing it. Thus, enforcing the " - "user-verification.\n"); - _data->user_verification = FIDO_OPT_TRUE; - ret = EOK; - goto done; - } - - if (_data->user_verification == FIDO_OPT_FALSE - && supports_uv == false) { - DEBUG(SSSDBG_CONF_SETTINGS, - "Policy disabled user-verification but the key doesn't support " - "it. Thus, omitting the user-verification.\n"); - _data->user_verification = FIDO_OPT_OMIT; - ret = EOK; - goto done; - } - - ret = EOK; - -done: - - return ret; -} +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa + + Copyright (C) 2022 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 . +*/ + +#include "util/debug.h" +#include "util/util.h" + +#include "passkey_child.h" + +errno_t +list_devices(fido_dev_info_t *dev_list, size_t *dev_number) +{ + errno_t ret; + + for (int i = 0; i < TIMEOUT; i += FREQUENCY) { + ret = fido_dev_info_manifest(dev_list, DEVLIST_SIZE, dev_number); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to discover device(s) [%d]: %s.\n", + ret, fido_strerr(ret)); + } + + if ((*dev_number) != 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Device found.\n"); + break; + } + + if (i < (TIMEOUT - 1)) { + DEBUG(SSSDBG_TRACE_FUNC, "No device available, retrying.\n"); + sleep(FREQUENCY); + } + } + + return ret; +} + +errno_t +select_device(enum action_opt action, fido_dev_info_t *dev_list, + size_t dev_list_len, fido_assert_t *assert, + fido_dev_t **_dev) +{ + fido_dev_t *dev = NULL; + const char *path; + const fido_dev_info_t *di = NULL; + errno_t ret; + + if (dev_list_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "No device found. Aborting.\n"); + ret = ENOENT; + goto done; + } else if (action == ACTION_REGISTER && dev_list_len == 1) { + dev = fido_dev_new(); + if (dev == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_new failed.\n"); + ret = ENOMEM; + goto done; + } + + di = fido_dev_info_ptr(dev_list, 0); + if (di == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_ptr failed.\n"); + ret = ENOMEM; + goto done; + } + + path = fido_dev_info_path(di); + if (path == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_path failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_dev_open(dev, path); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_open failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + *_dev = dev; + } else if (action == ACTION_REGISTER && dev_list_len > 1) { + DEBUG(SSSDBG_OP_FAILURE, + "Only one device is supported at a time. Aborting.\n"); + fprintf(stderr, "Only one device is supported at a time. Aborting.\n"); + ret = EPERM; + goto done; + } else { + if (assert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "assert cannot be NULL.\n"); + ret = EINVAL; + goto done; + } + + ret = select_from_multiple_devices(dev_list, dev_list_len, assert, _dev); + if (ret != FIDO_OK) { + goto done; + } + } + + ret = EOK; + +done: + if (ret != EOK) { + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + } + + return ret; +} + +errno_t +select_from_multiple_devices(fido_dev_info_t *dev_list, + size_t dev_list_len, + fido_assert_t *assert, + fido_dev_t **_dev) +{ + fido_dev_t *dev = NULL; + const fido_dev_info_t *di = NULL; + const char *path; + bool is_fido2; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, + "Working with %ld authenticator(s).\n", dev_list_len); + + for (size_t i = 0; i < dev_list_len; i++) { + dev = fido_dev_new(); + if (dev == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_new failed.\n"); + ret = ENOMEM; + goto done; + } + + di = fido_dev_info_ptr(dev_list, i); + if (di == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_ptr failed.\n"); + ret = ENOMEM; + goto done; + } + + path = fido_dev_info_path(di); + if (path == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_path failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_dev_open(dev, path); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_open failed [%d]: %s.\n", + ret, fido_strerr(ret)); + } + + is_fido2 = fido_dev_is_fido2(dev); + ret = fido_dev_get_assert(dev, assert, NULL); + if ((is_fido2 == false && ret == FIDO_ERR_USER_PRESENCE_REQUIRED) + || (is_fido2 == true && ret == FIDO_OK)) { + *_dev = dev; + DEBUG(SSSDBG_FUNC_DATA, "Assertion found in passkey %ld.\n", i); + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Assertion not found in passkey %ld.\n", i); + + fido_dev_close(dev); + fido_dev_free(&dev); + } + + ret = FIDO_ERR_NOTFOUND; + DEBUG(SSSDBG_OP_FAILURE, "Assertion not found.\n"); + +done: + return ret; +} + +errno_t +get_device_options(fido_dev_t *dev, struct passkey_data *_data) +{ + bool has_pin; + bool has_uv; + bool supports_uv; + errno_t ret; + + has_uv = fido_dev_has_uv(dev); + has_pin = fido_dev_has_pin(dev); + supports_uv = fido_dev_supports_uv(dev); + + if (_data->user_verification == FIDO_OPT_TRUE && has_pin != true + && has_uv != true) { + DEBUG(SSSDBG_OP_FAILURE, + "Policy enabled user-verification but there isn't any " + "verification method set.\n"); + ret = EINVAL; + goto done; + } + + if (_data->user_verification == FIDO_OPT_OMIT + && (has_uv == true || has_pin == true)) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Policy didn't indicate any preference for user-verification " + "but the key settings are enforcing it. Thus, enforcing the " + "user-verification.\n"); + _data->user_verification = FIDO_OPT_TRUE; + ret = EOK; + goto done; + } + + if (_data->user_verification == FIDO_OPT_FALSE + && supports_uv == false) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Policy disabled user-verification but the key doesn't support " + "it. Thus, omitting the user-verification.\n"); + _data->user_verification = FIDO_OPT_OMIT; + ret = EOK; + goto done; + } + + ret = EOK; + +done: + + return ret; +} diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c index 26699dbd3f8..c0348f9c9fe 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -1,3225 +1,3225 @@ -/* - SSSD - - PAM Responder - - Copyright (C) Simo Sorce 2009 - Copyright (C) Sumit Bose 2009 - - 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 . -*/ - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include - -#include "util/util.h" -#include "util/auth_utils.h" -#include "util/find_uid.h" -#include "util/sss_ptr_hash.h" -#include "db/sysdb.h" - -#include "confdb/confdb.h" -#include "responder/common/responder_packet.h" -#include "responder/common/responder.h" -#include "responder/common/negcache.h" -#include "providers/data_provider.h" -#include "responder/pam/pamsrv.h" -#include "responder/pam/pamsrv_passkey.h" -#include "responder/pam/pam_helpers.h" -#include "responder/common/cache_req/cache_req.h" - -enum pam_verbosity { - PAM_VERBOSITY_NO_MESSAGES = 0, - PAM_VERBOSITY_IMPORTANT, - PAM_VERBOSITY_INFO, - PAM_VERBOSITY_DEBUG -}; - -#define DEFAULT_PAM_VERBOSITY PAM_VERBOSITY_IMPORTANT - -struct pam_initgroup_enum_str { - enum pam_initgroups_scheme scheme; - const char *option; -}; - -struct pam_initgroup_enum_str pam_initgroup_enum_str[] = { - { PAM_INITGR_NEVER, "never" }, - { PAM_INITGR_NO_SESSION, "no_session" }, - { PAM_INITGR_ALWAYS, "always" }, - { PAM_INITGR_INVALID, NULL } -}; - -enum pam_initgroups_scheme pam_initgroups_string_to_enum(const char *str) -{ - size_t c; - - for (c = 0 ; pam_initgroup_enum_str[c].option != NULL; c++) { - if (strcasecmp(pam_initgroup_enum_str[c].option, str) == 0) { - return pam_initgroup_enum_str[c].scheme; - } - } - - return PAM_INITGR_INVALID; -} - -const char *pam_initgroup_enum_to_string(enum pam_initgroups_scheme scheme) -{ - size_t c; - - for (c = 0 ; pam_initgroup_enum_str[c].option != NULL; c++) { - if (pam_initgroup_enum_str[c].scheme == scheme) { - return pam_initgroup_enum_str[c].option; - } - } - - return "(NULL)"; -} - -static errno_t -pam_null_last_online_auth_with_curr_token(struct sss_domain_info *domain, - const char *username); -static errno_t -pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain, - const char *name, - uint64_t *_value); - -void pam_reply(struct pam_auth_req *preq); - -static errno_t check_cert(TALLOC_CTX *mctx, - struct tevent_context *ev, - struct pam_ctx *pctx, - struct pam_auth_req *preq, - struct pam_data *pd); - -int pam_check_user_done(struct pam_auth_req *preq, int ret); - -static errno_t pack_user_info_msg(TALLOC_CTX *mem_ctx, - const char *user_error_message, - size_t *resp_len, - uint8_t **_resp) -{ - uint32_t resp_type = SSS_PAM_USER_INFO_ACCOUNT_EXPIRED; - size_t err_len; - uint8_t *resp; - size_t p; - - err_len = strlen(user_error_message); - *resp_len = 2 * sizeof(uint32_t) + err_len; - resp = talloc_size(mem_ctx, *resp_len); - if (resp == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); - return ENOMEM; - } - - p = 0; - SAFEALIGN_SET_UINT32(&resp[p], resp_type, &p); - SAFEALIGN_SET_UINT32(&resp[p], err_len, &p); - safealign_memcpy(&resp[p], user_error_message, err_len, &p); - if (p != *resp_len) { - DEBUG(SSSDBG_FATAL_FAILURE, "Size mismatch\n"); - } - - *_resp = resp; - return EOK; -} - -static void inform_user(struct pam_data* pd, const char *pam_message) -{ - size_t msg_len; - uint8_t *msg; - errno_t ret; - - ret = pack_user_info_msg(pd, pam_message, &msg_len, &msg); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "pack_user_info_msg failed.\n"); - } else { - ret = pam_add_response(pd, SSS_PAM_USER_INFO, msg_len, msg); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); - } - } -} - -static bool is_domain_requested(struct pam_data *pd, const char *domain_name) -{ - int i; - - /* If none specific domains got requested via pam, all domains are allowed. - * Which mimics the default/original behaviour. - */ - if (!pd->requested_domains) { - return true; - } - - for (i = 0; pd->requested_domains[i]; i++) { - if (strcasecmp(domain_name, pd->requested_domains[i])) { - continue; - } - - return true; - } - - return false; -} - -static int extract_authtok_v2(struct sss_auth_token *tok, - size_t data_size, uint8_t *body, size_t blen, - size_t *c) -{ - uint32_t auth_token_type; - uint32_t auth_token_length; - uint8_t *auth_token_data; - int ret = EOK; - - if (data_size < sizeof(uint32_t) || *c+data_size > blen || - SIZE_T_OVERFLOW(*c, data_size)) return EINVAL; - - SAFEALIGN_COPY_UINT32_CHECK(&auth_token_type, &body[*c], blen, c); - auth_token_length = data_size - sizeof(uint32_t); - auth_token_data = body+(*c); - - switch (auth_token_type) { - case SSS_AUTHTOK_TYPE_EMPTY: - sss_authtok_set_empty(tok); - break; - case SSS_AUTHTOK_TYPE_PASSWORD: - if (auth_token_length == 0) { - sss_authtok_set_empty(tok); - } else { - ret = sss_authtok_set_password(tok, (const char *)auth_token_data, - auth_token_length); - } - break; - case SSS_AUTHTOK_TYPE_2FA: - case SSS_AUTHTOK_TYPE_2FA_SINGLE: - case SSS_AUTHTOK_TYPE_SC_PIN: - case SSS_AUTHTOK_TYPE_SC_KEYPAD: - case SSS_AUTHTOK_TYPE_OAUTH2: - case SSS_AUTHTOK_TYPE_PASSKEY: - case SSS_AUTHTOK_TYPE_PASSKEY_KRB: - case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: - case SSS_AUTHTOK_TYPE_PAM_STACKED: - ret = sss_authtok_set(tok, auth_token_type, - auth_token_data, auth_token_length); - break; - default: - return EINVAL; - } - - *c += auth_token_length; - - return ret; -} - -static int extract_string(char **var, size_t size, uint8_t *body, size_t blen, - size_t *c) { - uint8_t *str; - - if (*c+size > blen || SIZE_T_OVERFLOW(*c, size)) return EINVAL; - - str = body+(*c); - - if (str[size-1]!='\0') return EINVAL; - - /* If the string isn't valid UTF-8, fail */ - if (!sss_utf8_check(str, size-1)) { - return EINVAL; - } - - *c += size; - - *var = (char *) str; - - return EOK; -} - -static int extract_uint32_t(uint32_t *var, size_t size, uint8_t *body, - size_t blen, size_t *c) { - - if (size != sizeof(uint32_t) || *c+size > blen || SIZE_T_OVERFLOW(*c, size)) - return EINVAL; - - SAFEALIGN_COPY_UINT32_CHECK(var, &body[*c], blen, c); - - return EOK; -} - -static int pd_set_primary_name(const struct ldb_message *msg,struct pam_data *pd) -{ - const char *name; - - name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); - if (!name) { - DEBUG(SSSDBG_CRIT_FAILURE, "A user with no name?\n"); - return EIO; - } - - if (strcmp(pd->user, name)) { - DEBUG(SSSDBG_TRACE_FUNC, "User's primary name is %s\n", name); - talloc_free(pd->user); - pd->user = talloc_strdup(pd, name); - if (!pd->user) return ENOMEM; - } - - return EOK; -} - -static int pam_parse_in_data_v2(struct pam_data *pd, - uint8_t *body, size_t blen) -{ - size_t c; - uint32_t type; - uint32_t size; - int ret; - uint32_t start; - uint32_t terminator; - char *requested_domains; - - if (blen < 4*sizeof(uint32_t)+2) { - DEBUG(SSSDBG_CRIT_FAILURE, "Received data is invalid.\n"); - return EINVAL; - } - - SAFEALIGN_COPY_UINT32(&start, body, NULL); - SAFEALIGN_COPY_UINT32(&terminator, body + blen - sizeof(uint32_t), NULL); - - if (start != SSS_START_OF_PAM_REQUEST - || terminator != SSS_END_OF_PAM_REQUEST) { - DEBUG(SSSDBG_CRIT_FAILURE, "Received data is invalid.\n"); - return EINVAL; - } - - c = sizeof(uint32_t); - do { - SAFEALIGN_COPY_UINT32_CHECK(&type, &body[c], blen, &c); - - if (type == SSS_END_OF_PAM_REQUEST) { - if (c != blen) return EINVAL; - } else { - SAFEALIGN_COPY_UINT32_CHECK(&size, &body[c], blen, &c); - /* the uint32_t end maker SSS_END_OF_PAM_REQUEST does not count to - * the remaining buffer */ - if (size > (blen - c - sizeof(uint32_t))) { - DEBUG(SSSDBG_CRIT_FAILURE, "Invalid data size.\n"); - return EINVAL; - } - - switch(type) { - case SSS_PAM_ITEM_USER: - ret = extract_string(&pd->logon_name, size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_SERVICE: - ret = extract_string(&pd->service, size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_TTY: - ret = extract_string(&pd->tty, size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_RUSER: - ret = extract_string(&pd->ruser, size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_RHOST: - ret = extract_string(&pd->rhost, size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_REQUESTED_DOMAINS: - ret = extract_string(&requested_domains, size, body, blen, - &c); - if (ret != EOK) return ret; - - ret = split_on_separator(pd, requested_domains, ',', true, - true, &pd->requested_domains, - NULL); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to parse requested_domains list!\n"); - return ret; - } - break; - case SSS_PAM_ITEM_CLI_PID: - ret = extract_uint32_t(&pd->cli_pid, size, - body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_CHILD_PID: - /* This is optional. */ - ret = extract_uint32_t(&pd->child_pid, size, - body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_AUTHTOK: - ret = extract_authtok_v2(pd->authtok, - size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_NEWAUTHTOK: - ret = extract_authtok_v2(pd->newauthtok, - size, body, blen, &c); - if (ret != EOK) return ret; - break; - case SSS_PAM_ITEM_FLAGS: - ret = extract_uint32_t(&pd->cli_flags, size, - body, blen, &c); - if (ret != EOK) return ret; - break; - default: - DEBUG(SSSDBG_CRIT_FAILURE, - "Ignoring unknown data type [%d].\n", type); - c += size; - } - } - - } while(c < blen); - - return EOK; - -} - -static int pam_parse_in_data_v3(struct pam_data *pd, - uint8_t *body, size_t blen) -{ - int ret; - - ret = pam_parse_in_data_v2(pd, body, blen); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_parse_in_data_v2 failed.\n"); - return ret; - } - - if (pd->cli_pid == 0) { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing client PID.\n"); - return EINVAL; - } - - return EOK; -} - -static int extract_authtok_v1(struct sss_auth_token *tok, - uint8_t *body, size_t blen, size_t *c) -{ - uint32_t auth_token_type; - uint32_t auth_token_length; - uint8_t *auth_token_data; - int ret = EOK; - - SAFEALIGN_COPY_UINT32_CHECK(&auth_token_type, &body[*c], blen, c); - SAFEALIGN_COPY_UINT32_CHECK(&auth_token_length, &body[*c], blen, c); - auth_token_data = body+(*c); - - switch (auth_token_type) { - case SSS_AUTHTOK_TYPE_EMPTY: - sss_authtok_set_empty(tok); - break; - case SSS_AUTHTOK_TYPE_PASSWORD: - ret = sss_authtok_set_password(tok, (const char *)auth_token_data, - auth_token_length); - break; - default: - return EINVAL; - } - - *c += auth_token_length; - - return ret; -} - -static int pam_parse_in_data(struct pam_data *pd, - uint8_t *body, size_t blen) -{ - size_t start; - size_t end; - size_t last; - int ret; - - last = blen - 1; - end = 0; - - /* user name */ - for (start = end; end < last; end++) if (body[end] == '\0') break; - if (body[end++] != '\0') return EINVAL; - pd->logon_name = (char *) &body[start]; - - for (start = end; end < last; end++) if (body[end] == '\0') break; - if (body[end++] != '\0') return EINVAL; - pd->service = (char *) &body[start]; - - for (start = end; end < last; end++) if (body[end] == '\0') break; - if (body[end++] != '\0') return EINVAL; - pd->tty = (char *) &body[start]; - - for (start = end; end < last; end++) if (body[end] == '\0') break; - if (body[end++] != '\0') return EINVAL; - pd->ruser = (char *) &body[start]; - - for (start = end; end < last; end++) if (body[end] == '\0') break; - if (body[end++] != '\0') return EINVAL; - pd->rhost = (char *) &body[start]; - - ret = extract_authtok_v1(pd->authtok, body, blen, &end); - if (ret) { - DEBUG(SSSDBG_CRIT_FAILURE, "Invalid auth token\n"); - return ret; - } - ret = extract_authtok_v1(pd->newauthtok, body, blen, &end); - if (ret) { - DEBUG(SSSDBG_CRIT_FAILURE, "Invalid new auth token\n"); - return ret; - } - - DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); - - return EOK; -} - -static errno_t -pam_get_local_auth_policy(struct sss_domain_info *domain, - const char *name, - bool *_sc_allow, - bool *_passkey_allow) -{ - TALLOC_CTX *tmp_ctx = NULL; - const char *attrs[] = { SYSDB_LOCAL_SMARTCARD_AUTH, SYSDB_LOCAL_PASSKEY_AUTH, NULL }; - struct ldb_message *ldb_msg; - bool sc_allow = false; - bool passkey_allow = false; - errno_t ret; - - if (name == NULL || *name == '\0') { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); - ret = EINVAL; - goto done; - } - - if (domain->sysdb == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing sysdb db context.\n"); - ret = EINVAL; - goto done; - } - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - ret = sysdb_search_user_by_name(tmp_ctx, domain, name, attrs, &ldb_msg); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "sysdb_search_user_by_name failed [%d][%s].\n", - ret, strerror(ret)); - goto done; - } - - sc_allow = ldb_msg_find_attr_as_bool(ldb_msg, SYSDB_LOCAL_SMARTCARD_AUTH, - false); - - passkey_allow = ldb_msg_find_attr_as_bool(ldb_msg, SYSDB_LOCAL_PASSKEY_AUTH, - true); - - ret = EOK; - -done: - if (ret == EOK) { - *_sc_allow = sc_allow; - *_passkey_allow = passkey_allow; - } - - talloc_free(tmp_ctx); - return ret; -} -static errno_t set_local_auth_type(struct pam_auth_req *preq, - bool sc_allow, - bool passkey_allow) -{ - struct sysdb_attrs *attrs; - errno_t ret; - - attrs = sysdb_new_attrs(preq); - if (!attrs) { - ret = ENOMEM; - goto fail; - } - - if (sc_allow) { - /* Only set SYSDB_LOCAL_SMARTCARD_AUTH to 'true' but never to - * 'false'. The krb5 backend will only returns that Smartcard - * authentication is available if a Smartcard is present. That means - * if the user authenticates with a different method and a Smartcard - * is not present at this time 'sc_allow' will be 'false' and might - * overwrite a 'true' value written during a previous authentication - * attempt where a Smartcard was present. To avoid this we only write - * 'true' values. Since the default if SYSDB_LOCAL_SMARTCARD_AUTH is - * missing is 'false' local Smartcard authentication (offline) will - * still only be enabled if online Smartcard authentication was - * detected. */ - ret = sysdb_attrs_add_bool(attrs, SYSDB_LOCAL_SMARTCARD_AUTH, sc_allow); - if (ret != EOK) { - goto fail; - } - } - - ret = sysdb_attrs_add_bool(attrs, SYSDB_LOCAL_PASSKEY_AUTH, passkey_allow); - if (ret != EOK) { - goto fail; - } - - ret = sysdb_set_user_attr(preq->domain, preq->pd->user, attrs, - SYSDB_MOD_REP); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "set_local_auth_type failed.\n"); - preq->pd->pam_status = PAM_SYSTEM_ERR; - goto fail; - } - - return EOK; - -fail: - return ret; -} -/*=Save-Last-Login-State===================================================*/ - -static errno_t set_last_login(struct pam_auth_req *preq) -{ - struct sysdb_attrs *attrs; - errno_t ret; - - attrs = sysdb_new_attrs(preq); - if (!attrs) { - ret = ENOMEM; - goto fail; - } - - ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_ONLINE_AUTH, time(NULL)); - if (ret != EOK) { - goto fail; - } - - ret = sysdb_attrs_add_time_t(attrs, - SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, - time(NULL)); - if (ret != EOK) { - goto fail; - } - - ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_LOGIN, time(NULL)); - if (ret != EOK) { - goto fail; - } - - ret = sysdb_set_user_attr(preq->domain, preq->pd->user, attrs, - SYSDB_MOD_REP); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "set_last_login failed.\n"); - preq->pd->pam_status = PAM_SYSTEM_ERR; - goto fail; - } else { - preq->pd->last_auth_saved = true; - } - preq->callback(preq); - - return EOK; - -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 ? var_name : "(NULL)"), - (service ? service : "(NULL)")); - - 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 pam_ctx *pctx, - struct response_data *resp_list, - struct pam_data *pd) -{ - int ret; - struct response_data *resp; - uint32_t user_info_type; - int64_t expire_date = 0; - int pam_verbosity = DEFAULT_PAM_VERBOSITY; - char **new_opts; - size_t c; - const char *default_pam_response_filter[] = { "ENV:KRB5CCNAME:sudo", - "ENV:KRB5CCNAME:sudo-i", - NULL }; - - ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_VERBOSITY, DEFAULT_PAM_VERBOSITY, - &pam_verbosity); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read PAM verbosity, not fatal.\n"); - pam_verbosity = DEFAULT_PAM_VERBOSITY; - } - - if (pctx->pam_filter_opts == NULL) { - ret = confdb_get_string_as_list(pctx->rctx->cdb, pctx, - CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_RESPONSE_FILTER, - &pctx->pam_filter_opts); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read values of [%s], not fatal.\n", - CONFDB_PAM_RESPONSE_FILTER); - pctx->pam_filter_opts = NULL; - } else { - if (pctx->pam_filter_opts == NULL - || *pctx->pam_filter_opts[0] == '+' - || *pctx->pam_filter_opts[0] == '-') { - ret = mod_defaults_list(pctx, default_pam_response_filter, - pctx->pam_filter_opts, &new_opts); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to modify [%s] defaults.\n", - CONFDB_PAM_RESPONSE_FILTER); - return ret; - } - talloc_free(pctx->pam_filter_opts); - pctx->pam_filter_opts = new_opts; - } - } - - if (pctx->pam_filter_opts == NULL) { - DEBUG(SSSDBG_CONF_SETTINGS, "No PAM response filter set.\n"); - } else { - /* Make sure there are no '+' or '-' prefixes anymore */ - for (c = 0; pctx->pam_filter_opts[c] != NULL; c++) { - if (*pctx->pam_filter_opts[0] == '+' - || *pctx->pam_filter_opts[0] == '-') { - DEBUG(SSSDBG_CRIT_FAILURE, - "Unsupport mix of prefixed and not prefixed " - "values of [%s].\n", CONFDB_PAM_RESPONSE_FILTER); - return EINVAL; - } - DEBUG(SSSDBG_CONF_SETTINGS, - "PAM response filter: [%s].\n", - pctx->pam_filter_opts[c]); - } - } - } - - 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"); - ret = EINVAL; - goto done; - } - - if (pam_verbosity == PAM_VERBOSITY_NO_MESSAGES) { - resp->do_not_send_to_client = true; - resp = resp->next; - continue; - } - - memcpy(&user_info_type, resp->data, sizeof(uint32_t)); - - resp->do_not_send_to_client = false; - switch (user_info_type) { - case SSS_PAM_USER_INFO_OFFLINE_AUTH: - if (resp->len != sizeof(uint32_t) + sizeof(int64_t)) { - DEBUG(SSSDBG_CRIT_FAILURE, - "User info offline auth entry is " - "too short.\n"); - ret = EINVAL; - goto done; - } - memcpy(&expire_date, resp->data + sizeof(uint32_t), - sizeof(int64_t)); - if ((expire_date == 0 && - pam_verbosity < PAM_VERBOSITY_INFO) || - (expire_date > 0 && - pam_verbosity < PAM_VERBOSITY_IMPORTANT)) { - resp->do_not_send_to_client = true; - } - - break; - default: - DEBUG(SSSDBG_TRACE_LIBS, - "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, pctx->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; - } - - resp = resp->next; - } - - ret = EOK; -done: - - return ret; -} - -static void do_not_send_cert_info(struct pam_data *pd) -{ - struct response_data *resp; - - resp = pd->resp_list; - while (resp != NULL) { - switch (resp->type) { - case SSS_PAM_CERT_INFO: - case SSS_PAM_CERT_INFO_WITH_HINT: - resp->do_not_send_to_client = true; - break; - default: - break; - } - resp = resp->next; - } -} - -static void evaluate_pam_resp_list(struct pam_data *pd, - struct pam_resp_auth_type *_auth_types, - bool *_found_cert_info) -{ - struct response_data *resp; - struct pam_resp_auth_type types = {0}; - bool found_cert_info = false; - - resp = pd->resp_list; - while (resp != NULL) { - switch (resp->type) { - case SSS_PAM_OTP_INFO: - types.otp_auth = true; - break; - case SSS_PAM_CERT_INFO: - case SSS_PAM_CERT_INFO_WITH_HINT: - found_cert_info = true; - break; - case SSS_PAM_PASSKEY_INFO: - case SSS_PAM_PASSKEY_KRB_INFO: - types.passkey_auth = true; - break; - case SSS_PASSWORD_PROMPTING: - types.password_auth = true; - break; - case SSS_CERT_AUTH_PROMPTING: - types.cert_auth = true; - break; - default: - break; - } - resp = resp->next; - } - - if (_auth_types != NULL) { - *_auth_types = types; - } - if (_found_cert_info != NULL) { - *_found_cert_info = found_cert_info; - } -} - -static void evalute_sending_cert_info(struct pam_data *pd) -{ - struct pam_resp_auth_type types = {0}; - bool found_cert_info = false; - - evaluate_pam_resp_list(pd, &types, &found_cert_info); - - if (found_cert_info && !types.cert_auth) { - do_not_send_cert_info(pd); - } -} - -errno_t pam_get_auth_types(struct pam_data *pd, - struct pam_resp_auth_type *_auth_types) -{ - int ret; - struct pam_resp_auth_type types = {0}; - - evaluate_pam_resp_list(pd, &types, NULL); - - if (!types.password_auth && !types.otp_auth && !types.cert_auth && !types.passkey_auth) { - /* If the backend cannot determine which authentication types are - * available the default would be to prompt for a password. */ - types.password_auth = true; - types.backend_returned_no_auth_type = true; - } - - DEBUG(SSSDBG_TRACE_ALL, "Authentication types for user [%s] and service " - "[%s]:%s%s%s%s\n", pd->user, pd->service, - types.password_auth ? " password": "", - types.otp_auth ? " two-factor" : "", - types.passkey_auth ? " passkey" : "", - types.cert_auth ? " smartcard" : ""); - - ret = EOK; - - *_auth_types = types; - - return ret; -} - -static errno_t pam_eval_local_auth_policy(TALLOC_CTX *mem_ctx, - struct pam_ctx *pctx, - struct pam_data *pd, - struct pam_auth_req *preq, - bool *_sc_allow, - bool *_passkey_allow, - char **_local_policy) { - - TALLOC_CTX *tmp_ctx; - errno_t ret; - const char *domain_cdb; - char *local_policy = NULL; - bool sc_allow = false; - bool passkey_allow = false; - struct pam_resp_auth_type auth_types; - char **opts; - size_t c; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - return ENOMEM; - } - - /* Check local auth policy */ - domain_cdb = talloc_asprintf(tmp_ctx, CONFDB_DOMAIN_PATH_TMPL, preq->domain->name); - if (domain_cdb == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); - ret = ENOMEM; - goto done; - } - - ret = confdb_get_string(pctx->rctx->cdb, tmp_ctx, domain_cdb, - CONFDB_DOMAIN_LOCAL_AUTH_POLICY, - "match", &local_policy); - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get the confdb local_auth_policy\n"); - return ret; - } - - /* "only" ignores online methods and allows all local ones */ - if (strcasecmp(local_policy, "only") == 0) { - sc_allow = true; - passkey_allow = true; - /* Match what the KDC supports and provides */ - } else if (strcasecmp(local_policy, "match") == 0) { - /* Don't overwrite the local auth type when offline */ - if (pd->pam_status == PAM_SUCCESS && pd->cmd == SSS_PAM_PREAUTH && - !is_domain_provider(preq->domain, "ldap")) { - ret = pam_get_auth_types(pd, &auth_types); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to get authentication types\n"); - goto done; - } - - if (auth_types.cert_auth) { - sc_allow = true; - } else if (auth_types.passkey_auth) { - passkey_allow = true; - } - - /* Store the local auth types, in case we go offline */ - if (!auth_types.backend_returned_no_auth_type) { - ret = set_local_auth_type(preq, sc_allow, passkey_allow); - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, - "Failed to evaluate local auth policy\n"); - goto done; - } - } - } - - /* Read the latest auth types */ - ret = pam_get_local_auth_policy(preq->domain, preq->pd->user, - &sc_allow, &passkey_allow); - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, - "Failed to get PAM local auth policy\n"); - goto done; - } - /* Check for enable */ - } else { - ret = split_on_separator(tmp_ctx, local_policy, ',', true, true, &opts, - NULL); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d], %s.\n", - ret, sss_strerror(ret)); - goto done; - } - - for (c = 0; opts[c] != NULL; c++) { - if (strcasestr(opts[c], "passkey") != NULL) { - passkey_allow = strstr(opts[c], "enable") ? true : false; - } else if (strcasestr(opts[c], "smartcard") != NULL) { - sc_allow = strstr(opts[c], "enable") ? true : false; - } else { - DEBUG(SSSDBG_MINOR_FAILURE, - "Unexpected local auth policy option [%s], " \ - "skipping.\n", opts[c]); - } - } - } - - /* if passkey is enabled but local Smartcard authentication is not but - * possible, the cert info data has to be remove as well if only local - * Smartcard authentication is possible. If Smartcard authentication - * is possible on the server side we have to keep it because the - * 'enable' option should only add local methods but not reject remote - * ones. */ - if (!sc_allow) { - evalute_sending_cert_info(pd); - } - - *_sc_allow = sc_allow; - *_passkey_allow = passkey_allow; - *_local_policy = talloc_steal(mem_ctx, local_policy); - - ret = EOK; - -done: - talloc_free(tmp_ctx); - return ret; -} - -static void pam_reply_delay(struct tevent_context *ev, struct tevent_timer *te, - struct timeval tv, void *pvt) -{ - struct pam_auth_req *preq; - - DEBUG(SSSDBG_CONF_SETTINGS, "pam_reply_delay get called.\n"); - - preq = talloc_get_type(pvt, struct pam_auth_req); - - pam_reply(preq); -} - -static errno_t get_password_for_cache_auth(struct sss_auth_token *authtok, - const char **password) -{ - int ret; - size_t pw_len; - const char *fa2; - size_t fa2_len; - - switch (sss_authtok_get_type(authtok)) { - case SSS_AUTHTOK_TYPE_PASSWORD: - ret = sss_authtok_get_password(authtok, password, NULL); - break; - case SSS_AUTHTOK_TYPE_2FA: - ret = sss_authtok_get_2fa(authtok, password, &pw_len, &fa2, &fa2_len); - break; - default: - DEBUG(SSSDBG_FATAL_FAILURE, "Unsupported auth token type [%d].\n", - sss_authtok_get_type(authtok)); - ret = EINVAL; - } - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get password.\n"); - return ret; - } - - return EOK; -} - -static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd); -static void pam_handle_cached_login(struct pam_auth_req *preq, int ret, - time_t expire_date, time_t delayed_until, bool cached_auth); - -/* - * Add a request to add a variable to the PAM user environment, containing the - * actual (not overridden) user shell, in case session recording is enabled. - */ -static int pam_reply_sr_export_shell(struct pam_auth_req *preq, - const char *var_name) -{ - int ret; - TALLOC_CTX *ctx = NULL; - bool enabled; - const char *enabled_str; - const char *shell; - char *buf; - - /* Create temporary talloc context */ - ctx = talloc_new(NULL); - if (ctx == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); - ret = ENOMEM; - goto done; - } - - /* Check if session recording is enabled */ - if (preq->cctx->rctx->sr_conf.scope == - SESSION_RECORDING_SCOPE_NONE) { - enabled = false; - } else { - enabled_str = ldb_msg_find_attr_as_string(preq->user_obj, - SYSDB_SESSION_RECORDING, NULL); - if (enabled_str == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "%s attribute not found\n", SYSDB_SESSION_RECORDING); - ret = ENOENT; - goto done; - } else if (strcmp(enabled_str, "TRUE") == 0) { - enabled = true; - } else if (strcmp(enabled_str, "FALSE") == 0) { - enabled = false; - } else { - DEBUG(SSSDBG_CRIT_FAILURE, "invalid value of %s attribute: %s\n", - SYSDB_SESSION_RECORDING, enabled_str); - ret = ENOENT; - goto done; - } - } - - /* Export original shell if recording is enabled and so it's overridden */ - if (enabled) { - /* Extract the shell */ - shell = sss_resp_get_shell_override(preq->user_obj, - preq->cctx->rctx, preq->domain); - if (shell == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "user has no shell\n"); - ret = ENOENT; - goto done; - } - - /* Format environment entry */ - buf = talloc_asprintf(ctx, "%s=%s", var_name, shell); - if (buf == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); - ret = ENOMEM; - goto done; - } - - /* Add request to add the entry to user environment */ - ret = pam_add_response(preq->pd, SSS_PAM_ENV_ITEM, - strlen(buf) + 1, (uint8_t *)buf); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); - goto done; - } - } - - ret = EOK; - -done: - talloc_free(ctx); - return ret; -} - -void pam_reply(struct pam_auth_req *preq) -{ - struct cli_ctx *cctx; - struct cli_protocol *prctx; - uint8_t *body; - size_t blen; - int ret; - int32_t resp_c; - int32_t resp_size; - struct response_data *resp; - int p; - struct timeval tv; - struct tevent_timer *te; - struct pam_data *pd; - char *local_policy = NULL; - struct pam_ctx *pctx; - uint32_t user_info_type; - time_t exp_date = -1; - time_t delay_until = -1; - char* pam_account_expired_message; - char* pam_account_locked_message; - int pam_verbosity; - bool local_sc_auth_allow = false; - bool local_passkey_auth_allow = false; -#ifdef BUILD_PASSKEY - bool pk_preauth_done = false; -#endif /* BUILD_PASSKEY */ - - pd = preq->pd; - cctx = preq->cctx; - pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); - prctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); - - ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_VERBOSITY, DEFAULT_PAM_VERBOSITY, - &pam_verbosity); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read PAM verbosity, not fatal.\n"); - pam_verbosity = DEFAULT_PAM_VERBOSITY; - } - - DEBUG(SSSDBG_TRACE_ALL, - "pam_reply initially called with result [%d]: %s. " - "this result might be changed during processing\n", - pd->pam_status, pam_strerror(NULL, pd->pam_status)); - - if (preq->domain != NULL && preq->domain->name != NULL) { - ret = pam_eval_local_auth_policy(cctx, pctx, pd, preq, - &local_sc_auth_allow, - &local_passkey_auth_allow, - &local_policy); - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, - "Failed to evaluate local auth policy\n"); - goto done; - } - } - - /* Ignore local_auth_policy for the files provider, allow local - * smartcard auth (default behavior prior to local_auth_policy) */ - if (is_domain_provider(preq->domain, "files")) { - local_sc_auth_allow = true; - /* For the ldap auth provider we currently only support - * password based authentication */ - } else if (is_domain_provider(preq->domain, "ldap") && local_policy != NULL - && strcasecmp(local_policy, "match") == 0) { - local_passkey_auth_allow = false; - local_sc_auth_allow = false; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Local auth policy allowed: smartcard [%s], passkey [%s]\n", - local_sc_auth_allow ? "True" : "False", - local_passkey_auth_allow ? "True" : "False"); - - if (pd->cmd == SSS_PAM_AUTHENTICATE - && !preq->cert_auth_local - && (pd->pam_status == PAM_AUTHINFO_UNAVAIL - || pd->pam_status == PAM_NO_MODULE_DATA - || pd->pam_status == PAM_BAD_ITEM) - && may_do_cert_auth(pctx, pd)) { - /* We have Smartcard credentials and the backend indicates that it is - * offline (PAM_AUTHINFO_UNAVAIL) or cannot handle the credentials - * (PAM_BAD_ITEM), so let's try authentication against the Smartcard - * PAM_NO_MODULE_DATA is returned by the krb5 backend if no - * authentication method was found at all, this might happen if the - * user has a Smartcard assigned but the pkint plugin is not available - * on the client. */ - DEBUG(SSSDBG_IMPORTANT_INFO, - "Backend cannot handle Smartcard authentication, " - "trying local Smartcard authentication.\n"); - if (local_sc_auth_allow) { - preq->cert_auth_local = true; - ret = check_cert(cctx, cctx->ev, pctx, preq, pd); - pam_check_user_done(preq, ret); - return; - } else { - DEBUG(SSSDBG_IMPORTANT_INFO, - "Local smartcard auth not allowed by local_auth_policy"); - } - } - - if (pd->pam_status == PAM_AUTHINFO_UNAVAIL || preq->use_cached_auth) { - - switch(pd->cmd) { - case SSS_PAM_AUTHENTICATE: - if ((preq->domain != NULL) && - (preq->domain->cache_credentials == true) && - (pd->offline_auth == false)) { - const char *password = NULL; - bool use_cached_auth; - - /* backup value of preq->use_cached_auth*/ - use_cached_auth = preq->use_cached_auth; - /* set to false to avoid entering this branch when pam_reply() - * is recursively called from pam_handle_cached_login() */ - preq->use_cached_auth = false; - - /* do auth with offline credentials */ - pd->offline_auth = true; - - if (preq->domain->sysdb == NULL) { - DEBUG(SSSDBG_FATAL_FAILURE, - "Fatal: Sysdb CTX not found for domain" - " [%s]!\n", preq->domain->name); - goto done; - } - - ret = get_password_for_cache_auth(pd->authtok, &password); - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, - "get_password_and_type_for_cache_auth failed.\n"); - goto done; - } - - ret = sysdb_cache_auth(preq->domain, - pd->user, password, - pctx->rctx->cdb, false, - &exp_date, &delay_until); - - pam_handle_cached_login(preq, ret, exp_date, delay_until, - use_cached_auth); - return; - } - break; - case SSS_PAM_CHAUTHTOK_PRELIM: - case SSS_PAM_CHAUTHTOK: - DEBUG(SSSDBG_FUNC_DATA, - "Password change not possible while offline.\n"); - pd->pam_status = PAM_AUTHTOK_ERR; - user_info_type = SSS_PAM_USER_INFO_OFFLINE_CHPASS; - ret = pam_add_response(pd, SSS_PAM_USER_INFO, sizeof(uint32_t), - (const uint8_t *) &user_info_type); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); - goto done; - } - break; -/* TODO: we need the pam session cookie here to make sure that cached - * authentication was successful */ - case SSS_PAM_PREAUTH: - case SSS_PAM_SETCRED: - case SSS_PAM_ACCT_MGMT: - case SSS_PAM_OPEN_SESSION: - case SSS_PAM_CLOSE_SESSION: - DEBUG(SSSDBG_OP_FAILURE, - "Assuming offline authentication setting status for " - "pam call %d to PAM_SUCCESS.\n", pd->cmd); - pd->pam_status = PAM_SUCCESS; - break; - default: - DEBUG(SSSDBG_CRIT_FAILURE, "Unknown PAM call [%d].\n", pd->cmd); - pd->pam_status = PAM_MODULE_UNKNOWN; - } - } - - if (pd->pam_status == PAM_SUCCESS && pd->cmd == SSS_PAM_CHAUTHTOK) { - ret = pam_null_last_online_auth_with_curr_token(preq->domain, - pd->user); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "sysdb_null_last_online_auth_with_curr_token failed: " - "%s [%d].\n", sss_strerror(ret), ret); - goto done; - } - } - - if (pd->response_delay > 0) { - ret = gettimeofday(&tv, NULL); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "gettimeofday failed [%d][%s].\n", - errno, strerror(errno)); - goto done; - } - tv.tv_sec += pd->response_delay; - tv.tv_usec = 0; - pd->response_delay = 0; - - te = tevent_add_timer(cctx->ev, cctx, tv, pam_reply_delay, preq); - if (te == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to add event pam_reply_delay.\n"); - goto done; - } - - return; - } - - /* If this was a successful login, save the lastLogin time */ - if (pd->cmd == SSS_PAM_AUTHENTICATE && - pd->pam_status == PAM_SUCCESS && - preq->domain && - preq->domain->cache_credentials && - !pd->offline_auth && - !pd->last_auth_saved) { - ret = set_last_login(preq); - if (ret != EOK) { - goto done; - } - return; - } - - ret = sss_packet_new(prctx->creq, 0, sss_packet_get_cmd(prctx->creq->in), - &prctx->creq->out); - if (ret != EOK) { - goto done; - } - -#ifdef BUILD_PASSKEY - if(pd->cmd == SSS_PAM_AUTHENTICATE && - pd->pam_status == PAM_NEW_AUTHTOK_REQD && - sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_REPLY) { - DEBUG(SSSDBG_TRACE_FUNC, "Passkey authentication reply, ignoring " - "new authtok required status\n"); - pd->pam_status = PAM_SUCCESS; - } - - /* Passkey auth user notification if no TGT is granted */ - if (pd->cmd == SSS_PAM_AUTHENTICATE && - pd->pam_status == PAM_SUCCESS && - preq->pd->passkey_local_done) { - user_info_type = SSS_PAM_USER_INFO_NO_KRB_TGT; - pam_add_response(pd, SSS_PAM_USER_INFO, - sizeof(uint32_t), (const uint8_t *) &user_info_type); - DEBUG(SSSDBG_IMPORTANT_INFO, - "User [%s] logged in with local passkey authentication, single " - "sign on ticket is not obtained.\n", pd->user); - } -#endif /* BUILD_PASSKEY */ - - /* Account expiration warning is printed for sshd. If pam_verbosity - * is equal or above PAM_VERBOSITY_INFO then all services are informed - * about account expiration. - */ - if (pd->pam_status == PAM_ACCT_EXPIRED && - ((pd->service != NULL && strcasecmp(pd->service, "sshd") == 0) || - pam_verbosity >= PAM_VERBOSITY_INFO)) { - - ret = confdb_get_string(pctx->rctx->cdb, pd, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_ACCOUNT_EXPIRED_MESSAGE, "", - &pam_account_expired_message); - if (ret != EOK) { - DEBUG(SSSDBG_MINOR_FAILURE, - "Failed to get expiration message: %d:[%s].\n", - ret, sss_strerror(ret)); - goto done; - } - - inform_user(pd, pam_account_expired_message); - } - - if (pd->account_locked) { - - ret = confdb_get_string(pctx->rctx->cdb, pd, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_ACCOUNT_LOCKED_MESSAGE, "", - &pam_account_locked_message); - if (ret != EOK) { - DEBUG(SSSDBG_MINOR_FAILURE, - "Failed to get expiration message: %d:[%s].\n", - ret, sss_strerror(ret)); - goto done; - } - - inform_user(pd, pam_account_locked_message); - } - - ret = filter_responses(pctx, pd->resp_list, pd); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "filter_responses failed, not fatal.\n"); - } - - if (pd->domain != NULL) { - ret = pam_add_response(pd, SSS_PAM_DOMAIN_NAME, strlen(pd->domain)+1, - (uint8_t *) pd->domain); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); - goto done; - } - } - - 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"); - } - -#ifdef BUILD_PASSKEY - ret = pam_eval_passkey_response(pctx, pd, preq, &pk_preauth_done); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to eval passkey response\n"); - goto done; - } - - if (may_do_passkey_auth(pctx, pd) - && !pk_preauth_done - && preq->passkey_data_exists - && local_passkey_auth_allow) { - ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); - pam_check_user_done(preq, ret); - return; - } -#endif /* BUILD_PASSKEY */ - } - - /* - * Export non-overridden shell to tlog-rec-session when opening the session - */ - if (pd->cmd == SSS_PAM_OPEN_SESSION && pd->pam_status == PAM_SUCCESS) { - ret = pam_reply_sr_export_shell(preq, "TLOG_REC_SESSION_SHELL"); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "failed to export the shell to tlog-rec-session.\n"); - goto done; - } - } - - resp_c = 0; - resp_size = 0; - resp = pd->resp_list; - while(resp != NULL) { - if (!resp->do_not_send_to_client) { - resp_c++; - resp_size += resp->len; - } - resp = resp->next; - } - - ret = sss_packet_grow(prctx->creq->out, sizeof(int32_t) + - sizeof(int32_t) + - resp_c * 2* sizeof(int32_t) + - resp_size); - if (ret != EOK) { - goto done; - } - - sss_packet_get_body(prctx->creq->out, &body, &blen); - DEBUG(SSSDBG_FUNC_DATA, "blen: %zu\n", blen); - p = 0; - - memcpy(&body[p], &pd->pam_status, sizeof(int32_t)); - p += sizeof(int32_t); - - memcpy(&body[p], &resp_c, sizeof(int32_t)); - p += sizeof(int32_t); - - resp = pd->resp_list; - while(resp != NULL) { - if (!resp->do_not_send_to_client) { - memcpy(&body[p], &resp->type, sizeof(int32_t)); - p += sizeof(int32_t); - memcpy(&body[p], &resp->len, sizeof(int32_t)); - p += sizeof(int32_t); - memcpy(&body[p], resp->data, resp->len); - p += resp->len; - } - - resp = resp->next; - } - -done: - DEBUG(SSSDBG_FUNC_DATA, "Returning [%d]: %s to the client\n", - pd->pam_status, pam_strerror(NULL, pd->pam_status)); - sss_cmd_done(cctx, preq); -} - -static void pam_dom_forwarder(struct pam_auth_req *preq); - -static void pam_handle_cached_login(struct pam_auth_req *preq, int ret, - time_t expire_date, time_t delayed_until, - bool use_cached_auth) -{ - uint32_t resp_type; - size_t resp_len; - uint8_t *resp; - int64_t dummy; - - preq->pd->pam_status = cached_login_pam_status(ret); - - switch (preq->pd->pam_status) { - case PAM_SUCCESS: - resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH; - resp_len = sizeof(uint32_t) + sizeof(int64_t); - resp = talloc_size(preq->pd, resp_len); - if (resp == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "talloc_size failed, cannot prepare user info.\n"); - } else { - memcpy(resp, &resp_type, sizeof(uint32_t)); - dummy = (int64_t) expire_date; - memcpy(resp+sizeof(uint32_t), &dummy, sizeof(int64_t)); - ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, - (const uint8_t *) resp); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); - } - } - break; - case PAM_PERM_DENIED: - if (delayed_until >= 0) { - resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED; - resp_len = sizeof(uint32_t) + sizeof(int64_t); - resp = talloc_size(preq->pd, resp_len); - if (resp == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "talloc_size failed, cannot prepare user info.\n"); - } else { - memcpy(resp, &resp_type, sizeof(uint32_t)); - dummy = (int64_t) delayed_until; - memcpy(resp+sizeof(uint32_t), &dummy, sizeof(int64_t)); - ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, - (const uint8_t *) resp); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "pam_add_response failed.\n"); - } - } - } - break; - case PAM_AUTH_ERR: - /* Was this attempt to authenticate from cache? */ - if (use_cached_auth) { - /* Don't try cached authentication again, try online check. */ - DEBUG(SSSDBG_FUNC_DATA, - "Cached authentication failed for: %s\n", - preq->pd->user); - preq->cached_auth_failed = true; - pam_dom_forwarder(preq); - return; - } - break; - default: - DEBUG(SSSDBG_TRACE_LIBS, - "cached login returned: %d\n", preq->pd->pam_status); - } - - pam_reply(preq); - return; -} - -static void pam_forwarder_cb(struct tevent_req *req); -static void pam_forwarder_cert_cb(struct tevent_req *req); -int pam_check_user_search(struct pam_auth_req *preq); - - -/* TODO: we should probably return some sort of cookie that is set in the - * PAM_ENVIRONMENT, so that we can save performing some calls and cache - * data. */ - -static errno_t pam_forwarder_parse_data(struct cli_ctx *cctx, struct pam_data *pd) -{ - struct cli_protocol *prctx; - uint8_t *body; - size_t blen; - errno_t ret; - uint32_t terminator; - const char *key_id; - - prctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); - - sss_packet_get_body(prctx->creq->in, &body, &blen); - if (blen >= sizeof(uint32_t)) { - SAFEALIGN_COPY_UINT32(&terminator, - body + blen - sizeof(uint32_t), - NULL); - if (terminator != SSS_END_OF_PAM_REQUEST) { - DEBUG(SSSDBG_CRIT_FAILURE, "Received data not terminated.\n"); - ret = EINVAL; - goto done; - } - } - - switch (prctx->cli_protocol_version->version) { - case 1: - ret = pam_parse_in_data(pd, body, blen); - break; - case 2: - ret = pam_parse_in_data_v2(pd, body, blen); - break; - case 3: - ret = pam_parse_in_data_v3(pd, body, blen); - break; - default: - DEBUG(SSSDBG_CRIT_FAILURE, "Illegal protocol version [%d].\n", - prctx->cli_protocol_version->version); - ret = EINVAL; - } - if (ret != EOK) { - goto done; - } - - if (pd->logon_name != NULL) { - ret = sss_parse_name_for_domains(pd, cctx->rctx->domains, - cctx->rctx->default_domain, - pd->logon_name, - &pd->domain, &pd->user); - } else { - /* SSS_PAM_PREAUTH request may have a missing name, e.g. if the - * name is determined with the help of a certificate. During - * SSS_PAM_AUTHENTICATE at least a key ID is needed to identify the - * selected certificate. */ - if (pd->cmd == SSS_PAM_AUTHENTICATE - && may_do_cert_auth(talloc_get_type(cctx->rctx->pvt_ctx, - struct pam_ctx), pd) - && (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_SC_PIN - || sss_authtok_get_type(pd->authtok) - == SSS_AUTHTOK_TYPE_SC_KEYPAD)) { - ret = sss_authtok_get_sc(pd->authtok, NULL, NULL, NULL, NULL, NULL, - NULL, &key_id, NULL, NULL, NULL); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_sc failed.\n"); - goto done; - } - - if (key_id == NULL || *key_id == '\0') { - DEBUG(SSSDBG_CRIT_FAILURE, - "Missing logon and Smartcard key ID during " - "authentication.\n"); - ret = ERR_NO_CREDS; - goto done; - } - - ret = EOK; - } else if (pd->cmd == SSS_PAM_PREAUTH - && may_do_cert_auth(talloc_get_type(cctx->rctx->pvt_ctx, - struct pam_ctx), pd)) { - ret = EOK; - } else { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing logon name in PAM request.\n"); - ret = ERR_NO_CREDS; - goto done; - } - } - - DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); - -done: - return ret; -} - -static bool is_uid_trusted(struct cli_creds *creds, - size_t trusted_uids_count, - uid_t *trusted_uids) -{ - errno_t ret; - - /* root is always trusted */ - if (client_euid(creds) == 0) { - return true; - } - - /* All uids are allowed */ - if (trusted_uids_count == 0) { - return true; - } - - ret = check_allowed_uids(client_euid(creds), trusted_uids_count, trusted_uids); - if (ret == EOK) return true; - - return false; -} - -static bool is_domain_public(char *name, - char **public_dom_names, - size_t public_dom_names_count) -{ - size_t i; - - for(i=0; i < public_dom_names_count; i++) { - if (strcasecmp(name, public_dom_names[i]) == 0) { - return true; - } - } - return false; -} - -static enum cache_req_dom_type -get_domain_request_type(struct pam_auth_req *preq, - struct pam_ctx *pctx) -{ - enum cache_req_dom_type req_dom_type; - - /* By default, only POSIX domains are to be contacted */ - req_dom_type = CACHE_REQ_POSIX_DOM; - - for (int i = 0; pctx->app_services[i]; i++) { - if (strcmp(pctx->app_services[i], preq->pd->service) == 0) { - req_dom_type = CACHE_REQ_APPLICATION_DOM; - break; - } - } - - return req_dom_type; -} - -static errno_t check_cert(TALLOC_CTX *mctx, - struct tevent_context *ev, - struct pam_ctx *pctx, - struct pam_auth_req *preq, - struct pam_data *pd) -{ - int p11_child_timeout; - int wait_for_card_timeout; - char *cert_verification_opts; - errno_t ret; - struct tevent_req *req; - char *uri = NULL; - - ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_P11_CHILD_TIMEOUT, - P11_CHILD_TIMEOUT_DEFAULT, - &p11_child_timeout); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read p11_child_timeout from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - return ret; - } - if ((pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) && pd->priv == 1) { - ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_WAIT_FOR_CARD_TIMEOUT, - P11_WAIT_FOR_CARD_TIMEOUT_DEFAULT, - &wait_for_card_timeout); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read [%s] from confdb: [%d]: %s\n", - CONFDB_PAM_WAIT_FOR_CARD_TIMEOUT, ret, sss_strerror(ret)); - return ret; - } - - p11_child_timeout += wait_for_card_timeout; - } - - ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_CERT_VERIFICATION, - NULL, &cert_verification_opts); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read '"CONFDB_PAM_CERT_VERIFICATION"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - return ret; - } - - if (cert_verification_opts == NULL) { - ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_MONITOR_CONF_ENTRY, - CONFDB_MONITOR_CERT_VERIFICATION, NULL, - &cert_verification_opts); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read '"CONFDB_MONITOR_CERT_VERIFICATION"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - return ret; - } - } - - ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_P11_URI, NULL, &uri); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to read '"CONFDB_PAM_P11_URI"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - return ret; - } - - req = pam_check_cert_send(mctx, ev, - pctx->ca_db, p11_child_timeout, - cert_verification_opts, pctx->sss_certmap_ctx, - uri, pd); - if (req == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "pam_check_cert_send failed.\n"); - return ENOMEM; - } - - tevent_req_set_callback(req, pam_forwarder_cert_cb, preq); - return EAGAIN; -} - - -static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd) -{ - struct pam_auth_req *preq; - struct pam_data *pd; - int ret; - struct pam_ctx *pctx = - talloc_get_type(cctx->rctx->pvt_ctx, struct pam_ctx); - struct tevent_req *req; - - preq = talloc_zero(cctx, struct pam_auth_req); - if (!preq) { - return ENOMEM; - } - preq->cctx = cctx; - preq->cert_auth_local = false; - preq->client_id_num = cctx->client_id_num; - - preq->pd = create_pam_data(preq); - if (!preq->pd) { - talloc_free(preq); - return ENOMEM; - } - pd = preq->pd; - - preq->is_uid_trusted = is_uid_trusted(cctx->creds, - pctx->trusted_uids_count, - pctx->trusted_uids); - - if (!preq->is_uid_trusted) { - DEBUG(SSSDBG_MINOR_FAILURE, "uid %"SPRIuid" is not trusted.\n", - client_euid(cctx->creds)); - } - - - pd->cmd = pam_cmd; - pd->priv = cctx->priv; - pd->client_id_num = cctx->client_id_num; - - ret = pam_forwarder_parse_data(cctx, pd); - if (ret == EAGAIN) { - req = sss_dp_get_domains_send(cctx, cctx->rctx, true, pd->domain); - if (req == NULL) { - ret = ENOMEM; - } else { - tevent_req_set_callback(req, pam_forwarder_cb, preq); - ret = EAGAIN; - } - goto done; - } else if (ret != EOK) { - goto done; - } - - /* Determine what domain type to contact */ - preq->req_dom_type = get_domain_request_type(preq, pctx); - - if (pd->cmd == SSS_PAM_AUTHENTICATE - && (pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) - && !IS_SC_AUTHTOK(pd->authtok)) { - DEBUG(SSSDBG_MINOR_FAILURE, - "Smartcard authentication required but authentication " - "token [%d][%s] is not suitable.\n", - sss_authtok_get_type(pd->authtok), - sss_authtok_type_to_str(sss_authtok_get_type(pd->authtok))); - ret = ERR_NO_CREDS; - goto done; - } - - /* Try backend first for authentication before doing local Smartcard - * authentication if a logon name is available. Otherwise try to derive - * the logon name from the certificate first. */ - if ((pd->cmd != SSS_PAM_AUTHENTICATE - || (pd->cmd == SSS_PAM_AUTHENTICATE && pd->logon_name == NULL)) - && may_do_cert_auth(pctx, pd)) { - ret = check_cert(cctx, cctx->ev, pctx, preq, pd); - /* Finish here */ - goto done; - } - - /* This is set to false inside passkey_local() if no passkey data is found. - * It is checked in pam_reply() to avoid an endless loop */ - preq->passkey_data_exists = true; - -#ifdef BUILD_PASSKEY - if ((pd->cmd == SSS_PAM_AUTHENTICATE)) { - if (may_do_passkey_auth(pctx, pd)) { - if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { - ret = passkey_kerberos(pctx, preq->pd, preq); - goto done; - } else if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY) { - /* TYPE_EMPTY is not a valid */ - ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); - goto done; - } - } - } - - if (pd->cmd == SSS_PAM_PASSKEY_PREAUTH) { - -#define PASSKEY_DOPIN "/var/run/passkey-dopin" -#define PASSKEY_PINUV "/var/run/passkey-pinuv" -#define PASSKEY_PINONLY "/var/run/passkey-pinonly" -#define PASSKEY_NODEV "/var/run/passkey-nodev" - - DEBUG(SSSDBG_TRACE_FUNC, "required passkey device information .\n"); - if (may_do_passkey_auth(pctx, pd)) { - const char *devinfo = "nocacheddevinfo"; - - if (pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_PASSKEY_CACHED_DEVINFO) { - struct stat st_nodev; - int e; - DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] requests cached devinfo .\n", getpid()); - - e = stat (PASSKEY_NODEV, &st_nodev); - if (e == 0) { - /* indicator exists */ - if (time(NULL) < st_nodev.st_ctime + 100 /*seconds : configurable ??? */) { - /* the info is still valid */ - devinfo = "nodev"; - } else { - /* but the info is obsolete, we shall retest device existence */ - devinfo = "nodevobsolete"; - (void)remove (PASSKEY_NODEV); - } - } else { - DEBUG(SSSDBG_TRACE_FUNC, "stat nodev: errno=%d\n", errno); - if (access(PASSKEY_DOPIN, F_OK) == 0) { - /* the device requires a PIN to perform next authentication; */ - devinfo = "dopin"; - } else if (access(PASSKEY_PINONLY, F_OK) == 0) { - /* the device ALWAYS requires a PIN to perform authentication; */ - devinfo = "pinonly"; - } else if (access(PASSKEY_PINUV, F_OK) == 0) { - /* the cached device supports UV and does not fallback to PIN using - * PASSKEY_DOPIN indicator. - */ - devinfo = "pinuv"; - } - } - /* remove cache indicators. - * They will be rebuild according the device capabilities - * (see passkey_child) - * during authentication or fresh devinfo request. - * do not remove DOPIN that is updated only during authentication - */ - (void)remove (PASSKEY_PINUV); - (void)remove (PASSKEY_PINONLY); - - } else { - /* remove cache indicators. - * They will be rebuild according the device capabilities - * (see passkey_child) - * during this devinfo request - * do not remove DOPIN that is updated only during authentication - */ - (void)remove (PASSKEY_PINUV); - (void)remove (PASSKEY_PINONLY); - - DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] requests fresh devinfo .\n", getpid()); - /* - * ask the device capabilities - * This assumes the device is connected. - */ - if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { - DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] calls passkey_kerberos_get_devinfo() \n", getpid()); - ret = passkey_kerberos_get_devinfo(pctx, preq->pd, preq); - DEBUG(SSSDBG_TRACE_FUNC, "passkey_kerberos_get_devinfo() returns %d\n", ret); - goto done; - } else if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_EMPTY) { - - DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] calls passkey_local_get_devinfo() .\n", getpid()); - ret = passkey_local_get_devinfo(cctx, cctx->ev, pctx, preq, pd); - DEBUG(SSSDBG_TRACE_FUNC, "passkey_local_get_devinfo() returns %d\n", ret); - goto done; - } else { - /* note that AUTHTOK_TYPE_PASSKEY is not a valid authtok - * see get_device_info in pam_sss.c - */ - DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] requests fresh with invalid authtok type: [%d]\n", - getpid(),sss_authtok_get_type(pd->authtok) ); - ret = PAM_AUTHTOK_ERR; - goto done; - } - } - - DEBUG(SSSDBG_TRACE_FUNC, "cached devinfo request replies: [%s}\n", devinfo); - - ret = pam_add_response(pd, SSS_PAM_PASSKEY_DEVINFO, strlen(devinfo) + 1, - (const uint8_t *) devinfo); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed. [%d]: %s\n", - ret, sss_strerror(ret)); - return ret; - } - - pd->pam_status = PAM_SUCCESS; - pam_reply(preq); - - /* finish */ - return EOK; - } - DEBUG(SSSDBG_TRACE_FUNC, "passkey_get_devinfo requires OK from may_do_passkey_auth .\n"); - } -#endif /* BUILD_PASSKEY */ - - ret = pam_check_user_search(preq); - -done: - return pam_check_user_done(preq, ret); -} - -static errno_t pam_user_by_cert_step(struct pam_auth_req *preq); -static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req); -static void pam_forwarder_cert_cb(struct tevent_req *req) -{ - struct pam_auth_req *preq = tevent_req_callback_data(req, - struct pam_auth_req); - struct pam_data *pd; - errno_t ret = EOK; - const char *cert; - - ret = pam_check_cert_recv(req, preq, &preq->cert_list); - talloc_free(req); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "get_cert request failed.\n"); - goto done; - } - - pd = preq->pd; - - cert = sss_cai_get_cert(preq->cert_list); - - if (cert == NULL) { - if (pd->logon_name == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "No certificate found and no logon name given, " \ - "authentication not possible.\n"); - ret = ENOENT; - } else if (pd->cmd == SSS_PAM_PREAUTH - && (pd->cli_flags & PAM_CLI_FLAGS_TRY_CERT_AUTH)) { - DEBUG(SSSDBG_TRACE_ALL, - "try_cert_auth flag set but no certificate available, " - "request finished.\n"); - preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; - pam_reply(preq); - return; - } else { - if (pd->cmd == SSS_PAM_AUTHENTICATE) { - DEBUG(SSSDBG_CRIT_FAILURE, - "No certificate returned, authentication failed.\n"); - preq->pd->pam_status = PAM_AUTH_ERR; - pam_reply(preq); - return; - } else { - ret = pam_check_user_search(preq); - } - - } - goto done; - } - - preq->current_cert = preq->cert_list; - ret = pam_user_by_cert_step(preq); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n"); - goto done; - } - - return; - -done: - pam_check_user_done(preq, ret); -} - -static errno_t pam_user_by_cert_step(struct pam_auth_req *preq) -{ - struct cli_ctx *cctx = preq->cctx; - struct tevent_req *req; - struct pam_ctx *pctx = - talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); - - if (preq->current_cert == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate data.\n"); - return EINVAL; - } - - req = cache_req_user_by_cert_send(preq, cctx->ev, cctx->rctx, - pctx->rctx->ncache, 0, - preq->req_dom_type, NULL, - sss_cai_get_cert(preq->current_cert)); - if (req == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert_send failed.\n"); - return ENOMEM; - } - - tevent_req_set_callback(req, pam_forwarder_lookup_by_cert_done, preq); - return EOK; -} - -static errno_t get_results_from_all_domains(TALLOC_CTX *mem_ctx, - struct cache_req_result **results, - struct ldb_result **ldb_results) -{ - int ret; - size_t count = 0; - size_t c; - size_t d; - size_t r = 0; - struct ldb_result *res; - - for (d = 0; results != NULL && results[d] != NULL; d++) { - count += results[d]->count; - } - - res = talloc_zero(mem_ctx, struct ldb_result); - if (res == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); - return ENOMEM; - } - - if (count == 0) { - *ldb_results = res; - return EOK; - } - - res->msgs = talloc_zero_array(res, struct ldb_message *, count); - if (res->msgs == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); - return ENOMEM; - } - res->count = count; - - for (d = 0; results != NULL && results[d] != NULL; d++) { - for (c = 0; c < results[d]->count; c++) { - if (r >= count) { - DEBUG(SSSDBG_CRIT_FAILURE, - "More results found then counted before.\n"); - ret = EINVAL; - goto done; - } - res->msgs[r++] = talloc_steal(res->msgs, results[d]->msgs[c]); - } - } - - *ldb_results = res; - ret = EOK; - -done: - if (ret != EOK) { - talloc_free(res); - } - - return ret; -} - -/* Return true if hint is set for at least one domain */ -static bool get_user_name_hint(struct sss_domain_info *domains) -{ - struct sss_domain_info *d; - - DLIST_FOR_EACH(d, domains) { - if (d->user_name_hint == true) { - return true; - } - } - - return false; -} - -static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req) -{ - int ret; - struct cache_req_result **results; - struct pam_auth_req *preq = tevent_req_callback_data(req, - struct pam_auth_req); - const char *cert_user = NULL; - size_t cert_count = 0; - size_t cert_user_count = 0; - struct ldb_result *cert_user_objs; - - ret = cache_req_recv(preq, req, &results); - talloc_zfree(req); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert request failed.\n"); - goto done; - } - - if (ret == EOK) { - ret = get_results_from_all_domains(preq, results, - &cert_user_objs); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "get_results_from_all_domains failed.\n"); - goto done; - } - - sss_cai_set_cert_user_objs(preq->current_cert, cert_user_objs); - } - - preq->current_cert = sss_cai_get_next(preq->current_cert); - if (preq->current_cert != NULL) { - ret = pam_user_by_cert_step(preq); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n"); - goto done; - } - return; - } - - sss_cai_check_users(&preq->cert_list, &cert_count, &cert_user_count); - DEBUG(SSSDBG_TRACE_ALL, - "Found [%zu] certificates and [%zu] related users.\n", - cert_count, cert_user_count); - - if (cert_user_count == 0) { - if (preq->pd->logon_name == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Missing logon name and no certificate user found.\n"); - ret = ENOENT; - goto done; - } - } else { - - if (preq->pd->logon_name == NULL) { - if (preq->pd->cmd != SSS_PAM_PREAUTH - && preq->pd->cmd != SSS_PAM_AUTHENTICATE) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Missing logon name only allowed during (pre-)auth.\n"); - ret = ENOENT; - goto done; - } - /* Multiple certificates are only expected during pre-auth */ - if (cert_count > 1 && preq->pd->cmd == SSS_PAM_PREAUTH) { - for (preq->current_cert = preq->cert_list; - preq->current_cert != NULL; - preq->current_cert = sss_cai_get_next(preq->current_cert)) { - - ret = add_pam_cert_response(preq->pd, - preq->cctx->rctx->domains, "", - preq->current_cert, - get_user_name_hint(preq->cctx->rctx->domains) - ? SSS_PAM_CERT_INFO_WITH_HINT - : SSS_PAM_CERT_INFO); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "add_pam_cert_response failed.\n"); - preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; - } - } - - ret = EOK; - preq->pd->pam_status = PAM_SUCCESS; - pam_reply(preq); - goto done; - } - - if (cert_user_count == 1) { - cert_user_objs = sss_cai_get_cert_user_objs(preq->cert_list); - if (cert_user_objs == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate user.\n"); - ret = ENOENT; - goto done; - } - - cert_user = ldb_msg_find_attr_as_string( - cert_user_objs->msgs[0], - SYSDB_NAME, NULL); - if (cert_user == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Certificate user object has not name.\n"); - ret = ENOENT; - goto done; - } - - DEBUG(SSSDBG_FUNC_DATA, - "Found certificate user [%s].\n", cert_user); - - ret = sss_parse_name_for_domains(preq->pd, - preq->cctx->rctx->domains, - preq->cctx->rctx->default_domain, - cert_user, - &preq->pd->domain, - &preq->pd->user); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "sss_parse_name_for_domains failed.\n"); - goto done; - } - } - - if (get_user_name_hint(preq->cctx->rctx->domains) - && preq->pd->cmd == SSS_PAM_PREAUTH) { - ret = add_pam_cert_response(preq->pd, - preq->cctx->rctx->domains, cert_user, - preq->cert_list, - SSS_PAM_CERT_INFO_WITH_HINT); - preq->pd->pam_status = PAM_SUCCESS; - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); - preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; - } - ret = EOK; - pam_reply(preq); - goto done; - } - - /* Without user name hints the certificate must map to single user - * if no login name was given */ - if (cert_user == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "More than one user mapped to certificate.\n"); - ret = ERR_NO_CREDS; - goto done; - } - - /* If logon_name was not given during authentication add a - * SSS_PAM_CERT_INFO message to send the name to the caller. - * Additionally initial_cert_auth_successful is set to - * indicate that the user is already authenticated. */ - if (preq->pd->cmd == SSS_PAM_AUTHENTICATE - && preq->pd->logon_name == NULL) { - ret = add_pam_cert_response(preq->pd, - preq->cctx->rctx->domains, cert_user, - preq->cert_list, - SSS_PAM_CERT_INFO); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); - preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; - goto done; - } - - preq->initial_cert_auth_successful = true; - } - - /* cert_user will be returned to the PAM client as user name, so - * we can use it here already e.g. to set in initgroups timeout */ - preq->pd->logon_name = talloc_strdup(preq->pd, cert_user); - if (preq->pd->logon_name == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); - ret = ENOMEM; - goto done; - } - } - } - - if (preq->user_obj == NULL) { - ret = pam_check_user_search(preq); - } else { - ret = EOK; - } - - if (ret == EOK) { - pam_dom_forwarder(preq); - } - -done: - pam_check_user_done(preq, ret); -} - -static void pam_forwarder_cb(struct tevent_req *req) -{ - struct pam_auth_req *preq = tevent_req_callback_data(req, - struct pam_auth_req); - struct cli_ctx *cctx = preq->cctx; - struct pam_data *pd; - errno_t ret = EOK; - struct pam_ctx *pctx = - talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); - - ret = sss_dp_get_domains_recv(req); - talloc_free(req); - if (ret != EOK) { - goto done; - } - - ret = p11_refresh_certmap_ctx(pctx, pctx->rctx->domains); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "p11_refresh_certmap_ctx failed, " - "certificate matching might not work as expected"); - } - - pd = preq->pd; - - ret = pam_forwarder_parse_data(cctx, pd); - if (ret == EAGAIN) { - DEBUG(SSSDBG_TRACE_FUNC, "Assuming %s is a UPN\n", pd->logon_name); - /* If not, cache_req will error out later */ - pd->user = talloc_strdup(pd, pd->logon_name); - if (pd->user == NULL) { - ret = ENOMEM; - goto done; - } - pd->domain = NULL; - } else if (ret != EOK) { - ret = EINVAL; - goto done; - } - - /* try backend first for authentication before doing local Smartcard - * authentication */ - if (pd->cmd != SSS_PAM_AUTHENTICATE && may_do_cert_auth(pctx, pd)) { - ret = check_cert(cctx, cctx->ev, pctx, preq, pd); - /* Finish here */ - goto done; - } - -#ifdef BUILD_PASSKEY - /* This is set to false inside passkey_local() if no passkey data is found. - * It is checked in pam_reply() to avoid an endless loop */ - preq->passkey_data_exists = true; - - if ((pd->cmd == SSS_PAM_AUTHENTICATE)) { - if (may_do_passkey_auth(pctx, pd)) { - if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { - ret = passkey_kerberos(pctx, preq->pd, preq); - goto done; - } else if ((sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY) || - (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_EMPTY)) { - ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); - goto done; - } - } - } -#endif /* BUILD_PASSKEY */ - - ret = pam_check_user_search(preq); - -done: - pam_check_user_done(preq, ret); -} - -static void pam_check_user_search_next(struct tevent_req *req); -static void pam_check_user_search_lookup(struct tevent_req *req); -static void pam_check_user_search_done(struct pam_auth_req *preq, int ret, - struct cache_req_result *result); - -/* lookup the user uid from the cache first, - * then we'll refresh initgroups if needed */ -int pam_check_user_search(struct pam_auth_req *preq) -{ - struct tevent_req *dpreq; - struct cache_req_data *data; - - data = cache_req_data_name(preq, - CACHE_REQ_INITGROUPS, - preq->pd->logon_name); - if (data == NULL) { - return ENOMEM; - } - - cache_req_data_set_bypass_cache(data, false); - cache_req_data_set_bypass_dp(data, true); - cache_req_data_set_requested_domains(data, preq->pd->requested_domains); - - dpreq = cache_req_send(preq, - preq->cctx->rctx->ev, - preq->cctx->rctx, - preq->cctx->rctx->ncache, - 0, - preq->req_dom_type, - NULL, - data); - if (!dpreq) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Out of memory sending data provider request\n"); - return ENOMEM; - } - - tevent_req_set_callback(dpreq, pam_check_user_search_next, preq); - - /* tell caller we are in an async call */ - return EAGAIN; -} - -static void pam_check_user_search_next(struct tevent_req *req) -{ - struct pam_auth_req *preq; - struct pam_ctx *pctx; - struct cache_req_result *result = NULL; - struct cache_req_data *data; - struct tevent_req *dpreq; - int ret; - - preq = tevent_req_callback_data(req, struct pam_auth_req); - pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); - - ret = cache_req_single_domain_recv(preq, req, &result); - talloc_zfree(req); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_OP_FAILURE, "Cache lookup failed, trying to get fresh " - "data from the backend.\n"); - } - - DEBUG(SSSDBG_TRACE_ALL, "PAM initgroups scheme [%s].\n", - pam_initgroup_enum_to_string(pctx->initgroups_scheme)); - - if (ret == EOK) { - bool user_has_session = false; - - if (pctx->initgroups_scheme == PAM_INITGR_NO_SESSION) { - uid_t uid = ldb_msg_find_attr_as_uint64(result->msgs[0], - SYSDB_UIDNUM, 0); - if (!uid) { - DEBUG(SSSDBG_CRIT_FAILURE, "A user with no UID?\n"); - talloc_zfree(preq->cctx); - return; - } - - /* If a user already has a session on the system, we take the - * cache for granted and do not force an online lookup. This is - * because in most cases the user is just trying to authenticate - * but not create a new session (sudo, lockscreen, polkit, etc.) - * An online refresh in this situation would just delay operations - * without providing any useful additional information. - */ - (void)check_if_uid_is_active(uid, &user_has_session); - - DEBUG(SSSDBG_TRACE_ALL, "Found %s session for uid %"SPRIuid".\n", - user_has_session ? "a" : "no", uid); - } - - /* The initgr cache is used to make sure that during a single PAM - * session (auth, acct_mgtm, ....) the backend is contacted only - * once. logon_name is the name provided by the PAM client and - * will not be modified during the request, so it makes sense to - * use it here instead od the pd->user. - */ - ret = pam_initgr_check_timeout(pctx->id_table, preq->pd->logon_name); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_OP_FAILURE, "Could not look up initgroup timeout\n"); - } - - if ((ret == EOK) || user_has_session - || pctx->initgroups_scheme == PAM_INITGR_NEVER) { - DEBUG(SSSDBG_TRACE_ALL, "No new initgroups needed because:\n"); - if (ret == EOK) { - DEBUG(SSSDBG_TRACE_ALL, "PAM initgr cache still valid.\n"); - } else if (user_has_session) { - DEBUG(SSSDBG_TRACE_ALL, "there is a active session for " - "user [%s].\n", preq->pd->logon_name); - } else if (pctx->initgroups_scheme == PAM_INITGR_NEVER) { - DEBUG(SSSDBG_TRACE_ALL, "initgroups scheme is 'never'.\n"); - } - pam_check_user_search_done(preq, EOK, result); - return; - } - } - - /* If we get here it means the user was not found or does not have a - * session, or initgr has not been cached before, so we force a new - * online lookup */ - data = cache_req_data_name(preq, - CACHE_REQ_INITGROUPS, - preq->pd->logon_name); - if (data == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory\n"); - talloc_zfree(preq->cctx); - return; - } - cache_req_data_set_bypass_cache(data, true); - cache_req_data_set_bypass_dp(data, false); - cache_req_data_set_requested_domains(data, preq->pd->requested_domains); - - dpreq = cache_req_send(preq, - preq->cctx->rctx->ev, - preq->cctx->rctx, - preq->cctx->rctx->ncache, - 0, - preq->req_dom_type, - NULL, - data); - if (!dpreq) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Out of memory sending data provider request\n"); - talloc_zfree(preq->cctx); - return; - } - - tevent_req_set_callback(dpreq, pam_check_user_search_lookup, preq); -} - -static void pam_check_user_search_lookup(struct tevent_req *req) -{ - struct cache_req_result *result; - struct pam_auth_req *preq; - int ret; - - preq = tevent_req_callback_data(req, struct pam_auth_req); - - ret = cache_req_single_domain_recv(preq, req, &result); - talloc_zfree(req); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Fatal error, killing connection!\n"); - talloc_zfree(preq->cctx); - return; - } - - pam_check_user_search_done(preq, ret, result); -} - -static void pam_check_user_search_done(struct pam_auth_req *preq, int ret, - struct cache_req_result *result) -{ - struct pam_ctx *pctx; - - pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); - - if (ret == EOK) { - preq->user_obj = result->msgs[0]; - pd_set_primary_name(preq->user_obj, preq->pd); - preq->domain = result->domain; - - ret = pam_initgr_cache_set(pctx->rctx->ev, - pctx->id_table, - preq->pd->logon_name, - pctx->id_timeout); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Could not save initgr timestamp." - "Proceeding with PAM actions\n"); - } - - pam_dom_forwarder(preq); - } - - ret = pam_check_user_done(preq, ret); - if (ret != EOK) { - preq->pd->pam_status = PAM_SYSTEM_ERR; - pam_reply(preq); - } -} - -int pam_check_user_done(struct pam_auth_req *preq, int ret) -{ - switch (ret) { - case EOK: - break; - - case EAGAIN: - /* performing async request, just return */ - break; - - case ENOENT: - preq->pd->pam_status = PAM_USER_UNKNOWN; - pam_reply(preq); - break; - - case ERR_P11_PIN_LOCKED: - preq->pd->pam_status = PAM_AUTH_ERR; - pam_reply(preq); - break; - - case ERR_NO_CREDS: - preq->pd->pam_status = PAM_CRED_INSUFFICIENT; - pam_reply(preq); - break; - - default: - preq->pd->pam_status = PAM_SYSTEM_ERR; - pam_reply(preq); - break; - } - - return EOK; -} - -static errno_t pam_is_last_online_login_fresh(struct sss_domain_info *domain, - const char* user, - int cached_auth_timeout, - bool *_result) -{ - errno_t ret; - bool result = true; - uint64_t last_login; - - ret = pam_get_last_online_auth_with_curr_token(domain, user, &last_login); - if (ret != EOK) { - DEBUG(SSSDBG_MINOR_FAILURE, - "sysdb_get_last_online_auth_with_curr_token failed: %s:[%d]\n", - sss_strerror(ret), ret); - goto done; - } - - result = time(NULL) < (last_login + cached_auth_timeout); - ret = EOK; - -done: - if (ret == EOK) { - *_result = result; - } - return ret; -} - -static bool pam_is_authtok_cachable(struct sss_auth_token *authtok) -{ - enum sss_authtok_type type; - bool cachable = false; - - type = sss_authtok_get_type(authtok); - if (type == SSS_AUTHTOK_TYPE_PASSWORD) { - cachable = true; - } else { - DEBUG(SSSDBG_TRACE_LIBS, "Authentication token can't be cached\n"); - } - - return cachable; -} - -static bool pam_can_user_cache_auth(struct sss_domain_info *domain, - int pam_cmd, - struct sss_auth_token *authtok, - const char* user, - bool cached_auth_failed) -{ - errno_t ret; - bool result = false; - - if (cached_auth_failed) { - /* Do not retry indefinitely */ - return false; - } - - if (!domain->cache_credentials || domain->cached_auth_timeout <= 0) { - return false; - } - - if (pam_cmd == SSS_PAM_PREAUTH - || (pam_cmd == SSS_PAM_AUTHENTICATE - && pam_is_authtok_cachable(authtok))) { - - ret = pam_is_last_online_login_fresh(domain, user, - domain->cached_auth_timeout, - &result); - if (ret != EOK) { - /* non-critical, consider fail as 'non-fresh value' */ - DEBUG(SSSDBG_MINOR_FAILURE, - "pam_is_last_online_login_fresh failed: %s:[%d]\n", - sss_strerror(ret), ret); - } - } - - return result; -} - -static void pam_dom_forwarder(struct pam_auth_req *preq) -{ - TALLOC_CTX *tmp_ctx = NULL; - int ret; - struct pam_ctx *pctx = - talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); - const char *cert_user; - struct ldb_result *cert_user_objs; - bool sc_auth; - bool passkey_auth; - size_t c; - char *local_policy = NULL; - bool found = false; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - return; - } - - if (!preq->pd->domain) { - preq->pd->domain = preq->domain->name; - } - - /* Untrusted users can access only public domains. */ - if (!preq->is_uid_trusted && - !is_domain_public(preq->pd->domain, pctx->public_domains, - pctx->public_domains_count)) { - DEBUG(SSSDBG_MINOR_FAILURE, - "Untrusted user %"SPRIuid" cannot access non-public domain %s.\n", - client_euid(preq->cctx->creds), preq->pd->domain); - preq->pd->pam_status = PAM_PERM_DENIED; - pam_reply(preq); - return; - } - - /* skip this domain if not requested and the user is trusted - * as untrusted users can't request a domain */ - if (preq->is_uid_trusted && - !is_domain_requested(preq->pd, preq->pd->domain)) { - preq->pd->pam_status = PAM_USER_UNKNOWN; - pam_reply(preq); - return; - } - - if (pam_can_user_cache_auth(preq->domain, - preq->pd->cmd, - preq->pd->authtok, - preq->pd->user, - preq->cached_auth_failed)) { - preq->use_cached_auth = true; - pam_reply(preq); - return; - } - - /* Skip online auth when local auth policy = only */ -#ifdef BUILD_PASSKEY - if (may_do_cert_auth(pctx, preq->pd) || may_do_passkey_auth(pctx, preq->pd)) { -#else - if (may_do_cert_auth(pctx, preq->pd)) { -#endif /* BUILD_PASSKEY */ - if (preq->domain->name != NULL) { - ret = pam_eval_local_auth_policy(preq->cctx, pctx, preq->pd, preq, - &sc_auth, - &passkey_auth, - &local_policy); - if (ret != EOK) { - DEBUG(SSSDBG_FATAL_FAILURE, - "Failed to evaluate local auth policy\n"); - preq->pd->pam_status = PAM_AUTH_ERR; - pam_reply(preq); - return; - } - } - } - - if (may_do_cert_auth(pctx, preq->pd) && preq->cert_list != NULL) { - /* Check if user matches certificate user */ - found = false; - for (preq->current_cert = preq->cert_list; - preq->current_cert != NULL; - preq->current_cert = sss_cai_get_next(preq->current_cert)) { - - cert_user_objs = sss_cai_get_cert_user_objs(preq->current_cert); - if (cert_user_objs == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "Unexpected missing certificate user, " - "trying next certificate.\n"); - continue; - } - - for (c = 0; c < cert_user_objs->count; c++) { - cert_user = ldb_msg_find_attr_as_string(cert_user_objs->msgs[c], - SYSDB_NAME, NULL); - if (cert_user == NULL) { - /* Even if there might be other users mapped to the - * certificate a missing SYSDB_NAME indicates some critical - * condition which justifies that the whole request is aborted - * */ - DEBUG(SSSDBG_CRIT_FAILURE, - "Certificate user object has no name.\n"); - preq->pd->pam_status = PAM_USER_UNKNOWN; - pam_reply(preq); - return; - } - - if (ldb_dn_compare(cert_user_objs->msgs[c]->dn, - preq->user_obj->dn) == 0) { - found = true; - if (preq->pd->cmd == SSS_PAM_PREAUTH) { - ret = sss_authtok_set_sc(preq->pd->authtok, - SSS_AUTHTOK_TYPE_SC_PIN, NULL, 0, - sss_cai_get_token_name(preq->current_cert), 0, - sss_cai_get_module_name(preq->current_cert), 0, - sss_cai_get_key_id(preq->current_cert), 0, - sss_cai_get_label(preq->current_cert), 0); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "sss_authtok_set_sc failed, Smartcard " - "authentication detection might fail in " - "the backend.\n"); - } - - ret = add_pam_cert_response(preq->pd, - preq->cctx->rctx->domains, - cert_user, - preq->current_cert, - SSS_PAM_CERT_INFO); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); - preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; - } - } - - } - } - } - - if (found) { - if (local_policy != NULL && strcasecmp(local_policy, "only") == 0) { - talloc_free(tmp_ctx); - DEBUG(SSSDBG_IMPORTANT_INFO, - "Local auth only set and matching certificate was found, " - "skipping online auth\n"); - if (preq->pd->cmd == SSS_PAM_PREAUTH) { - preq->pd->pam_status = PAM_SUCCESS; - } else if (preq->pd->cmd == SSS_PAM_AUTHENTICATE - && IS_SC_AUTHTOK(preq->pd->authtok) - && (preq->cert_auth_local - || preq->initial_cert_auth_successful)) { - preq->pd->pam_status = PAM_SUCCESS; - preq->callback = pam_reply; - } - - pam_reply(preq); - return; - } - - /* We are done if we do not have to call the backend */ - if (preq->pd->cmd == SSS_PAM_AUTHENTICATE - && preq->cert_auth_local) { - preq->pd->pam_status = PAM_SUCCESS; - preq->callback = pam_reply; - pam_reply(preq); - return; - } - } else { - if (preq->pd->cmd == SSS_PAM_PREAUTH) { - DEBUG(SSSDBG_TRACE_FUNC, - "User and certificate user do not match, " - "continue with other authentication methods.\n"); - } else { - DEBUG(SSSDBG_CRIT_FAILURE, - "User and certificate user do not match.\n"); - preq->pd->pam_status = PAM_AUTH_ERR; - pam_reply(preq); - return; - } - } - } - - if (local_policy != NULL && strcasecmp(local_policy, "only") == 0) { - talloc_free(tmp_ctx); - DEBUG(SSSDBG_IMPORTANT_INFO, "Local auth only set, skipping online auth\n"); - if (preq->pd->cmd == SSS_PAM_PREAUTH) { - preq->pd->pam_status = PAM_SUCCESS; - } else if (preq->pd->cmd == SSS_PAM_AUTHENTICATE && IS_SC_AUTHTOK(preq->pd->authtok)) { - /* Trigger offline smartcardcard autheitcation */ - preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; - } - - pam_reply(preq); - return; - } - - preq->callback = pam_reply; - ret = pam_dp_send_req(preq); - DEBUG(SSSDBG_CONF_SETTINGS, "pam_dp_send_req returned %d\n", ret); - - talloc_free(tmp_ctx); - - if (ret != EOK) { - preq->pd->pam_status = PAM_SYSTEM_ERR; - pam_reply(preq); - } -} - -static int pam_cmd_authenticate(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_authenticate\n"); - return pam_forwarder(cctx, SSS_PAM_AUTHENTICATE); -} - -static int pam_cmd_setcred(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_setcred\n"); - return pam_forwarder(cctx, SSS_PAM_SETCRED); -} - -static int pam_cmd_acct_mgmt(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_acct_mgmt\n"); - return pam_forwarder(cctx, SSS_PAM_ACCT_MGMT); -} - -static int pam_cmd_open_session(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_open_session\n"); - return pam_forwarder(cctx, SSS_PAM_OPEN_SESSION); -} - -static int pam_cmd_close_session(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_close_session\n"); - return pam_forwarder(cctx, SSS_PAM_CLOSE_SESSION); -} - -static int pam_cmd_chauthtok(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_chauthtok\n"); - return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK); -} - -static int pam_cmd_chauthtok_prelim(struct cli_ctx *cctx) { - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_chauthtok_prelim\n"); - return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK_PRELIM); -} - -static int pam_cmd_preauth(struct cli_ctx *cctx) -{ - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_preauth\n"); - return pam_forwarder(cctx, SSS_PAM_PREAUTH); -} - -static int pam_cmd_passkey_preauth(struct cli_ctx *cctx) -{ - DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_passkey_preauth\n"); - return pam_forwarder(cctx, SSS_PAM_PASSKEY_PREAUTH); -} - -struct cli_protocol_version *register_cli_protocol_version(void) -{ - static struct cli_protocol_version pam_cli_protocol_version[] = { - {3, "2009-09-14", "make cli_pid mandatory"}, - {2, "2009-05-12", "new format "}, - {1, "2008-09-05", "initial version, \\0 terminated strings"}, - {0, NULL, NULL} - }; - - return pam_cli_protocol_version; -} - -struct sss_cmd_table *get_pam_cmds(void) -{ - static struct sss_cmd_table sss_cmds[] = { - {SSS_GET_VERSION, sss_cmd_get_version}, - {SSS_PAM_AUTHENTICATE, pam_cmd_authenticate}, - {SSS_PAM_SETCRED, pam_cmd_setcred}, - {SSS_PAM_ACCT_MGMT, pam_cmd_acct_mgmt}, - {SSS_PAM_OPEN_SESSION, pam_cmd_open_session}, - {SSS_PAM_CLOSE_SESSION, pam_cmd_close_session}, - {SSS_PAM_CHAUTHTOK, pam_cmd_chauthtok}, - {SSS_PAM_CHAUTHTOK_PRELIM, pam_cmd_chauthtok_prelim}, - {SSS_PAM_PREAUTH, pam_cmd_preauth}, - {SSS_PAM_PASSKEY_PREAUTH, pam_cmd_passkey_preauth}, - {SSS_GSSAPI_INIT, pam_cmd_gssapi_init}, - {SSS_GSSAPI_SEC_CTX, pam_cmd_gssapi_sec_ctx}, - {SSS_CLI_NULL, NULL} - }; - - return sss_cmds; -} - -errno_t -pam_set_last_online_auth_with_curr_token(struct sss_domain_info *domain, - const char *username, - uint64_t value) -{ - TALLOC_CTX *tmp_ctx; - struct sysdb_attrs *attrs; - int ret; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - attrs = sysdb_new_attrs(tmp_ctx); - if (attrs == NULL) { - ret = ENOMEM; - goto done; - } - - ret = sysdb_attrs_add_time_t(attrs, - SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, - value); - if (ret != EOK) { goto done; } - - ret = sysdb_set_user_attr(domain, username, attrs, SYSDB_MOD_REP); - if (ret != EOK) { goto done; } - -done: - if (ret != EOK) { - DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, sss_strerror(ret)); - } - - talloc_zfree(tmp_ctx); - return ret; -} - -static errno_t -pam_null_last_online_auth_with_curr_token(struct sss_domain_info *domain, - const char *username) -{ - return pam_set_last_online_auth_with_curr_token(domain, username, 0); -} - -static errno_t -pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain, - const char *name, - uint64_t *_value) -{ - TALLOC_CTX *tmp_ctx = NULL; - const char *attrs[] = { SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, NULL }; - struct ldb_message *ldb_msg; - uint64_t value = 0; - errno_t ret; - - if (name == NULL || *name == '\0') { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); - ret = EINVAL; - goto done; - } - - if (domain->sysdb == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing sysdb db context.\n"); - ret = EINVAL; - goto done; - } - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - ret = sysdb_search_user_by_name(tmp_ctx, domain, name, attrs, &ldb_msg); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, - "sysdb_search_user_by_name failed [%d][%s].\n", - ret, strerror(ret)); - goto done; - } - - /* Check offline_auth_cache_timeout */ - value = ldb_msg_find_attr_as_uint64(ldb_msg, - SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, - 0); - ret = EOK; - -done: - if (ret == EOK) { - *_value = value; - } - - talloc_free(tmp_ctx); - return ret; -} +/* + SSSD + + PAM Responder + + Copyright (C) Simo Sorce 2009 + Copyright (C) Sumit Bose 2009 + + 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 . +*/ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include + +#include "util/util.h" +#include "util/auth_utils.h" +#include "util/find_uid.h" +#include "util/sss_ptr_hash.h" +#include "db/sysdb.h" + +#include "confdb/confdb.h" +#include "responder/common/responder_packet.h" +#include "responder/common/responder.h" +#include "responder/common/negcache.h" +#include "providers/data_provider.h" +#include "responder/pam/pamsrv.h" +#include "responder/pam/pamsrv_passkey.h" +#include "responder/pam/pam_helpers.h" +#include "responder/common/cache_req/cache_req.h" + +enum pam_verbosity { + PAM_VERBOSITY_NO_MESSAGES = 0, + PAM_VERBOSITY_IMPORTANT, + PAM_VERBOSITY_INFO, + PAM_VERBOSITY_DEBUG +}; + +#define DEFAULT_PAM_VERBOSITY PAM_VERBOSITY_IMPORTANT + +struct pam_initgroup_enum_str { + enum pam_initgroups_scheme scheme; + const char *option; +}; + +struct pam_initgroup_enum_str pam_initgroup_enum_str[] = { + { PAM_INITGR_NEVER, "never" }, + { PAM_INITGR_NO_SESSION, "no_session" }, + { PAM_INITGR_ALWAYS, "always" }, + { PAM_INITGR_INVALID, NULL } +}; + +enum pam_initgroups_scheme pam_initgroups_string_to_enum(const char *str) +{ + size_t c; + + for (c = 0 ; pam_initgroup_enum_str[c].option != NULL; c++) { + if (strcasecmp(pam_initgroup_enum_str[c].option, str) == 0) { + return pam_initgroup_enum_str[c].scheme; + } + } + + return PAM_INITGR_INVALID; +} + +const char *pam_initgroup_enum_to_string(enum pam_initgroups_scheme scheme) +{ + size_t c; + + for (c = 0 ; pam_initgroup_enum_str[c].option != NULL; c++) { + if (pam_initgroup_enum_str[c].scheme == scheme) { + return pam_initgroup_enum_str[c].option; + } + } + + return "(NULL)"; +} + +static errno_t +pam_null_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *username); +static errno_t +pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *name, + uint64_t *_value); + +void pam_reply(struct pam_auth_req *preq); + +static errno_t check_cert(TALLOC_CTX *mctx, + struct tevent_context *ev, + struct pam_ctx *pctx, + struct pam_auth_req *preq, + struct pam_data *pd); + +int pam_check_user_done(struct pam_auth_req *preq, int ret); + +static errno_t pack_user_info_msg(TALLOC_CTX *mem_ctx, + const char *user_error_message, + size_t *resp_len, + uint8_t **_resp) +{ + uint32_t resp_type = SSS_PAM_USER_INFO_ACCOUNT_EXPIRED; + size_t err_len; + uint8_t *resp; + size_t p; + + err_len = strlen(user_error_message); + *resp_len = 2 * sizeof(uint32_t) + err_len; + resp = talloc_size(mem_ctx, *resp_len); + if (resp == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + p = 0; + SAFEALIGN_SET_UINT32(&resp[p], resp_type, &p); + SAFEALIGN_SET_UINT32(&resp[p], err_len, &p); + safealign_memcpy(&resp[p], user_error_message, err_len, &p); + if (p != *resp_len) { + DEBUG(SSSDBG_FATAL_FAILURE, "Size mismatch\n"); + } + + *_resp = resp; + return EOK; +} + +static void inform_user(struct pam_data* pd, const char *pam_message) +{ + size_t msg_len; + uint8_t *msg; + errno_t ret; + + ret = pack_user_info_msg(pd, pam_message, &msg_len, &msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "pack_user_info_msg failed.\n"); + } else { + ret = pam_add_response(pd, SSS_PAM_USER_INFO, msg_len, msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + } +} + +static bool is_domain_requested(struct pam_data *pd, const char *domain_name) +{ + int i; + + /* If none specific domains got requested via pam, all domains are allowed. + * Which mimics the default/original behaviour. + */ + if (!pd->requested_domains) { + return true; + } + + for (i = 0; pd->requested_domains[i]; i++) { + if (strcasecmp(domain_name, pd->requested_domains[i])) { + continue; + } + + return true; + } + + return false; +} + +static int extract_authtok_v2(struct sss_auth_token *tok, + size_t data_size, uint8_t *body, size_t blen, + size_t *c) +{ + uint32_t auth_token_type; + uint32_t auth_token_length; + uint8_t *auth_token_data; + int ret = EOK; + + if (data_size < sizeof(uint32_t) || *c+data_size > blen || + SIZE_T_OVERFLOW(*c, data_size)) return EINVAL; + + SAFEALIGN_COPY_UINT32_CHECK(&auth_token_type, &body[*c], blen, c); + auth_token_length = data_size - sizeof(uint32_t); + auth_token_data = body+(*c); + + switch (auth_token_type) { + case SSS_AUTHTOK_TYPE_EMPTY: + sss_authtok_set_empty(tok); + break; + case SSS_AUTHTOK_TYPE_PASSWORD: + if (auth_token_length == 0) { + sss_authtok_set_empty(tok); + } else { + ret = sss_authtok_set_password(tok, (const char *)auth_token_data, + auth_token_length); + } + break; + case SSS_AUTHTOK_TYPE_2FA: + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + case SSS_AUTHTOK_TYPE_SC_PIN: + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_OAUTH2: + case SSS_AUTHTOK_TYPE_PASSKEY: + case SSS_AUTHTOK_TYPE_PASSKEY_KRB: + case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: + case SSS_AUTHTOK_TYPE_PAM_STACKED: + ret = sss_authtok_set(tok, auth_token_type, + auth_token_data, auth_token_length); + break; + default: + return EINVAL; + } + + *c += auth_token_length; + + return ret; +} + +static int extract_string(char **var, size_t size, uint8_t *body, size_t blen, + size_t *c) { + uint8_t *str; + + if (*c+size > blen || SIZE_T_OVERFLOW(*c, size)) return EINVAL; + + str = body+(*c); + + if (str[size-1]!='\0') return EINVAL; + + /* If the string isn't valid UTF-8, fail */ + if (!sss_utf8_check(str, size-1)) { + return EINVAL; + } + + *c += size; + + *var = (char *) str; + + return EOK; +} + +static int extract_uint32_t(uint32_t *var, size_t size, uint8_t *body, + size_t blen, size_t *c) { + + if (size != sizeof(uint32_t) || *c+size > blen || SIZE_T_OVERFLOW(*c, size)) + return EINVAL; + + SAFEALIGN_COPY_UINT32_CHECK(var, &body[*c], blen, c); + + return EOK; +} + +static int pd_set_primary_name(const struct ldb_message *msg,struct pam_data *pd) +{ + const char *name; + + name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + if (!name) { + DEBUG(SSSDBG_CRIT_FAILURE, "A user with no name?\n"); + return EIO; + } + + if (strcmp(pd->user, name)) { + DEBUG(SSSDBG_TRACE_FUNC, "User's primary name is %s\n", name); + talloc_free(pd->user); + pd->user = talloc_strdup(pd, name); + if (!pd->user) return ENOMEM; + } + + return EOK; +} + +static int pam_parse_in_data_v2(struct pam_data *pd, + uint8_t *body, size_t blen) +{ + size_t c; + uint32_t type; + uint32_t size; + int ret; + uint32_t start; + uint32_t terminator; + char *requested_domains; + + if (blen < 4*sizeof(uint32_t)+2) { + DEBUG(SSSDBG_CRIT_FAILURE, "Received data is invalid.\n"); + return EINVAL; + } + + SAFEALIGN_COPY_UINT32(&start, body, NULL); + SAFEALIGN_COPY_UINT32(&terminator, body + blen - sizeof(uint32_t), NULL); + + if (start != SSS_START_OF_PAM_REQUEST + || terminator != SSS_END_OF_PAM_REQUEST) { + DEBUG(SSSDBG_CRIT_FAILURE, "Received data is invalid.\n"); + return EINVAL; + } + + c = sizeof(uint32_t); + do { + SAFEALIGN_COPY_UINT32_CHECK(&type, &body[c], blen, &c); + + if (type == SSS_END_OF_PAM_REQUEST) { + if (c != blen) return EINVAL; + } else { + SAFEALIGN_COPY_UINT32_CHECK(&size, &body[c], blen, &c); + /* the uint32_t end maker SSS_END_OF_PAM_REQUEST does not count to + * the remaining buffer */ + if (size > (blen - c - sizeof(uint32_t))) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid data size.\n"); + return EINVAL; + } + + switch(type) { + case SSS_PAM_ITEM_USER: + ret = extract_string(&pd->logon_name, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_SERVICE: + ret = extract_string(&pd->service, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_TTY: + ret = extract_string(&pd->tty, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_RUSER: + ret = extract_string(&pd->ruser, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_RHOST: + ret = extract_string(&pd->rhost, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_REQUESTED_DOMAINS: + ret = extract_string(&requested_domains, size, body, blen, + &c); + if (ret != EOK) return ret; + + ret = split_on_separator(pd, requested_domains, ',', true, + true, &pd->requested_domains, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse requested_domains list!\n"); + return ret; + } + break; + case SSS_PAM_ITEM_CLI_PID: + ret = extract_uint32_t(&pd->cli_pid, size, + body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_CHILD_PID: + /* This is optional. */ + ret = extract_uint32_t(&pd->child_pid, size, + body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_AUTHTOK: + ret = extract_authtok_v2(pd->authtok, + size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_NEWAUTHTOK: + ret = extract_authtok_v2(pd->newauthtok, + size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_FLAGS: + ret = extract_uint32_t(&pd->cli_flags, size, + body, blen, &c); + if (ret != EOK) return ret; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Ignoring unknown data type [%d].\n", type); + c += size; + } + } + + } while(c < blen); + + return EOK; + +} + +static int pam_parse_in_data_v3(struct pam_data *pd, + uint8_t *body, size_t blen) +{ + int ret; + + ret = pam_parse_in_data_v2(pd, body, blen); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_parse_in_data_v2 failed.\n"); + return ret; + } + + if (pd->cli_pid == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing client PID.\n"); + return EINVAL; + } + + return EOK; +} + +static int extract_authtok_v1(struct sss_auth_token *tok, + uint8_t *body, size_t blen, size_t *c) +{ + uint32_t auth_token_type; + uint32_t auth_token_length; + uint8_t *auth_token_data; + int ret = EOK; + + SAFEALIGN_COPY_UINT32_CHECK(&auth_token_type, &body[*c], blen, c); + SAFEALIGN_COPY_UINT32_CHECK(&auth_token_length, &body[*c], blen, c); + auth_token_data = body+(*c); + + switch (auth_token_type) { + case SSS_AUTHTOK_TYPE_EMPTY: + sss_authtok_set_empty(tok); + break; + case SSS_AUTHTOK_TYPE_PASSWORD: + ret = sss_authtok_set_password(tok, (const char *)auth_token_data, + auth_token_length); + break; + default: + return EINVAL; + } + + *c += auth_token_length; + + return ret; +} + +static int pam_parse_in_data(struct pam_data *pd, + uint8_t *body, size_t blen) +{ + size_t start; + size_t end; + size_t last; + int ret; + + last = blen - 1; + end = 0; + + /* user name */ + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->logon_name = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->service = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->tty = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->ruser = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->rhost = (char *) &body[start]; + + ret = extract_authtok_v1(pd->authtok, body, blen, &end); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid auth token\n"); + return ret; + } + ret = extract_authtok_v1(pd->newauthtok, body, blen, &end); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid new auth token\n"); + return ret; + } + + DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); + + return EOK; +} + +static errno_t +pam_get_local_auth_policy(struct sss_domain_info *domain, + const char *name, + bool *_sc_allow, + bool *_passkey_allow) +{ + TALLOC_CTX *tmp_ctx = NULL; + const char *attrs[] = { SYSDB_LOCAL_SMARTCARD_AUTH, SYSDB_LOCAL_PASSKEY_AUTH, NULL }; + struct ldb_message *ldb_msg; + bool sc_allow = false; + bool passkey_allow = false; + errno_t ret; + + if (name == NULL || *name == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); + ret = EINVAL; + goto done; + } + + if (domain->sysdb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing sysdb db context.\n"); + ret = EINVAL; + goto done; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_user_by_name(tmp_ctx, domain, name, attrs, &ldb_msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_search_user_by_name failed [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + + sc_allow = ldb_msg_find_attr_as_bool(ldb_msg, SYSDB_LOCAL_SMARTCARD_AUTH, + false); + + passkey_allow = ldb_msg_find_attr_as_bool(ldb_msg, SYSDB_LOCAL_PASSKEY_AUTH, + true); + + ret = EOK; + +done: + if (ret == EOK) { + *_sc_allow = sc_allow; + *_passkey_allow = passkey_allow; + } + + talloc_free(tmp_ctx); + return ret; +} +static errno_t set_local_auth_type(struct pam_auth_req *preq, + bool sc_allow, + bool passkey_allow) +{ + struct sysdb_attrs *attrs; + errno_t ret; + + attrs = sysdb_new_attrs(preq); + if (!attrs) { + ret = ENOMEM; + goto fail; + } + + if (sc_allow) { + /* Only set SYSDB_LOCAL_SMARTCARD_AUTH to 'true' but never to + * 'false'. The krb5 backend will only returns that Smartcard + * authentication is available if a Smartcard is present. That means + * if the user authenticates with a different method and a Smartcard + * is not present at this time 'sc_allow' will be 'false' and might + * overwrite a 'true' value written during a previous authentication + * attempt where a Smartcard was present. To avoid this we only write + * 'true' values. Since the default if SYSDB_LOCAL_SMARTCARD_AUTH is + * missing is 'false' local Smartcard authentication (offline) will + * still only be enabled if online Smartcard authentication was + * detected. */ + ret = sysdb_attrs_add_bool(attrs, SYSDB_LOCAL_SMARTCARD_AUTH, sc_allow); + if (ret != EOK) { + goto fail; + } + } + + ret = sysdb_attrs_add_bool(attrs, SYSDB_LOCAL_PASSKEY_AUTH, passkey_allow); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_set_user_attr(preq->domain, preq->pd->user, attrs, + SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "set_local_auth_type failed.\n"); + preq->pd->pam_status = PAM_SYSTEM_ERR; + goto fail; + } + + return EOK; + +fail: + return ret; +} +/*=Save-Last-Login-State===================================================*/ + +static errno_t set_last_login(struct pam_auth_req *preq) +{ + struct sysdb_attrs *attrs; + errno_t ret; + + attrs = sysdb_new_attrs(preq); + if (!attrs) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_ONLINE_AUTH, time(NULL)); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_attrs_add_time_t(attrs, + SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, + time(NULL)); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_LOGIN, time(NULL)); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_set_user_attr(preq->domain, preq->pd->user, attrs, + SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "set_last_login failed.\n"); + preq->pd->pam_status = PAM_SYSTEM_ERR; + goto fail; + } else { + preq->pd->last_auth_saved = true; + } + preq->callback(preq); + + return EOK; + +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 ? var_name : "(NULL)"), + (service ? service : "(NULL)")); + + 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 pam_ctx *pctx, + struct response_data *resp_list, + struct pam_data *pd) +{ + int ret; + struct response_data *resp; + uint32_t user_info_type; + int64_t expire_date = 0; + int pam_verbosity = DEFAULT_PAM_VERBOSITY; + char **new_opts; + size_t c; + const char *default_pam_response_filter[] = { "ENV:KRB5CCNAME:sudo", + "ENV:KRB5CCNAME:sudo-i", + NULL }; + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_VERBOSITY, DEFAULT_PAM_VERBOSITY, + &pam_verbosity); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read PAM verbosity, not fatal.\n"); + pam_verbosity = DEFAULT_PAM_VERBOSITY; + } + + if (pctx->pam_filter_opts == NULL) { + ret = confdb_get_string_as_list(pctx->rctx->cdb, pctx, + CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_RESPONSE_FILTER, + &pctx->pam_filter_opts); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read values of [%s], not fatal.\n", + CONFDB_PAM_RESPONSE_FILTER); + pctx->pam_filter_opts = NULL; + } else { + if (pctx->pam_filter_opts == NULL + || *pctx->pam_filter_opts[0] == '+' + || *pctx->pam_filter_opts[0] == '-') { + ret = mod_defaults_list(pctx, default_pam_response_filter, + pctx->pam_filter_opts, &new_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to modify [%s] defaults.\n", + CONFDB_PAM_RESPONSE_FILTER); + return ret; + } + talloc_free(pctx->pam_filter_opts); + pctx->pam_filter_opts = new_opts; + } + } + + if (pctx->pam_filter_opts == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "No PAM response filter set.\n"); + } else { + /* Make sure there are no '+' or '-' prefixes anymore */ + for (c = 0; pctx->pam_filter_opts[c] != NULL; c++) { + if (*pctx->pam_filter_opts[0] == '+' + || *pctx->pam_filter_opts[0] == '-') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unsupport mix of prefixed and not prefixed " + "values of [%s].\n", CONFDB_PAM_RESPONSE_FILTER); + return EINVAL; + } + DEBUG(SSSDBG_CONF_SETTINGS, + "PAM response filter: [%s].\n", + pctx->pam_filter_opts[c]); + } + } + } + + 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"); + ret = EINVAL; + goto done; + } + + if (pam_verbosity == PAM_VERBOSITY_NO_MESSAGES) { + resp->do_not_send_to_client = true; + resp = resp->next; + continue; + } + + memcpy(&user_info_type, resp->data, sizeof(uint32_t)); + + resp->do_not_send_to_client = false; + switch (user_info_type) { + case SSS_PAM_USER_INFO_OFFLINE_AUTH: + if (resp->len != sizeof(uint32_t) + sizeof(int64_t)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "User info offline auth entry is " + "too short.\n"); + ret = EINVAL; + goto done; + } + memcpy(&expire_date, resp->data + sizeof(uint32_t), + sizeof(int64_t)); + if ((expire_date == 0 && + pam_verbosity < PAM_VERBOSITY_INFO) || + (expire_date > 0 && + pam_verbosity < PAM_VERBOSITY_IMPORTANT)) { + resp->do_not_send_to_client = true; + } + + break; + default: + DEBUG(SSSDBG_TRACE_LIBS, + "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, pctx->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; + } + + resp = resp->next; + } + + ret = EOK; +done: + + return ret; +} + +static void do_not_send_cert_info(struct pam_data *pd) +{ + struct response_data *resp; + + resp = pd->resp_list; + while (resp != NULL) { + switch (resp->type) { + case SSS_PAM_CERT_INFO: + case SSS_PAM_CERT_INFO_WITH_HINT: + resp->do_not_send_to_client = true; + break; + default: + break; + } + resp = resp->next; + } +} + +static void evaluate_pam_resp_list(struct pam_data *pd, + struct pam_resp_auth_type *_auth_types, + bool *_found_cert_info) +{ + struct response_data *resp; + struct pam_resp_auth_type types = {0}; + bool found_cert_info = false; + + resp = pd->resp_list; + while (resp != NULL) { + switch (resp->type) { + case SSS_PAM_OTP_INFO: + types.otp_auth = true; + break; + case SSS_PAM_CERT_INFO: + case SSS_PAM_CERT_INFO_WITH_HINT: + found_cert_info = true; + break; + case SSS_PAM_PASSKEY_INFO: + case SSS_PAM_PASSKEY_KRB_INFO: + types.passkey_auth = true; + break; + case SSS_PASSWORD_PROMPTING: + types.password_auth = true; + break; + case SSS_CERT_AUTH_PROMPTING: + types.cert_auth = true; + break; + default: + break; + } + resp = resp->next; + } + + if (_auth_types != NULL) { + *_auth_types = types; + } + if (_found_cert_info != NULL) { + *_found_cert_info = found_cert_info; + } +} + +static void evalute_sending_cert_info(struct pam_data *pd) +{ + struct pam_resp_auth_type types = {0}; + bool found_cert_info = false; + + evaluate_pam_resp_list(pd, &types, &found_cert_info); + + if (found_cert_info && !types.cert_auth) { + do_not_send_cert_info(pd); + } +} + +errno_t pam_get_auth_types(struct pam_data *pd, + struct pam_resp_auth_type *_auth_types) +{ + int ret; + struct pam_resp_auth_type types = {0}; + + evaluate_pam_resp_list(pd, &types, NULL); + + if (!types.password_auth && !types.otp_auth && !types.cert_auth && !types.passkey_auth) { + /* If the backend cannot determine which authentication types are + * available the default would be to prompt for a password. */ + types.password_auth = true; + types.backend_returned_no_auth_type = true; + } + + DEBUG(SSSDBG_TRACE_ALL, "Authentication types for user [%s] and service " + "[%s]:%s%s%s%s\n", pd->user, pd->service, + types.password_auth ? " password": "", + types.otp_auth ? " two-factor" : "", + types.passkey_auth ? " passkey" : "", + types.cert_auth ? " smartcard" : ""); + + ret = EOK; + + *_auth_types = types; + + return ret; +} + +static errno_t pam_eval_local_auth_policy(TALLOC_CTX *mem_ctx, + struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq, + bool *_sc_allow, + bool *_passkey_allow, + char **_local_policy) { + + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char *domain_cdb; + char *local_policy = NULL; + bool sc_allow = false; + bool passkey_allow = false; + struct pam_resp_auth_type auth_types; + char **opts; + size_t c; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Check local auth policy */ + domain_cdb = talloc_asprintf(tmp_ctx, CONFDB_DOMAIN_PATH_TMPL, preq->domain->name); + if (domain_cdb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = confdb_get_string(pctx->rctx->cdb, tmp_ctx, domain_cdb, + CONFDB_DOMAIN_LOCAL_AUTH_POLICY, + "match", &local_policy); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get the confdb local_auth_policy\n"); + return ret; + } + + /* "only" ignores online methods and allows all local ones */ + if (strcasecmp(local_policy, "only") == 0) { + sc_allow = true; + passkey_allow = true; + /* Match what the KDC supports and provides */ + } else if (strcasecmp(local_policy, "match") == 0) { + /* Don't overwrite the local auth type when offline */ + if (pd->pam_status == PAM_SUCCESS && pd->cmd == SSS_PAM_PREAUTH && + !is_domain_provider(preq->domain, "ldap")) { + ret = pam_get_auth_types(pd, &auth_types); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get authentication types\n"); + goto done; + } + + if (auth_types.cert_auth) { + sc_allow = true; + } else if (auth_types.passkey_auth) { + passkey_allow = true; + } + + /* Store the local auth types, in case we go offline */ + if (!auth_types.backend_returned_no_auth_type) { + ret = set_local_auth_type(preq, sc_allow, passkey_allow); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to evaluate local auth policy\n"); + goto done; + } + } + } + + /* Read the latest auth types */ + ret = pam_get_local_auth_policy(preq->domain, preq->pd->user, + &sc_allow, &passkey_allow); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get PAM local auth policy\n"); + goto done; + } + /* Check for enable */ + } else { + ret = split_on_separator(tmp_ctx, local_policy, ',', true, true, &opts, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d], %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + for (c = 0; opts[c] != NULL; c++) { + if (strcasestr(opts[c], "passkey") != NULL) { + passkey_allow = strstr(opts[c], "enable") ? true : false; + } else if (strcasestr(opts[c], "smartcard") != NULL) { + sc_allow = strstr(opts[c], "enable") ? true : false; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unexpected local auth policy option [%s], " \ + "skipping.\n", opts[c]); + } + } + } + + /* if passkey is enabled but local Smartcard authentication is not but + * possible, the cert info data has to be remove as well if only local + * Smartcard authentication is possible. If Smartcard authentication + * is possible on the server side we have to keep it because the + * 'enable' option should only add local methods but not reject remote + * ones. */ + if (!sc_allow) { + evalute_sending_cert_info(pd); + } + + *_sc_allow = sc_allow; + *_passkey_allow = passkey_allow; + *_local_policy = talloc_steal(mem_ctx, local_policy); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static void pam_reply_delay(struct tevent_context *ev, struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct pam_auth_req *preq; + + DEBUG(SSSDBG_CONF_SETTINGS, "pam_reply_delay get called.\n"); + + preq = talloc_get_type(pvt, struct pam_auth_req); + + pam_reply(preq); +} + +static errno_t get_password_for_cache_auth(struct sss_auth_token *authtok, + const char **password) +{ + int ret; + size_t pw_len; + const char *fa2; + size_t fa2_len; + + switch (sss_authtok_get_type(authtok)) { + case SSS_AUTHTOK_TYPE_PASSWORD: + ret = sss_authtok_get_password(authtok, password, NULL); + break; + case SSS_AUTHTOK_TYPE_2FA: + ret = sss_authtok_get_2fa(authtok, password, &pw_len, &fa2, &fa2_len); + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, "Unsupported auth token type [%d].\n", + sss_authtok_get_type(authtok)); + ret = EINVAL; + } + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get password.\n"); + return ret; + } + + return EOK; +} + +static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd); +static void pam_handle_cached_login(struct pam_auth_req *preq, int ret, + time_t expire_date, time_t delayed_until, bool cached_auth); + +/* + * Add a request to add a variable to the PAM user environment, containing the + * actual (not overridden) user shell, in case session recording is enabled. + */ +static int pam_reply_sr_export_shell(struct pam_auth_req *preq, + const char *var_name) +{ + int ret; + TALLOC_CTX *ctx = NULL; + bool enabled; + const char *enabled_str; + const char *shell; + char *buf; + + /* Create temporary talloc context */ + ctx = talloc_new(NULL); + if (ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + ret = ENOMEM; + goto done; + } + + /* Check if session recording is enabled */ + if (preq->cctx->rctx->sr_conf.scope == + SESSION_RECORDING_SCOPE_NONE) { + enabled = false; + } else { + enabled_str = ldb_msg_find_attr_as_string(preq->user_obj, + SYSDB_SESSION_RECORDING, NULL); + if (enabled_str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "%s attribute not found\n", SYSDB_SESSION_RECORDING); + ret = ENOENT; + goto done; + } else if (strcmp(enabled_str, "TRUE") == 0) { + enabled = true; + } else if (strcmp(enabled_str, "FALSE") == 0) { + enabled = false; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "invalid value of %s attribute: %s\n", + SYSDB_SESSION_RECORDING, enabled_str); + ret = ENOENT; + goto done; + } + } + + /* Export original shell if recording is enabled and so it's overridden */ + if (enabled) { + /* Extract the shell */ + shell = sss_resp_get_shell_override(preq->user_obj, + preq->cctx->rctx, preq->domain); + if (shell == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "user has no shell\n"); + ret = ENOENT; + goto done; + } + + /* Format environment entry */ + buf = talloc_asprintf(ctx, "%s=%s", var_name, shell); + if (buf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + /* Add request to add the entry to user environment */ + ret = pam_add_response(preq->pd, SSS_PAM_ENV_ITEM, + strlen(buf) + 1, (uint8_t *)buf); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(ctx); + return ret; +} + +void pam_reply(struct pam_auth_req *preq) +{ + struct cli_ctx *cctx; + struct cli_protocol *prctx; + uint8_t *body; + size_t blen; + int ret; + int32_t resp_c; + int32_t resp_size; + struct response_data *resp; + int p; + struct timeval tv; + struct tevent_timer *te; + struct pam_data *pd; + char *local_policy = NULL; + struct pam_ctx *pctx; + uint32_t user_info_type; + time_t exp_date = -1; + time_t delay_until = -1; + char* pam_account_expired_message; + char* pam_account_locked_message; + int pam_verbosity; + bool local_sc_auth_allow = false; + bool local_passkey_auth_allow = false; +#ifdef BUILD_PASSKEY + bool pk_preauth_done = false; +#endif /* BUILD_PASSKEY */ + + pd = preq->pd; + cctx = preq->cctx; + pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + prctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_VERBOSITY, DEFAULT_PAM_VERBOSITY, + &pam_verbosity); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read PAM verbosity, not fatal.\n"); + pam_verbosity = DEFAULT_PAM_VERBOSITY; + } + + DEBUG(SSSDBG_TRACE_ALL, + "pam_reply initially called with result [%d]: %s. " + "this result might be changed during processing\n", + pd->pam_status, pam_strerror(NULL, pd->pam_status)); + + if (preq->domain != NULL && preq->domain->name != NULL) { + ret = pam_eval_local_auth_policy(cctx, pctx, pd, preq, + &local_sc_auth_allow, + &local_passkey_auth_allow, + &local_policy); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to evaluate local auth policy\n"); + goto done; + } + } + + /* Ignore local_auth_policy for the files provider, allow local + * smartcard auth (default behavior prior to local_auth_policy) */ + if (is_domain_provider(preq->domain, "files")) { + local_sc_auth_allow = true; + /* For the ldap auth provider we currently only support + * password based authentication */ + } else if (is_domain_provider(preq->domain, "ldap") && local_policy != NULL + && strcasecmp(local_policy, "match") == 0) { + local_passkey_auth_allow = false; + local_sc_auth_allow = false; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Local auth policy allowed: smartcard [%s], passkey [%s]\n", + local_sc_auth_allow ? "True" : "False", + local_passkey_auth_allow ? "True" : "False"); + + if (pd->cmd == SSS_PAM_AUTHENTICATE + && !preq->cert_auth_local + && (pd->pam_status == PAM_AUTHINFO_UNAVAIL + || pd->pam_status == PAM_NO_MODULE_DATA + || pd->pam_status == PAM_BAD_ITEM) + && may_do_cert_auth(pctx, pd)) { + /* We have Smartcard credentials and the backend indicates that it is + * offline (PAM_AUTHINFO_UNAVAIL) or cannot handle the credentials + * (PAM_BAD_ITEM), so let's try authentication against the Smartcard + * PAM_NO_MODULE_DATA is returned by the krb5 backend if no + * authentication method was found at all, this might happen if the + * user has a Smartcard assigned but the pkint plugin is not available + * on the client. */ + DEBUG(SSSDBG_IMPORTANT_INFO, + "Backend cannot handle Smartcard authentication, " + "trying local Smartcard authentication.\n"); + if (local_sc_auth_allow) { + preq->cert_auth_local = true; + ret = check_cert(cctx, cctx->ev, pctx, preq, pd); + pam_check_user_done(preq, ret); + return; + } else { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Local smartcard auth not allowed by local_auth_policy"); + } + } + + if (pd->pam_status == PAM_AUTHINFO_UNAVAIL || preq->use_cached_auth) { + + switch(pd->cmd) { + case SSS_PAM_AUTHENTICATE: + if ((preq->domain != NULL) && + (preq->domain->cache_credentials == true) && + (pd->offline_auth == false)) { + const char *password = NULL; + bool use_cached_auth; + + /* backup value of preq->use_cached_auth*/ + use_cached_auth = preq->use_cached_auth; + /* set to false to avoid entering this branch when pam_reply() + * is recursively called from pam_handle_cached_login() */ + preq->use_cached_auth = false; + + /* do auth with offline credentials */ + pd->offline_auth = true; + + if (preq->domain->sysdb == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Fatal: Sysdb CTX not found for domain" + " [%s]!\n", preq->domain->name); + goto done; + } + + ret = get_password_for_cache_auth(pd->authtok, &password); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "get_password_and_type_for_cache_auth failed.\n"); + goto done; + } + + ret = sysdb_cache_auth(preq->domain, + pd->user, password, + pctx->rctx->cdb, false, + &exp_date, &delay_until); + + pam_handle_cached_login(preq, ret, exp_date, delay_until, + use_cached_auth); + return; + } + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + case SSS_PAM_CHAUTHTOK: + DEBUG(SSSDBG_FUNC_DATA, + "Password change not possible while offline.\n"); + pd->pam_status = PAM_AUTHTOK_ERR; + user_info_type = SSS_PAM_USER_INFO_OFFLINE_CHPASS; + ret = pam_add_response(pd, SSS_PAM_USER_INFO, sizeof(uint32_t), + (const uint8_t *) &user_info_type); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + break; +/* TODO: we need the pam session cookie here to make sure that cached + * authentication was successful */ + case SSS_PAM_PREAUTH: + case SSS_PAM_SETCRED: + case SSS_PAM_ACCT_MGMT: + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_CLOSE_SESSION: + DEBUG(SSSDBG_OP_FAILURE, + "Assuming offline authentication setting status for " + "pam call %d to PAM_SUCCESS.\n", pd->cmd); + pd->pam_status = PAM_SUCCESS; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown PAM call [%d].\n", pd->cmd); + pd->pam_status = PAM_MODULE_UNKNOWN; + } + } + + if (pd->pam_status == PAM_SUCCESS && pd->cmd == SSS_PAM_CHAUTHTOK) { + ret = pam_null_last_online_auth_with_curr_token(preq->domain, + pd->user); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_null_last_online_auth_with_curr_token failed: " + "%s [%d].\n", sss_strerror(ret), ret); + goto done; + } + } + + if (pd->response_delay > 0) { + ret = gettimeofday(&tv, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "gettimeofday failed [%d][%s].\n", + errno, strerror(errno)); + goto done; + } + tv.tv_sec += pd->response_delay; + tv.tv_usec = 0; + pd->response_delay = 0; + + te = tevent_add_timer(cctx->ev, cctx, tv, pam_reply_delay, preq); + if (te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add event pam_reply_delay.\n"); + goto done; + } + + return; + } + + /* If this was a successful login, save the lastLogin time */ + if (pd->cmd == SSS_PAM_AUTHENTICATE && + pd->pam_status == PAM_SUCCESS && + preq->domain && + preq->domain->cache_credentials && + !pd->offline_auth && + !pd->last_auth_saved) { + ret = set_last_login(preq); + if (ret != EOK) { + goto done; + } + return; + } + + ret = sss_packet_new(prctx->creq, 0, sss_packet_get_cmd(prctx->creq->in), + &prctx->creq->out); + if (ret != EOK) { + goto done; + } + +#ifdef BUILD_PASSKEY + if(pd->cmd == SSS_PAM_AUTHENTICATE && + pd->pam_status == PAM_NEW_AUTHTOK_REQD && + sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_REPLY) { + DEBUG(SSSDBG_TRACE_FUNC, "Passkey authentication reply, ignoring " + "new authtok required status\n"); + pd->pam_status = PAM_SUCCESS; + } + + /* Passkey auth user notification if no TGT is granted */ + if (pd->cmd == SSS_PAM_AUTHENTICATE && + pd->pam_status == PAM_SUCCESS && + preq->pd->passkey_local_done) { + user_info_type = SSS_PAM_USER_INFO_NO_KRB_TGT; + pam_add_response(pd, SSS_PAM_USER_INFO, + sizeof(uint32_t), (const uint8_t *) &user_info_type); + DEBUG(SSSDBG_IMPORTANT_INFO, + "User [%s] logged in with local passkey authentication, single " + "sign on ticket is not obtained.\n", pd->user); + } +#endif /* BUILD_PASSKEY */ + + /* Account expiration warning is printed for sshd. If pam_verbosity + * is equal or above PAM_VERBOSITY_INFO then all services are informed + * about account expiration. + */ + if (pd->pam_status == PAM_ACCT_EXPIRED && + ((pd->service != NULL && strcasecmp(pd->service, "sshd") == 0) || + pam_verbosity >= PAM_VERBOSITY_INFO)) { + + ret = confdb_get_string(pctx->rctx->cdb, pd, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_ACCOUNT_EXPIRED_MESSAGE, "", + &pam_account_expired_message); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to get expiration message: %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + inform_user(pd, pam_account_expired_message); + } + + if (pd->account_locked) { + + ret = confdb_get_string(pctx->rctx->cdb, pd, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_ACCOUNT_LOCKED_MESSAGE, "", + &pam_account_locked_message); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to get expiration message: %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + inform_user(pd, pam_account_locked_message); + } + + ret = filter_responses(pctx, pd->resp_list, pd); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "filter_responses failed, not fatal.\n"); + } + + if (pd->domain != NULL) { + ret = pam_add_response(pd, SSS_PAM_DOMAIN_NAME, strlen(pd->domain)+1, + (uint8_t *) pd->domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + } + + 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"); + } + +#ifdef BUILD_PASSKEY + ret = pam_eval_passkey_response(pctx, pd, preq, &pk_preauth_done); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to eval passkey response\n"); + goto done; + } + + if (may_do_passkey_auth(pctx, pd) + && !pk_preauth_done + && preq->passkey_data_exists + && local_passkey_auth_allow) { + ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); + pam_check_user_done(preq, ret); + return; + } +#endif /* BUILD_PASSKEY */ + } + + /* + * Export non-overridden shell to tlog-rec-session when opening the session + */ + if (pd->cmd == SSS_PAM_OPEN_SESSION && pd->pam_status == PAM_SUCCESS) { + ret = pam_reply_sr_export_shell(preq, "TLOG_REC_SESSION_SHELL"); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "failed to export the shell to tlog-rec-session.\n"); + goto done; + } + } + + resp_c = 0; + resp_size = 0; + resp = pd->resp_list; + while(resp != NULL) { + if (!resp->do_not_send_to_client) { + resp_c++; + resp_size += resp->len; + } + resp = resp->next; + } + + ret = sss_packet_grow(prctx->creq->out, sizeof(int32_t) + + sizeof(int32_t) + + resp_c * 2* sizeof(int32_t) + + resp_size); + if (ret != EOK) { + goto done; + } + + sss_packet_get_body(prctx->creq->out, &body, &blen); + DEBUG(SSSDBG_FUNC_DATA, "blen: %zu\n", blen); + p = 0; + + memcpy(&body[p], &pd->pam_status, sizeof(int32_t)); + p += sizeof(int32_t); + + memcpy(&body[p], &resp_c, sizeof(int32_t)); + p += sizeof(int32_t); + + resp = pd->resp_list; + while(resp != NULL) { + if (!resp->do_not_send_to_client) { + memcpy(&body[p], &resp->type, sizeof(int32_t)); + p += sizeof(int32_t); + memcpy(&body[p], &resp->len, sizeof(int32_t)); + p += sizeof(int32_t); + memcpy(&body[p], resp->data, resp->len); + p += resp->len; + } + + resp = resp->next; + } + +done: + DEBUG(SSSDBG_FUNC_DATA, "Returning [%d]: %s to the client\n", + pd->pam_status, pam_strerror(NULL, pd->pam_status)); + sss_cmd_done(cctx, preq); +} + +static void pam_dom_forwarder(struct pam_auth_req *preq); + +static void pam_handle_cached_login(struct pam_auth_req *preq, int ret, + time_t expire_date, time_t delayed_until, + bool use_cached_auth) +{ + uint32_t resp_type; + size_t resp_len; + uint8_t *resp; + int64_t dummy; + + preq->pd->pam_status = cached_login_pam_status(ret); + + switch (preq->pd->pam_status) { + case PAM_SUCCESS: + resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH; + resp_len = sizeof(uint32_t) + sizeof(int64_t); + resp = talloc_size(preq->pd, resp_len); + if (resp == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_size failed, cannot prepare user info.\n"); + } else { + memcpy(resp, &resp_type, sizeof(uint32_t)); + dummy = (int64_t) expire_date; + memcpy(resp+sizeof(uint32_t), &dummy, sizeof(int64_t)); + ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, + (const uint8_t *) resp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + } + break; + case PAM_PERM_DENIED: + if (delayed_until >= 0) { + resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED; + resp_len = sizeof(uint32_t) + sizeof(int64_t); + resp = talloc_size(preq->pd, resp_len); + if (resp == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_size failed, cannot prepare user info.\n"); + } else { + memcpy(resp, &resp_type, sizeof(uint32_t)); + dummy = (int64_t) delayed_until; + memcpy(resp+sizeof(uint32_t), &dummy, sizeof(int64_t)); + ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, + (const uint8_t *) resp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "pam_add_response failed.\n"); + } + } + } + break; + case PAM_AUTH_ERR: + /* Was this attempt to authenticate from cache? */ + if (use_cached_auth) { + /* Don't try cached authentication again, try online check. */ + DEBUG(SSSDBG_FUNC_DATA, + "Cached authentication failed for: %s\n", + preq->pd->user); + preq->cached_auth_failed = true; + pam_dom_forwarder(preq); + return; + } + break; + default: + DEBUG(SSSDBG_TRACE_LIBS, + "cached login returned: %d\n", preq->pd->pam_status); + } + + pam_reply(preq); + return; +} + +static void pam_forwarder_cb(struct tevent_req *req); +static void pam_forwarder_cert_cb(struct tevent_req *req); +int pam_check_user_search(struct pam_auth_req *preq); + + +/* TODO: we should probably return some sort of cookie that is set in the + * PAM_ENVIRONMENT, so that we can save performing some calls and cache + * data. */ + +static errno_t pam_forwarder_parse_data(struct cli_ctx *cctx, struct pam_data *pd) +{ + struct cli_protocol *prctx; + uint8_t *body; + size_t blen; + errno_t ret; + uint32_t terminator; + const char *key_id; + + prctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + + sss_packet_get_body(prctx->creq->in, &body, &blen); + if (blen >= sizeof(uint32_t)) { + SAFEALIGN_COPY_UINT32(&terminator, + body + blen - sizeof(uint32_t), + NULL); + if (terminator != SSS_END_OF_PAM_REQUEST) { + DEBUG(SSSDBG_CRIT_FAILURE, "Received data not terminated.\n"); + ret = EINVAL; + goto done; + } + } + + switch (prctx->cli_protocol_version->version) { + case 1: + ret = pam_parse_in_data(pd, body, blen); + break; + case 2: + ret = pam_parse_in_data_v2(pd, body, blen); + break; + case 3: + ret = pam_parse_in_data_v3(pd, body, blen); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Illegal protocol version [%d].\n", + prctx->cli_protocol_version->version); + ret = EINVAL; + } + if (ret != EOK) { + goto done; + } + + if (pd->logon_name != NULL) { + ret = sss_parse_name_for_domains(pd, cctx->rctx->domains, + cctx->rctx->default_domain, + pd->logon_name, + &pd->domain, &pd->user); + } else { + /* SSS_PAM_PREAUTH request may have a missing name, e.g. if the + * name is determined with the help of a certificate. During + * SSS_PAM_AUTHENTICATE at least a key ID is needed to identify the + * selected certificate. */ + if (pd->cmd == SSS_PAM_AUTHENTICATE + && may_do_cert_auth(talloc_get_type(cctx->rctx->pvt_ctx, + struct pam_ctx), pd) + && (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_SC_PIN + || sss_authtok_get_type(pd->authtok) + == SSS_AUTHTOK_TYPE_SC_KEYPAD)) { + ret = sss_authtok_get_sc(pd->authtok, NULL, NULL, NULL, NULL, NULL, + NULL, &key_id, NULL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_sc failed.\n"); + goto done; + } + + if (key_id == NULL || *key_id == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing logon and Smartcard key ID during " + "authentication.\n"); + ret = ERR_NO_CREDS; + goto done; + } + + ret = EOK; + } else if (pd->cmd == SSS_PAM_PREAUTH + && may_do_cert_auth(talloc_get_type(cctx->rctx->pvt_ctx, + struct pam_ctx), pd)) { + ret = EOK; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing logon name in PAM request.\n"); + ret = ERR_NO_CREDS; + goto done; + } + } + + DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); + +done: + return ret; +} + +static bool is_uid_trusted(struct cli_creds *creds, + size_t trusted_uids_count, + uid_t *trusted_uids) +{ + errno_t ret; + + /* root is always trusted */ + if (client_euid(creds) == 0) { + return true; + } + + /* All uids are allowed */ + if (trusted_uids_count == 0) { + return true; + } + + ret = check_allowed_uids(client_euid(creds), trusted_uids_count, trusted_uids); + if (ret == EOK) return true; + + return false; +} + +static bool is_domain_public(char *name, + char **public_dom_names, + size_t public_dom_names_count) +{ + size_t i; + + for(i=0; i < public_dom_names_count; i++) { + if (strcasecmp(name, public_dom_names[i]) == 0) { + return true; + } + } + return false; +} + +static enum cache_req_dom_type +get_domain_request_type(struct pam_auth_req *preq, + struct pam_ctx *pctx) +{ + enum cache_req_dom_type req_dom_type; + + /* By default, only POSIX domains are to be contacted */ + req_dom_type = CACHE_REQ_POSIX_DOM; + + for (int i = 0; pctx->app_services[i]; i++) { + if (strcmp(pctx->app_services[i], preq->pd->service) == 0) { + req_dom_type = CACHE_REQ_APPLICATION_DOM; + break; + } + } + + return req_dom_type; +} + +static errno_t check_cert(TALLOC_CTX *mctx, + struct tevent_context *ev, + struct pam_ctx *pctx, + struct pam_auth_req *preq, + struct pam_data *pd) +{ + int p11_child_timeout; + int wait_for_card_timeout; + char *cert_verification_opts; + errno_t ret; + struct tevent_req *req; + char *uri = NULL; + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_P11_CHILD_TIMEOUT, + P11_CHILD_TIMEOUT_DEFAULT, + &p11_child_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read p11_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + if ((pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) && pd->priv == 1) { + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_WAIT_FOR_CARD_TIMEOUT, + P11_WAIT_FOR_CARD_TIMEOUT_DEFAULT, + &wait_for_card_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read [%s] from confdb: [%d]: %s\n", + CONFDB_PAM_WAIT_FOR_CARD_TIMEOUT, ret, sss_strerror(ret)); + return ret; + } + + p11_child_timeout += wait_for_card_timeout; + } + + ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_CERT_VERIFICATION, + NULL, &cert_verification_opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read '"CONFDB_PAM_CERT_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (cert_verification_opts == NULL) { + ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_CERT_VERIFICATION, NULL, + &cert_verification_opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read '"CONFDB_MONITOR_CERT_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + } + + ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_P11_URI, NULL, &uri); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read '"CONFDB_PAM_P11_URI"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + req = pam_check_cert_send(mctx, ev, + pctx->ca_db, p11_child_timeout, + cert_verification_opts, pctx->sss_certmap_ctx, + uri, pd); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_check_cert_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(req, pam_forwarder_cert_cb, preq); + return EAGAIN; +} + + +static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd) +{ + struct pam_auth_req *preq; + struct pam_data *pd; + int ret; + struct pam_ctx *pctx = + talloc_get_type(cctx->rctx->pvt_ctx, struct pam_ctx); + struct tevent_req *req; + + preq = talloc_zero(cctx, struct pam_auth_req); + if (!preq) { + return ENOMEM; + } + preq->cctx = cctx; + preq->cert_auth_local = false; + preq->client_id_num = cctx->client_id_num; + + preq->pd = create_pam_data(preq); + if (!preq->pd) { + talloc_free(preq); + return ENOMEM; + } + pd = preq->pd; + + preq->is_uid_trusted = is_uid_trusted(cctx->creds, + pctx->trusted_uids_count, + pctx->trusted_uids); + + if (!preq->is_uid_trusted) { + DEBUG(SSSDBG_MINOR_FAILURE, "uid %"SPRIuid" is not trusted.\n", + client_euid(cctx->creds)); + } + + + pd->cmd = pam_cmd; + pd->priv = cctx->priv; + pd->client_id_num = cctx->client_id_num; + + ret = pam_forwarder_parse_data(cctx, pd); + if (ret == EAGAIN) { + req = sss_dp_get_domains_send(cctx, cctx->rctx, true, pd->domain); + if (req == NULL) { + ret = ENOMEM; + } else { + tevent_req_set_callback(req, pam_forwarder_cb, preq); + ret = EAGAIN; + } + goto done; + } else if (ret != EOK) { + goto done; + } + + /* Determine what domain type to contact */ + preq->req_dom_type = get_domain_request_type(preq, pctx); + + if (pd->cmd == SSS_PAM_AUTHENTICATE + && (pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) + && !IS_SC_AUTHTOK(pd->authtok)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Smartcard authentication required but authentication " + "token [%d][%s] is not suitable.\n", + sss_authtok_get_type(pd->authtok), + sss_authtok_type_to_str(sss_authtok_get_type(pd->authtok))); + ret = ERR_NO_CREDS; + goto done; + } + + /* Try backend first for authentication before doing local Smartcard + * authentication if a logon name is available. Otherwise try to derive + * the logon name from the certificate first. */ + if ((pd->cmd != SSS_PAM_AUTHENTICATE + || (pd->cmd == SSS_PAM_AUTHENTICATE && pd->logon_name == NULL)) + && may_do_cert_auth(pctx, pd)) { + ret = check_cert(cctx, cctx->ev, pctx, preq, pd); + /* Finish here */ + goto done; + } + + /* This is set to false inside passkey_local() if no passkey data is found. + * It is checked in pam_reply() to avoid an endless loop */ + preq->passkey_data_exists = true; + +#ifdef BUILD_PASSKEY + if ((pd->cmd == SSS_PAM_AUTHENTICATE)) { + if (may_do_passkey_auth(pctx, pd)) { + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + ret = passkey_kerberos(pctx, preq->pd, preq); + goto done; + } else if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY) { + /* TYPE_EMPTY is not a valid */ + ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); + goto done; + } + } + } + + if (pd->cmd == SSS_PAM_PASSKEY_PREAUTH) { + +#define PASSKEY_DOPIN "/var/run/passkey-dopin" +#define PASSKEY_PINUV "/var/run/passkey-pinuv" +#define PASSKEY_PINONLY "/var/run/passkey-pinonly" +#define PASSKEY_NODEV "/var/run/passkey-nodev" + + DEBUG(SSSDBG_TRACE_FUNC, "required passkey device information .\n"); + if (may_do_passkey_auth(pctx, pd)) { + const char *devinfo = "nocacheddevinfo"; + + if (pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_PASSKEY_CACHED_DEVINFO) { + struct stat st_nodev; + int e; + DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] requests cached devinfo .\n", getpid()); + + e = stat (PASSKEY_NODEV, &st_nodev); + if (e == 0) { + /* indicator exists */ + if (time(NULL) < st_nodev.st_ctime + 100 /*seconds : configurable ??? */) { + /* the info is still valid */ + devinfo = "nodev"; + } else { + /* but the info is obsolete, we shall retest device existence */ + devinfo = "nodevobsolete"; + (void)remove (PASSKEY_NODEV); + } + } else { + DEBUG(SSSDBG_TRACE_FUNC, "stat nodev: errno=%d\n", errno); + if (access(PASSKEY_DOPIN, F_OK) == 0) { + /* the device requires a PIN to perform next authentication; */ + devinfo = "dopin"; + } else if (access(PASSKEY_PINONLY, F_OK) == 0) { + /* the device ALWAYS requires a PIN to perform authentication; */ + devinfo = "pinonly"; + } else if (access(PASSKEY_PINUV, F_OK) == 0) { + /* the cached device supports UV and does not fallback to PIN using + * PASSKEY_DOPIN indicator. + */ + devinfo = "pinuv"; + } + } + /* remove cache indicators. + * They will be rebuild according the device capabilities + * (see passkey_child) + * during authentication or fresh devinfo request. + * do not remove DOPIN that is updated only during authentication + */ + (void)remove (PASSKEY_PINUV); + (void)remove (PASSKEY_PINONLY); + + } else { + /* remove cache indicators. + * They will be rebuild according the device capabilities + * (see passkey_child) + * during this devinfo request + * do not remove DOPIN that is updated only during authentication + */ + (void)remove (PASSKEY_PINUV); + (void)remove (PASSKEY_PINONLY); + + DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] requests fresh devinfo .\n", getpid()); + /* + * ask the device capabilities + * This assumes the device is connected. + */ + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] calls passkey_kerberos_get_devinfo() \n", getpid()); + ret = passkey_kerberos_get_devinfo(pctx, preq->pd, preq); + DEBUG(SSSDBG_TRACE_FUNC, "passkey_kerberos_get_devinfo() returns %d\n", ret); + goto done; + } else if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_EMPTY) { + + DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] calls passkey_local_get_devinfo() .\n", getpid()); + ret = passkey_local_get_devinfo(cctx, cctx->ev, pctx, preq, pd); + DEBUG(SSSDBG_TRACE_FUNC, "passkey_local_get_devinfo() returns %d\n", ret); + goto done; + } else { + /* note that AUTHTOK_TYPE_PASSKEY is not a valid authtok + * see get_device_info in pam_sss.c + */ + DEBUG(SSSDBG_TRACE_FUNC, "pid [%d] requests fresh with invalid authtok type: [%d]\n", + getpid(),sss_authtok_get_type(pd->authtok) ); + ret = PAM_AUTHTOK_ERR; + goto done; + } + } + + DEBUG(SSSDBG_TRACE_FUNC, "cached devinfo request replies: [%s}\n", devinfo); + + ret = pam_add_response(pd, SSS_PAM_PASSKEY_DEVINFO, strlen(devinfo) + 1, + (const uint8_t *) devinfo); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed. [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + pd->pam_status = PAM_SUCCESS; + pam_reply(preq); + + /* finish */ + return EOK; + } + DEBUG(SSSDBG_TRACE_FUNC, "passkey_get_devinfo requires OK from may_do_passkey_auth .\n"); + } +#endif /* BUILD_PASSKEY */ + + ret = pam_check_user_search(preq); + +done: + return pam_check_user_done(preq, ret); +} + +static errno_t pam_user_by_cert_step(struct pam_auth_req *preq); +static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req); +static void pam_forwarder_cert_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + struct pam_data *pd; + errno_t ret = EOK; + const char *cert; + + ret = pam_check_cert_recv(req, preq, &preq->cert_list); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_cert request failed.\n"); + goto done; + } + + pd = preq->pd; + + cert = sss_cai_get_cert(preq->cert_list); + + if (cert == NULL) { + if (pd->logon_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No certificate found and no logon name given, " \ + "authentication not possible.\n"); + ret = ENOENT; + } else if (pd->cmd == SSS_PAM_PREAUTH + && (pd->cli_flags & PAM_CLI_FLAGS_TRY_CERT_AUTH)) { + DEBUG(SSSDBG_TRACE_ALL, + "try_cert_auth flag set but no certificate available, " + "request finished.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + pam_reply(preq); + return; + } else { + if (pd->cmd == SSS_PAM_AUTHENTICATE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No certificate returned, authentication failed.\n"); + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + return; + } else { + ret = pam_check_user_search(preq); + } + + } + goto done; + } + + preq->current_cert = preq->cert_list; + ret = pam_user_by_cert_step(preq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n"); + goto done; + } + + return; + +done: + pam_check_user_done(preq, ret); +} + +static errno_t pam_user_by_cert_step(struct pam_auth_req *preq) +{ + struct cli_ctx *cctx = preq->cctx; + struct tevent_req *req; + struct pam_ctx *pctx = + talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + if (preq->current_cert == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate data.\n"); + return EINVAL; + } + + req = cache_req_user_by_cert_send(preq, cctx->ev, cctx->rctx, + pctx->rctx->ncache, 0, + preq->req_dom_type, NULL, + sss_cai_get_cert(preq->current_cert)); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(req, pam_forwarder_lookup_by_cert_done, preq); + return EOK; +} + +static errno_t get_results_from_all_domains(TALLOC_CTX *mem_ctx, + struct cache_req_result **results, + struct ldb_result **ldb_results) +{ + int ret; + size_t count = 0; + size_t c; + size_t d; + size_t r = 0; + struct ldb_result *res; + + for (d = 0; results != NULL && results[d] != NULL; d++) { + count += results[d]->count; + } + + res = talloc_zero(mem_ctx, struct ldb_result); + if (res == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + + if (count == 0) { + *ldb_results = res; + return EOK; + } + + res->msgs = talloc_zero_array(res, struct ldb_message *, count); + if (res->msgs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + return ENOMEM; + } + res->count = count; + + for (d = 0; results != NULL && results[d] != NULL; d++) { + for (c = 0; c < results[d]->count; c++) { + if (r >= count) { + DEBUG(SSSDBG_CRIT_FAILURE, + "More results found then counted before.\n"); + ret = EINVAL; + goto done; + } + res->msgs[r++] = talloc_steal(res->msgs, results[d]->msgs[c]); + } + } + + *ldb_results = res; + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(res); + } + + return ret; +} + +/* Return true if hint is set for at least one domain */ +static bool get_user_name_hint(struct sss_domain_info *domains) +{ + struct sss_domain_info *d; + + DLIST_FOR_EACH(d, domains) { + if (d->user_name_hint == true) { + return true; + } + } + + return false; +} + +static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req) +{ + int ret; + struct cache_req_result **results; + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + const char *cert_user = NULL; + size_t cert_count = 0; + size_t cert_user_count = 0; + struct ldb_result *cert_user_objs; + + ret = cache_req_recv(preq, req, &results); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert request failed.\n"); + goto done; + } + + if (ret == EOK) { + ret = get_results_from_all_domains(preq, results, + &cert_user_objs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_results_from_all_domains failed.\n"); + goto done; + } + + sss_cai_set_cert_user_objs(preq->current_cert, cert_user_objs); + } + + preq->current_cert = sss_cai_get_next(preq->current_cert); + if (preq->current_cert != NULL) { + ret = pam_user_by_cert_step(preq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n"); + goto done; + } + return; + } + + sss_cai_check_users(&preq->cert_list, &cert_count, &cert_user_count); + DEBUG(SSSDBG_TRACE_ALL, + "Found [%zu] certificates and [%zu] related users.\n", + cert_count, cert_user_count); + + if (cert_user_count == 0) { + if (preq->pd->logon_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing logon name and no certificate user found.\n"); + ret = ENOENT; + goto done; + } + } else { + + if (preq->pd->logon_name == NULL) { + if (preq->pd->cmd != SSS_PAM_PREAUTH + && preq->pd->cmd != SSS_PAM_AUTHENTICATE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing logon name only allowed during (pre-)auth.\n"); + ret = ENOENT; + goto done; + } + /* Multiple certificates are only expected during pre-auth */ + if (cert_count > 1 && preq->pd->cmd == SSS_PAM_PREAUTH) { + for (preq->current_cert = preq->cert_list; + preq->current_cert != NULL; + preq->current_cert = sss_cai_get_next(preq->current_cert)) { + + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, "", + preq->current_cert, + get_user_name_hint(preq->cctx->rctx->domains) + ? SSS_PAM_CERT_INFO_WITH_HINT + : SSS_PAM_CERT_INFO); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + } + + ret = EOK; + preq->pd->pam_status = PAM_SUCCESS; + pam_reply(preq); + goto done; + } + + if (cert_user_count == 1) { + cert_user_objs = sss_cai_get_cert_user_objs(preq->cert_list); + if (cert_user_objs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate user.\n"); + ret = ENOENT; + goto done; + } + + cert_user = ldb_msg_find_attr_as_string( + cert_user_objs->msgs[0], + SYSDB_NAME, NULL); + if (cert_user == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Certificate user object has not name.\n"); + ret = ENOENT; + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, + "Found certificate user [%s].\n", cert_user); + + ret = sss_parse_name_for_domains(preq->pd, + preq->cctx->rctx->domains, + preq->cctx->rctx->default_domain, + cert_user, + &preq->pd->domain, + &preq->pd->user); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_parse_name_for_domains failed.\n"); + goto done; + } + } + + if (get_user_name_hint(preq->cctx->rctx->domains) + && preq->pd->cmd == SSS_PAM_PREAUTH) { + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, cert_user, + preq->cert_list, + SSS_PAM_CERT_INFO_WITH_HINT); + preq->pd->pam_status = PAM_SUCCESS; + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + ret = EOK; + pam_reply(preq); + goto done; + } + + /* Without user name hints the certificate must map to single user + * if no login name was given */ + if (cert_user == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "More than one user mapped to certificate.\n"); + ret = ERR_NO_CREDS; + goto done; + } + + /* If logon_name was not given during authentication add a + * SSS_PAM_CERT_INFO message to send the name to the caller. + * Additionally initial_cert_auth_successful is set to + * indicate that the user is already authenticated. */ + if (preq->pd->cmd == SSS_PAM_AUTHENTICATE + && preq->pd->logon_name == NULL) { + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, cert_user, + preq->cert_list, + SSS_PAM_CERT_INFO); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + goto done; + } + + preq->initial_cert_auth_successful = true; + } + + /* cert_user will be returned to the PAM client as user name, so + * we can use it here already e.g. to set in initgroups timeout */ + preq->pd->logon_name = talloc_strdup(preq->pd, cert_user); + if (preq->pd->logon_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } + } + + if (preq->user_obj == NULL) { + ret = pam_check_user_search(preq); + } else { + ret = EOK; + } + + if (ret == EOK) { + pam_dom_forwarder(preq); + } + +done: + pam_check_user_done(preq, ret); +} + +static void pam_forwarder_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + struct cli_ctx *cctx = preq->cctx; + struct pam_data *pd; + errno_t ret = EOK; + struct pam_ctx *pctx = + talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + ret = sss_dp_get_domains_recv(req); + talloc_free(req); + if (ret != EOK) { + goto done; + } + + ret = p11_refresh_certmap_ctx(pctx, pctx->rctx->domains); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "p11_refresh_certmap_ctx failed, " + "certificate matching might not work as expected"); + } + + pd = preq->pd; + + ret = pam_forwarder_parse_data(cctx, pd); + if (ret == EAGAIN) { + DEBUG(SSSDBG_TRACE_FUNC, "Assuming %s is a UPN\n", pd->logon_name); + /* If not, cache_req will error out later */ + pd->user = talloc_strdup(pd, pd->logon_name); + if (pd->user == NULL) { + ret = ENOMEM; + goto done; + } + pd->domain = NULL; + } else if (ret != EOK) { + ret = EINVAL; + goto done; + } + + /* try backend first for authentication before doing local Smartcard + * authentication */ + if (pd->cmd != SSS_PAM_AUTHENTICATE && may_do_cert_auth(pctx, pd)) { + ret = check_cert(cctx, cctx->ev, pctx, preq, pd); + /* Finish here */ + goto done; + } + +#ifdef BUILD_PASSKEY + /* This is set to false inside passkey_local() if no passkey data is found. + * It is checked in pam_reply() to avoid an endless loop */ + preq->passkey_data_exists = true; + + if ((pd->cmd == SSS_PAM_AUTHENTICATE)) { + if (may_do_passkey_auth(pctx, pd)) { + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + ret = passkey_kerberos(pctx, preq->pd, preq); + goto done; + } else if ((sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY) || + (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_EMPTY)) { + ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); + goto done; + } + } + } +#endif /* BUILD_PASSKEY */ + + ret = pam_check_user_search(preq); + +done: + pam_check_user_done(preq, ret); +} + +static void pam_check_user_search_next(struct tevent_req *req); +static void pam_check_user_search_lookup(struct tevent_req *req); +static void pam_check_user_search_done(struct pam_auth_req *preq, int ret, + struct cache_req_result *result); + +/* lookup the user uid from the cache first, + * then we'll refresh initgroups if needed */ +int pam_check_user_search(struct pam_auth_req *preq) +{ + struct tevent_req *dpreq; + struct cache_req_data *data; + + data = cache_req_data_name(preq, + CACHE_REQ_INITGROUPS, + preq->pd->logon_name); + if (data == NULL) { + return ENOMEM; + } + + cache_req_data_set_bypass_cache(data, false); + cache_req_data_set_bypass_dp(data, true); + cache_req_data_set_requested_domains(data, preq->pd->requested_domains); + + dpreq = cache_req_send(preq, + preq->cctx->rctx->ev, + preq->cctx->rctx, + preq->cctx->rctx->ncache, + 0, + preq->req_dom_type, + NULL, + data); + if (!dpreq) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Out of memory sending data provider request\n"); + return ENOMEM; + } + + tevent_req_set_callback(dpreq, pam_check_user_search_next, preq); + + /* tell caller we are in an async call */ + return EAGAIN; +} + +static void pam_check_user_search_next(struct tevent_req *req) +{ + struct pam_auth_req *preq; + struct pam_ctx *pctx; + struct cache_req_result *result = NULL; + struct cache_req_data *data; + struct tevent_req *dpreq; + int ret; + + preq = tevent_req_callback_data(req, struct pam_auth_req); + pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + ret = cache_req_single_domain_recv(preq, req, &result); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Cache lookup failed, trying to get fresh " + "data from the backend.\n"); + } + + DEBUG(SSSDBG_TRACE_ALL, "PAM initgroups scheme [%s].\n", + pam_initgroup_enum_to_string(pctx->initgroups_scheme)); + + if (ret == EOK) { + bool user_has_session = false; + + if (pctx->initgroups_scheme == PAM_INITGR_NO_SESSION) { + uid_t uid = ldb_msg_find_attr_as_uint64(result->msgs[0], + SYSDB_UIDNUM, 0); + if (!uid) { + DEBUG(SSSDBG_CRIT_FAILURE, "A user with no UID?\n"); + talloc_zfree(preq->cctx); + return; + } + + /* If a user already has a session on the system, we take the + * cache for granted and do not force an online lookup. This is + * because in most cases the user is just trying to authenticate + * but not create a new session (sudo, lockscreen, polkit, etc.) + * An online refresh in this situation would just delay operations + * without providing any useful additional information. + */ + (void)check_if_uid_is_active(uid, &user_has_session); + + DEBUG(SSSDBG_TRACE_ALL, "Found %s session for uid %"SPRIuid".\n", + user_has_session ? "a" : "no", uid); + } + + /* The initgr cache is used to make sure that during a single PAM + * session (auth, acct_mgtm, ....) the backend is contacted only + * once. logon_name is the name provided by the PAM client and + * will not be modified during the request, so it makes sense to + * use it here instead od the pd->user. + */ + ret = pam_initgr_check_timeout(pctx->id_table, preq->pd->logon_name); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Could not look up initgroup timeout\n"); + } + + if ((ret == EOK) || user_has_session + || pctx->initgroups_scheme == PAM_INITGR_NEVER) { + DEBUG(SSSDBG_TRACE_ALL, "No new initgroups needed because:\n"); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_ALL, "PAM initgr cache still valid.\n"); + } else if (user_has_session) { + DEBUG(SSSDBG_TRACE_ALL, "there is a active session for " + "user [%s].\n", preq->pd->logon_name); + } else if (pctx->initgroups_scheme == PAM_INITGR_NEVER) { + DEBUG(SSSDBG_TRACE_ALL, "initgroups scheme is 'never'.\n"); + } + pam_check_user_search_done(preq, EOK, result); + return; + } + } + + /* If we get here it means the user was not found or does not have a + * session, or initgr has not been cached before, so we force a new + * online lookup */ + data = cache_req_data_name(preq, + CACHE_REQ_INITGROUPS, + preq->pd->logon_name); + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory\n"); + talloc_zfree(preq->cctx); + return; + } + cache_req_data_set_bypass_cache(data, true); + cache_req_data_set_bypass_dp(data, false); + cache_req_data_set_requested_domains(data, preq->pd->requested_domains); + + dpreq = cache_req_send(preq, + preq->cctx->rctx->ev, + preq->cctx->rctx, + preq->cctx->rctx->ncache, + 0, + preq->req_dom_type, + NULL, + data); + if (!dpreq) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Out of memory sending data provider request\n"); + talloc_zfree(preq->cctx); + return; + } + + tevent_req_set_callback(dpreq, pam_check_user_search_lookup, preq); +} + +static void pam_check_user_search_lookup(struct tevent_req *req) +{ + struct cache_req_result *result; + struct pam_auth_req *preq; + int ret; + + preq = tevent_req_callback_data(req, struct pam_auth_req); + + ret = cache_req_single_domain_recv(preq, req, &result); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Fatal error, killing connection!\n"); + talloc_zfree(preq->cctx); + return; + } + + pam_check_user_search_done(preq, ret, result); +} + +static void pam_check_user_search_done(struct pam_auth_req *preq, int ret, + struct cache_req_result *result) +{ + struct pam_ctx *pctx; + + pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + if (ret == EOK) { + preq->user_obj = result->msgs[0]; + pd_set_primary_name(preq->user_obj, preq->pd); + preq->domain = result->domain; + + ret = pam_initgr_cache_set(pctx->rctx->ev, + pctx->id_table, + preq->pd->logon_name, + pctx->id_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not save initgr timestamp." + "Proceeding with PAM actions\n"); + } + + pam_dom_forwarder(preq); + } + + ret = pam_check_user_done(preq, ret); + if (ret != EOK) { + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } +} + +int pam_check_user_done(struct pam_auth_req *preq, int ret) +{ + switch (ret) { + case EOK: + break; + + case EAGAIN: + /* performing async request, just return */ + break; + + case ENOENT: + preq->pd->pam_status = PAM_USER_UNKNOWN; + pam_reply(preq); + break; + + case ERR_P11_PIN_LOCKED: + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + break; + + case ERR_NO_CREDS: + preq->pd->pam_status = PAM_CRED_INSUFFICIENT; + pam_reply(preq); + break; + + default: + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + break; + } + + return EOK; +} + +static errno_t pam_is_last_online_login_fresh(struct sss_domain_info *domain, + const char* user, + int cached_auth_timeout, + bool *_result) +{ + errno_t ret; + bool result = true; + uint64_t last_login; + + ret = pam_get_last_online_auth_with_curr_token(domain, user, &last_login); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sysdb_get_last_online_auth_with_curr_token failed: %s:[%d]\n", + sss_strerror(ret), ret); + goto done; + } + + result = time(NULL) < (last_login + cached_auth_timeout); + ret = EOK; + +done: + if (ret == EOK) { + *_result = result; + } + return ret; +} + +static bool pam_is_authtok_cachable(struct sss_auth_token *authtok) +{ + enum sss_authtok_type type; + bool cachable = false; + + type = sss_authtok_get_type(authtok); + if (type == SSS_AUTHTOK_TYPE_PASSWORD) { + cachable = true; + } else { + DEBUG(SSSDBG_TRACE_LIBS, "Authentication token can't be cached\n"); + } + + return cachable; +} + +static bool pam_can_user_cache_auth(struct sss_domain_info *domain, + int pam_cmd, + struct sss_auth_token *authtok, + const char* user, + bool cached_auth_failed) +{ + errno_t ret; + bool result = false; + + if (cached_auth_failed) { + /* Do not retry indefinitely */ + return false; + } + + if (!domain->cache_credentials || domain->cached_auth_timeout <= 0) { + return false; + } + + if (pam_cmd == SSS_PAM_PREAUTH + || (pam_cmd == SSS_PAM_AUTHENTICATE + && pam_is_authtok_cachable(authtok))) { + + ret = pam_is_last_online_login_fresh(domain, user, + domain->cached_auth_timeout, + &result); + if (ret != EOK) { + /* non-critical, consider fail as 'non-fresh value' */ + DEBUG(SSSDBG_MINOR_FAILURE, + "pam_is_last_online_login_fresh failed: %s:[%d]\n", + sss_strerror(ret), ret); + } + } + + return result; +} + +static void pam_dom_forwarder(struct pam_auth_req *preq) +{ + TALLOC_CTX *tmp_ctx = NULL; + int ret; + struct pam_ctx *pctx = + talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + const char *cert_user; + struct ldb_result *cert_user_objs; + bool sc_auth; + bool passkey_auth; + size_t c; + char *local_policy = NULL; + bool found = false; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return; + } + + if (!preq->pd->domain) { + preq->pd->domain = preq->domain->name; + } + + /* Untrusted users can access only public domains. */ + if (!preq->is_uid_trusted && + !is_domain_public(preq->pd->domain, pctx->public_domains, + pctx->public_domains_count)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Untrusted user %"SPRIuid" cannot access non-public domain %s.\n", + client_euid(preq->cctx->creds), preq->pd->domain); + preq->pd->pam_status = PAM_PERM_DENIED; + pam_reply(preq); + return; + } + + /* skip this domain if not requested and the user is trusted + * as untrusted users can't request a domain */ + if (preq->is_uid_trusted && + !is_domain_requested(preq->pd, preq->pd->domain)) { + preq->pd->pam_status = PAM_USER_UNKNOWN; + pam_reply(preq); + return; + } + + if (pam_can_user_cache_auth(preq->domain, + preq->pd->cmd, + preq->pd->authtok, + preq->pd->user, + preq->cached_auth_failed)) { + preq->use_cached_auth = true; + pam_reply(preq); + return; + } + + /* Skip online auth when local auth policy = only */ +#ifdef BUILD_PASSKEY + if (may_do_cert_auth(pctx, preq->pd) || may_do_passkey_auth(pctx, preq->pd)) { +#else + if (may_do_cert_auth(pctx, preq->pd)) { +#endif /* BUILD_PASSKEY */ + if (preq->domain->name != NULL) { + ret = pam_eval_local_auth_policy(preq->cctx, pctx, preq->pd, preq, + &sc_auth, + &passkey_auth, + &local_policy); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to evaluate local auth policy\n"); + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + return; + } + } + } + + if (may_do_cert_auth(pctx, preq->pd) && preq->cert_list != NULL) { + /* Check if user matches certificate user */ + found = false; + for (preq->current_cert = preq->cert_list; + preq->current_cert != NULL; + preq->current_cert = sss_cai_get_next(preq->current_cert)) { + + cert_user_objs = sss_cai_get_cert_user_objs(preq->current_cert); + if (cert_user_objs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Unexpected missing certificate user, " + "trying next certificate.\n"); + continue; + } + + for (c = 0; c < cert_user_objs->count; c++) { + cert_user = ldb_msg_find_attr_as_string(cert_user_objs->msgs[c], + SYSDB_NAME, NULL); + if (cert_user == NULL) { + /* Even if there might be other users mapped to the + * certificate a missing SYSDB_NAME indicates some critical + * condition which justifies that the whole request is aborted + * */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Certificate user object has no name.\n"); + preq->pd->pam_status = PAM_USER_UNKNOWN; + pam_reply(preq); + return; + } + + if (ldb_dn_compare(cert_user_objs->msgs[c]->dn, + preq->user_obj->dn) == 0) { + found = true; + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + ret = sss_authtok_set_sc(preq->pd->authtok, + SSS_AUTHTOK_TYPE_SC_PIN, NULL, 0, + sss_cai_get_token_name(preq->current_cert), 0, + sss_cai_get_module_name(preq->current_cert), 0, + sss_cai_get_key_id(preq->current_cert), 0, + sss_cai_get_label(preq->current_cert), 0); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_authtok_set_sc failed, Smartcard " + "authentication detection might fail in " + "the backend.\n"); + } + + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, + cert_user, + preq->current_cert, + SSS_PAM_CERT_INFO); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + } + + } + } + } + + if (found) { + if (local_policy != NULL && strcasecmp(local_policy, "only") == 0) { + talloc_free(tmp_ctx); + DEBUG(SSSDBG_IMPORTANT_INFO, + "Local auth only set and matching certificate was found, " + "skipping online auth\n"); + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + preq->pd->pam_status = PAM_SUCCESS; + } else if (preq->pd->cmd == SSS_PAM_AUTHENTICATE + && IS_SC_AUTHTOK(preq->pd->authtok) + && (preq->cert_auth_local + || preq->initial_cert_auth_successful)) { + preq->pd->pam_status = PAM_SUCCESS; + preq->callback = pam_reply; + } + + pam_reply(preq); + return; + } + + /* We are done if we do not have to call the backend */ + if (preq->pd->cmd == SSS_PAM_AUTHENTICATE + && preq->cert_auth_local) { + preq->pd->pam_status = PAM_SUCCESS; + preq->callback = pam_reply; + pam_reply(preq); + return; + } + } else { + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + DEBUG(SSSDBG_TRACE_FUNC, + "User and certificate user do not match, " + "continue with other authentication methods.\n"); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "User and certificate user do not match.\n"); + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + return; + } + } + } + + if (local_policy != NULL && strcasecmp(local_policy, "only") == 0) { + talloc_free(tmp_ctx); + DEBUG(SSSDBG_IMPORTANT_INFO, "Local auth only set, skipping online auth\n"); + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + preq->pd->pam_status = PAM_SUCCESS; + } else if (preq->pd->cmd == SSS_PAM_AUTHENTICATE && IS_SC_AUTHTOK(preq->pd->authtok)) { + /* Trigger offline smartcardcard autheitcation */ + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + + pam_reply(preq); + return; + } + + preq->callback = pam_reply; + ret = pam_dp_send_req(preq); + DEBUG(SSSDBG_CONF_SETTINGS, "pam_dp_send_req returned %d\n", ret); + + talloc_free(tmp_ctx); + + if (ret != EOK) { + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } +} + +static int pam_cmd_authenticate(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_authenticate\n"); + return pam_forwarder(cctx, SSS_PAM_AUTHENTICATE); +} + +static int pam_cmd_setcred(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_setcred\n"); + return pam_forwarder(cctx, SSS_PAM_SETCRED); +} + +static int pam_cmd_acct_mgmt(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_acct_mgmt\n"); + return pam_forwarder(cctx, SSS_PAM_ACCT_MGMT); +} + +static int pam_cmd_open_session(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_open_session\n"); + return pam_forwarder(cctx, SSS_PAM_OPEN_SESSION); +} + +static int pam_cmd_close_session(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_close_session\n"); + return pam_forwarder(cctx, SSS_PAM_CLOSE_SESSION); +} + +static int pam_cmd_chauthtok(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_chauthtok\n"); + return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK); +} + +static int pam_cmd_chauthtok_prelim(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_chauthtok_prelim\n"); + return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK_PRELIM); +} + +static int pam_cmd_preauth(struct cli_ctx *cctx) +{ + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_preauth\n"); + return pam_forwarder(cctx, SSS_PAM_PREAUTH); +} + +static int pam_cmd_passkey_preauth(struct cli_ctx *cctx) +{ + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_passkey_preauth\n"); + return pam_forwarder(cctx, SSS_PAM_PASSKEY_PREAUTH); +} + +struct cli_protocol_version *register_cli_protocol_version(void) +{ + static struct cli_protocol_version pam_cli_protocol_version[] = { + {3, "2009-09-14", "make cli_pid mandatory"}, + {2, "2009-05-12", "new format "}, + {1, "2008-09-05", "initial version, \\0 terminated strings"}, + {0, NULL, NULL} + }; + + return pam_cli_protocol_version; +} + +struct sss_cmd_table *get_pam_cmds(void) +{ + static struct sss_cmd_table sss_cmds[] = { + {SSS_GET_VERSION, sss_cmd_get_version}, + {SSS_PAM_AUTHENTICATE, pam_cmd_authenticate}, + {SSS_PAM_SETCRED, pam_cmd_setcred}, + {SSS_PAM_ACCT_MGMT, pam_cmd_acct_mgmt}, + {SSS_PAM_OPEN_SESSION, pam_cmd_open_session}, + {SSS_PAM_CLOSE_SESSION, pam_cmd_close_session}, + {SSS_PAM_CHAUTHTOK, pam_cmd_chauthtok}, + {SSS_PAM_CHAUTHTOK_PRELIM, pam_cmd_chauthtok_prelim}, + {SSS_PAM_PREAUTH, pam_cmd_preauth}, + {SSS_PAM_PASSKEY_PREAUTH, pam_cmd_passkey_preauth}, + {SSS_GSSAPI_INIT, pam_cmd_gssapi_init}, + {SSS_GSSAPI_SEC_CTX, pam_cmd_gssapi_sec_ctx}, + {SSS_CLI_NULL, NULL} + }; + + return sss_cmds; +} + +errno_t +pam_set_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *username, + uint64_t value) +{ + TALLOC_CTX *tmp_ctx; + struct sysdb_attrs *attrs; + int ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + attrs = sysdb_new_attrs(tmp_ctx); + if (attrs == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_time_t(attrs, + SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, + value); + if (ret != EOK) { goto done; } + + ret = sysdb_set_user_attr(domain, username, attrs, SYSDB_MOD_REP); + if (ret != EOK) { goto done; } + +done: + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, sss_strerror(ret)); + } + + talloc_zfree(tmp_ctx); + return ret; +} + +static errno_t +pam_null_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *username) +{ + return pam_set_last_online_auth_with_curr_token(domain, username, 0); +} + +static errno_t +pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *name, + uint64_t *_value) +{ + TALLOC_CTX *tmp_ctx = NULL; + const char *attrs[] = { SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, NULL }; + struct ldb_message *ldb_msg; + uint64_t value = 0; + errno_t ret; + + if (name == NULL || *name == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); + ret = EINVAL; + goto done; + } + + if (domain->sysdb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing sysdb db context.\n"); + ret = EINVAL; + goto done; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_user_by_name(tmp_ctx, domain, name, attrs, &ldb_msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_search_user_by_name failed [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + + /* Check offline_auth_cache_timeout */ + value = ldb_msg_find_attr_as_uint64(ldb_msg, + SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, + 0); + ret = EOK; + +done: + if (ret == EOK) { + *_value = value; + } + + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/responder/pam/pamsrv_passkey.c b/src/responder/pam/pamsrv_passkey.c index c40515633b3..8782e5eec97 100644 --- a/src/responder/pam/pamsrv_passkey.c +++ b/src/responder/pam/pamsrv_passkey.c @@ -1,1879 +1,1878 @@ -/* - SSSD - - PAM Responder - passkey related requests - - Copyright (C) Justin Stephenson 2022 - - 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 . -*/ - -#include -#include "util/child_common.h" -#include "util/authtok.h" -#include "db/sysdb.h" -#include "db/sysdb_passkey_user_verification.h" -#include "responder/pam/pamsrv.h" - -#include "responder/pam/pamsrv_passkey.h" - -struct pam_passkey_verification_enum_str { - enum passkey_user_verification verification; - const char *option; -}; - -struct pam_passkey_table_data { - hash_table_t *table; - char *key; - struct pk_child_user_data *data; -}; - -struct pam_passkey_verification_enum_str pam_passkey_verification_enum_str[] = { - { PAM_PASSKEY_VERIFICATION_ON, "on" }, - { PAM_PASSKEY_VERIFICATION_OFF, "off" }, - { PAM_PASSKEY_VERIFICATION_OMIT, "unset" }, - { PAM_PASSKEY_VERIFICATION_INVALID, NULL } -}; - -#define PASSKEY_PREFIX "passkey:" -#define USER_VERIFICATION "user_verification=" -#define USER_VERIFICATION_LEN (sizeof(USER_VERIFICATION) -1) - -const char *pam_passkey_verification_enum_to_string(enum passkey_user_verification verification) -{ - size_t c; - - for (c = 0 ; pam_passkey_verification_enum_str[c].option != NULL; c++) { - if (pam_passkey_verification_enum_str[c].verification == verification) { - return pam_passkey_verification_enum_str[c].option; - } - } - - return "(NULL)"; -} - -struct passkey_ctx { - struct pam_ctx *pam_ctx; - struct tevent_context *ev; - struct pam_data *pd; - struct pam_auth_req *preq; -}; - -void pam_forwarder_passkey_cb(struct tevent_req *req); - -void pam_forwarder_get_devinfo_cb(struct tevent_req *req); -void pam_passkey_get_devinfo(struct tevent_req *req); - -errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, - struct pk_child_user_data *pk_data, - enum passkey_auth_action auth_action, - char **_result_kh, - char **_result_ph); - -struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - struct passkey_ctx *pctx); -void pam_passkey_get_user_done(struct tevent_req *req); -void pam_passkey_get_mapping_done(struct tevent_req *req); -errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, - struct tevent_req *req, - struct cache_req_result **_result); - -struct passkey_get_mapping_state { - struct pam_data *pd; - struct cache_req_result *result; -}; - -void passkey_kerberos_cb(struct tevent_req *req) -{ - struct pam_auth_req *preq = tevent_req_callback_data(req, - struct pam_auth_req); - errno_t ret = EOK; - int child_status; - - ret = pam_passkey_auth_recv(req, &child_status); - talloc_free(req); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); - - pam_check_user_search(preq); - -done: - pam_check_user_done(preq, ret); -} - -errno_t passkey_kerberos(struct pam_ctx *pctx, - struct pam_data *pd, - struct pam_auth_req *preq) -{ - errno_t ret; - const char *prompt; - const char *key; - const char *pin; - size_t pin_len; - struct pk_child_user_data *data; - struct tevent_req *req; - int timeout; - char *verify_opts; - bool debug_libfido2; - enum passkey_user_verification verification; - - ret = sss_authtok_get_passkey(preq, preq->pd->authtok, - &prompt, &key, &pin, &pin_len); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failure to get passkey authtok\n"); - return EIO; - } - - if (prompt == NULL || key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "Passkey prompt and key are missing or invalid.\n"); - return EIO; - } - - data = sss_ptr_hash_lookup(pctx->pk_table_data->table, key, - struct pk_child_user_data); - if (data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to lookup passkey authtok\n"); - return EIO; - } - - ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, - &timeout); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - ret = confdb_get_string(pctx->rctx->cdb, preq, CONFDB_MONITOR_CONF_ENTRY, - CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, - &verify_opts); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - /* Always use verification sent from passkey krb5 plugin */ - if (strcasecmp(data->user_verification, "false") == 0) { - verification = PAM_PASSKEY_VERIFICATION_OFF; - } else { - verification = PAM_PASSKEY_VERIFICATION_ON; - } - - ret = confdb_get_bool(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, - &debug_libfido2); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - req = pam_passkey_auth_send(preq->cctx, preq->cctx->ev, timeout, debug_libfido2, - verification, pd, data, - PASSKEY_KERBEROS_AUTH ); - if (req == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "passkey auth send failed [%d]: [%s]\n", - ret, sss_strerror(ret)); - goto done; - } - - tevent_req_set_callback(req, passkey_kerberos_cb, preq); - - ret = EAGAIN; - -done: - - return ret; - -} - - -errno_t passkey_kerberos_get_devinfo(struct pam_ctx *pctx, - struct pam_data *pd, - struct pam_auth_req *preq) -{ - errno_t ret; - const char *prompt; - const char *key; - const char *pin; - size_t pin_len; - struct pk_child_user_data *data; - struct tevent_req *req; - int timeout; - char *verify_opts; - bool debug_libfido2; - enum passkey_user_verification verification; - - if (pd->cmd != SSS_PAM_PASSKEY_PREAUTH) { - DEBUG(SSSDBG_OP_FAILURE, "passkey_kerberos_get_devinfo : invalid command\n"); - ret = PAM_SYSTEM_ERR; - goto done; - } - - ret = sss_authtok_get_passkey(preq, preq->pd->authtok, - &prompt, &key, &pin, &pin_len); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failure to get passkey authtok\n"); - return EIO; - } - - if (prompt == NULL || key == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "Passkey prompt and key are missing or invalid.\n"); - return EIO; - } - - DEBUG(SSSDBG_TRACE_FUNC, "lookup passkey auth keys: %s\nprompt: %s\n", - key ? key : "NULL", prompt ? prompt : "NULL"); - - data = sss_ptr_hash_lookup(pctx->pk_table_data->table, key, - struct pk_child_user_data); - if (data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to lookup passkey authtok\n"); - return EIO; - } - - ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, - &timeout); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - ret = confdb_get_string(pctx->rctx->cdb, preq, CONFDB_MONITOR_CONF_ENTRY, - CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, - &verify_opts); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - /* Always use verification sent from passkey krb5 plugin */ - if (strcasecmp(data->user_verification, "false") == 0) { - verification = PAM_PASSKEY_VERIFICATION_OFF; - } else { - verification = PAM_PASSKEY_VERIFICATION_ON; - } - - ret = confdb_get_bool(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, - &debug_libfido2); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - req = pam_passkey_auth_send(preq->cctx, preq->cctx->ev, timeout, debug_libfido2, - verification, pd, data, - PASSKEY_GET_DEVINFO); - if (req == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "passkey auth send failed [%d]: [%s]\n", - ret, sss_strerror(ret)); - goto done; - } - - tevent_req_set_callback(req, pam_forwarder_get_devinfo_cb, preq); - - ret = EAGAIN; - -done: - - return ret; - -} - -errno_t passkey_local(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - struct pam_ctx *pam_ctx, - struct pam_auth_req *preq, - struct pam_data *pd) -{ - struct tevent_req *req; - struct passkey_ctx *pctx; - errno_t ret; - - pctx = talloc_zero(mem_ctx, struct passkey_ctx); - if (pctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "pctx == NULL\n"); - return ENOMEM; - } - - pctx->pd = pd; - pctx->pam_ctx = pam_ctx; - pctx->ev = ev; - pctx->preq = preq; - - DEBUG(SSSDBG_TRACE_FUNC, "Checking for passkey authentication data\n"); - - req = pam_passkey_get_mapping_send(mem_ctx, pctx->ev, pctx); - if (req == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_get_mapping_send failed.\n"); - ret = ENOMEM; - goto done; - } - - tevent_req_set_callback(req, pam_passkey_get_user_done, pctx); - - ret = EAGAIN; - -done: - if (ret != EAGAIN) { - talloc_free(pctx); - } - - return ret; -} - -errno_t passkey_local_get_devinfo(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - struct pam_ctx *pam_ctx, - struct pam_auth_req *preq, - struct pam_data *pd) -{ - struct tevent_req *req; - struct passkey_ctx *pctx; - errno_t ret; - - pctx = talloc_zero(mem_ctx, struct passkey_ctx); - if (pctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "pctx == NULL\n"); - return ENOMEM; - } - - pctx->pd = pd; - pctx->pam_ctx = pam_ctx; - pctx->ev = ev; - pctx->preq = preq; - - if (pctx->pd->cmd != SSS_PAM_PASSKEY_PREAUTH) { - DEBUG(SSSDBG_OP_FAILURE, "passkey_local_get_devinfo : invalid command\n"); - ret = PAM_SYSTEM_ERR; - goto done; - } - - DEBUG(SSSDBG_TRACE_FUNC, "Checking for passkey authentication data\n"); - - req = pam_passkey_get_mapping_send(mem_ctx, pctx->ev, pctx); - if (req == NULL) { - DEBUG(SSSDBG_TRACE_ALL, "pam_passkey_get_mapping_send failed.\n"); - ret = ENOMEM; - goto done; - } - - tevent_req_set_callback(req, pam_passkey_get_devinfo, pctx); - - ret = EAGAIN; - -done: - if (ret != EAGAIN) { - talloc_free(pctx); - } - - return ret; -} -struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - struct passkey_ctx *pk_ctx) -{ - - struct passkey_get_mapping_state *state; - struct tevent_req *req; - struct tevent_req *subreq; - int ret; - static const char *attrs[] = { SYSDB_NAME, SYSDB_USER_PASSKEY, NULL }; - - req = tevent_req_create(mem_ctx, &state, struct passkey_get_mapping_state); - if (req == NULL) { - ret = ENOMEM; - goto done; - } - - subreq = cache_req_user_by_name_attrs_send(state, pk_ctx->ev, - pk_ctx->pam_ctx->rctx, - pk_ctx->pam_ctx->rctx->ncache, 0, - pk_ctx->pd->domain, - pk_ctx->pd->user, attrs); - if (subreq == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); - ret = ENOMEM; - goto done; - } - - tevent_req_set_callback(subreq, pam_passkey_get_mapping_done, req); - - return req; - -done: - tevent_req_error(req, ret); - tevent_req_post(req, ev); - - return req; -} - -void pam_passkey_get_mapping_done(struct tevent_req *subreq) -{ - struct cache_req_result *result; - struct tevent_req *req; - struct passkey_get_mapping_state *state; - - errno_t ret; - - req = tevent_req_callback_data(subreq, struct tevent_req); - state = tevent_req_data(req, struct passkey_get_mapping_state); - - ret = cache_req_user_by_name_attrs_recv(state, subreq, &result); - state->result = result; - - talloc_zfree(subreq); - if (ret != EOK) { - tevent_req_error(req, ret); - return; - } - - tevent_req_done(req); - return; -} - -errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, - struct tevent_req *req, - struct cache_req_result **_result) -{ - struct passkey_get_mapping_state *state = NULL; - - state = tevent_req_data(req, struct passkey_get_mapping_state); - - TEVENT_REQ_RETURN_ON_ERROR(req); - - *_result = talloc_steal(mem_ctx, state->result); - return EOK; -} - -errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, - const char *verify_opts, - enum passkey_user_verification *_user_verification) -{ - int ret; - TALLOC_CTX *tmp_ctx; - char **opts; - size_t c; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); - return ENOMEM; - } - - if (verify_opts == NULL) { - ret = EOK; - goto done; - } - - ret = split_on_separator(tmp_ctx, verify_opts, ',', true, true, &opts, - NULL); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d], %s.\n", - ret, sss_strerror(ret)); - goto done; - } - - for (c = 0; opts[c] != NULL; c++) { - if (strncasecmp(opts[c], USER_VERIFICATION, USER_VERIFICATION_LEN) == 0) { - if (strcasecmp("true", &opts[c][USER_VERIFICATION_LEN]) == 0) { - DEBUG(SSSDBG_TRACE_FUNC, "user_verification set to true.\n"); - *_user_verification = PAM_PASSKEY_VERIFICATION_ON; - } else if (strcasecmp("false", &opts[c][USER_VERIFICATION_LEN]) == 0) { - DEBUG(SSSDBG_TRACE_FUNC, "user_verification set to false.\n"); - *_user_verification = PAM_PASSKEY_VERIFICATION_OFF; - } - } else { - DEBUG(SSSDBG_MINOR_FAILURE, - "Unsupported passkey verification option [%s], " \ - "skipping.\n", opts[c]); - } - } - - ret = EOK; - -done: - talloc_free(tmp_ctx); - - return ret; -} - -static errno_t passkey_local_verification(TALLOC_CTX *mem_ctx, - struct passkey_ctx *pctx, - struct confdb_ctx *cdb, - struct sysdb_ctx *sysdb, - const char *domain_name, - struct pk_child_user_data *pk_data, - enum passkey_user_verification *_user_verification, - bool *_debug_libfido2) -{ - TALLOC_CTX *tmp_ctx; - errno_t ret; - const char *verification_from_ldap; - char *verify_opts = NULL; - bool debug_libfido2 = false; - enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - return ENOMEM; - } - - ret = sysdb_domain_get_passkey_user_verification(tmp_ctx, sysdb, domain_name, - &verification_from_ldap); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read passkeyUserVerification from sysdb: [%d]: %s\n", - ret, sss_strerror(ret)); - /* This is expected for AD and LDAP */ - ret = EOK; - goto done; - } - - ret = confdb_get_bool(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, - &debug_libfido2); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - /* If require user verification setting is set in LDAP, use it */ - if (verification_from_ldap != NULL) { - if (strcasecmp(verification_from_ldap, "true") == 0) { - verification = PAM_PASSKEY_VERIFICATION_ON; - } else if (strcasecmp(verification_from_ldap, "false") == 0) { - verification = PAM_PASSKEY_VERIFICATION_OFF; - } - DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification is being enforced from LDAP\n"); - } else { - /* No verification set in LDAP, fallback to local sssd.conf setting */ - ret = confdb_get_string(pctx->pam_ctx->rctx->cdb, tmp_ctx, CONFDB_MONITOR_CONF_ENTRY, - CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, - &verify_opts); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - - ret = read_passkey_conf_verification(tmp_ctx, verify_opts, &verification); - if (ret != EOK) { - DEBUG(SSSDBG_MINOR_FAILURE, "Unable to parse passkey verificaton options.\n"); - /* Continue anyway */ - } - DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification is being enforced from local configuration\n"); - } - DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification setting [%s]\n", - pam_passkey_verification_enum_to_string(verification)); - - *_user_verification = verification; - *_debug_libfido2 = debug_libfido2; - - ret = EOK; - -done: - talloc_free(tmp_ctx); - - return ret; -} - -static bool mapping_is_passkey(TALLOC_CTX *tmp_ctx, - const char *mapping_str) -{ - int ret; - char **mappings; - - if (mapping_str == NULL) { - return false; - } - - ret = split_on_separator(tmp_ctx, (const char *) mapping_str, ':', true, true, - &mappings, NULL); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Incorrectly formatted passkey data [%d]: %s\n", - ret, sss_strerror(ret)); - return false; - } - - if (strcasecmp(mappings[0], "passkey") != 0) { - DEBUG(SSSDBG_TRACE_FUNC, "Mapping data found is not passkey related\n"); - return false; - } - - return true; -} - -errno_t process_passkey_data(TALLOC_CTX *mem_ctx, - struct ldb_message *user_mesg, - const char *domain, - struct pk_child_user_data *_data) -{ - struct ldb_message_element *el; - TALLOC_CTX *tmp_ctx; - int ret; - int num_creds = 0; - char **mappings; - const char **kh_mappings; - const char **public_keys; - const char *domain_name; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - ERROR("talloc_new() failed\n"); - return ENOMEM; - } - - el = ldb_msg_find_element(user_mesg, SYSDB_USER_PASSKEY); - if (el == NULL) { - DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found\n"); - ret = ENOENT; - goto done; - } - - kh_mappings = talloc_zero_array(tmp_ctx, const char *, el->num_values + 1); - if (kh_mappings == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); - ret = ENOMEM; - goto done; - } - - public_keys = talloc_zero_array(tmp_ctx, const char *, el->num_values + 1); - if (public_keys == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); - ret = ENOMEM; - goto done; - } - - for (int i = 0; i < el->num_values; i++) { - /* This attribute may contain other mapping data unrelated to passkey. In that case - * let's skip it. For example, AD user altSecurityIdentities may store ssh public key - * or smart card mapping data */ - if ((mapping_is_passkey(tmp_ctx, (const char *)el->values[i].data)) == false) { - continue; - } - ret = split_on_separator(tmp_ctx, (const char *) el->values[i].data, ',', true, true, - &mappings, NULL); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Incorrectly formatted passkey data [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - kh_mappings[num_creds] = talloc_strdup(kh_mappings, mappings[0] + strlen(PASSKEY_PREFIX)); - if (kh_mappings[num_creds] == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup key handle failed.\n"); - ret = ENOMEM; - goto done; - } - - public_keys[num_creds] = talloc_strdup(public_keys, mappings[1]); - if (public_keys[num_creds] == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup public key failed.\n"); - ret = ENOMEM; - goto done; - } - - num_creds++; - } - - if (num_creds == 0) { - ret = ENOENT; - goto done; - } - - domain_name = talloc_strdup(tmp_ctx, domain); - if (domain_name == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup domain failed.\n"); - ret = ENOMEM; - goto done; - } - - _data->domain = talloc_steal(mem_ctx, domain_name); - _data->key_handles = talloc_steal(mem_ctx, kh_mappings); - _data->public_keys = talloc_steal(mem_ctx, public_keys); - _data->num_credentials = num_creds; - - ret = EOK; -done: - talloc_free(tmp_ctx); - - return ret; -} - -void pam_forwarder_passkey_cb(struct tevent_req *req) -{ - struct pam_auth_req *preq = tevent_req_callback_data(req, - struct pam_auth_req); - errno_t ret = EOK; - int child_status; - - ret = pam_passkey_auth_recv(req, &child_status); - talloc_free(req); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - preq->pd->passkey_local_done = true; - - DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); - preq->pd->pam_status = PAM_SUCCESS; - pam_reply(preq); - - return; - -done: - pam_check_user_done(preq, ret); -} - -#define PASSKEY_PINUV "/var/run/passkey-pinuv" -#define PASSKEY_PINONLY "/var/run/passkey-pinonly" -#define PASSKEY_NODEV "/var/run/passkey-nodev" - -void pam_forwarder_get_devinfo_cb(struct tevent_req *req) -{ - struct pam_auth_req *preq = tevent_req_callback_data(req, - struct pam_auth_req); - errno_t ret = EOK; - const char* devinfo = "errdev"; - int child_status; - - ret = pam_passkey_auth_recv(req, &child_status); - talloc_free(req); - if (ret != EOK) - DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_recv for devinfo failed [%d]: %s\n", - ret, sss_strerror(ret)); - - DEBUG(SSSDBG_TRACE_FUNC, "passkey child devinfo finished with status [%d]\n", child_status); - - if (child_status != 0) { - int fd; - devinfo = "nodev"; - fd = creat(PASSKEY_NODEV, 0000); - close(fd); - } else { - (void)remove (PASSKEY_NODEV); - if (access(PASSKEY_PINUV, F_OK) == 0) { - DEBUG(SSSDBG_TRACE_FUNC, "devinfo pinuv file indicator\n"); - devinfo = "pinuv"; - } else if (access(PASSKEY_PINONLY, F_OK) == 0) { - DEBUG(SSSDBG_TRACE_FUNC, "devinfo pinonly file indicator\n"); - devinfo = "pinonly"; - } else { - DEBUG(SSSDBG_TRACE_FUNC, "devinfo invalid file indicator\n"); - devinfo = "errdev"; - } - } - - /* remove indicators - * They will be rebuild according the device capabilities - * (see passkey_child) - * during authentication request. - */ - (void)remove (PASSKEY_PINUV); - (void)remove (PASSKEY_PINONLY); - - ret = pam_add_response(preq->pd, SSS_PAM_PASSKEY_DEVINFO, strlen(devinfo) + 1, - (const uint8_t *) devinfo); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "devinfo: pam_add_response failed. [%d]: %s\n", - ret, sss_strerror(ret)); - } - - - preq->pd->pam_status = PAM_SUCCESS; - - pam_reply(preq); - - return; -} - -void pam_passkey_get_user_done(struct tevent_req *req) -{ - int ret; - struct passkey_ctx *pctx; - bool debug_libfido2 = false; - char *domain_name = NULL; - int timeout; - struct cache_req_result *result = NULL; - struct pk_child_user_data *pk_data = NULL; - enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; - - pctx = tevent_req_callback_data(req, struct passkey_ctx); - - ret = pam_passkey_get_mapping_recv(pctx, req, &result); - talloc_zfree(req); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_name_attrs_recv failed [%d]: %s.\n", - ret, sss_strerror(ret)); - goto done; - } - - if (result == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "cache req result == NULL\n"); - ret = ENOMEM; - goto done; - } - - pk_data = talloc_zero(pctx, struct pk_child_user_data); - if (!pk_data) { - DEBUG(SSSDBG_CRIT_FAILURE, "pk_data == NULL\n"); - ret = ENOMEM; - goto done; - } - - /* Use dns_name for AD/IPA - for LDAP fallback to domain->name */ - if (result->domain != NULL) { - domain_name = result->domain->dns_name; - if (domain_name == NULL) { - domain_name = result->domain->name; - } - } - - if (domain_name == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Invalid or missing domain name\n"); - ret = EIO; - goto done; - } - - /* Get passkey data */ - DEBUG(SSSDBG_TRACE_ALL, "Processing passkey data\n"); - ret = process_passkey_data(pk_data, result->msgs[0], domain_name, pk_data); - if (ret != EOK) { - DEBUG(SSSDBG_TRACE_FUNC, - "process_passkey_data failed: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - /* timeout */ - ret = confdb_get_int(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, - &timeout); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - ret = passkey_local_verification(pctx, pctx, pctx->pam_ctx->rctx->cdb, - result->domain->sysdb, result->domain->dns_name, - pk_data, &verification, &debug_libfido2); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to check passkey verification [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - /* Preauth respond with prompt_pin true or false based on user verification */ - if (pctx->pd->cmd == SSS_PAM_PREAUTH) { - const char *prompt_pin = verification == PAM_PASSKEY_VERIFICATION_OFF ? "false" : "true"; - - ret = pam_add_response(pctx->pd, SSS_PAM_PASSKEY_INFO, strlen(prompt_pin) + 1, - (const uint8_t *) prompt_pin); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed. [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - pctx->pd->pam_status = PAM_SUCCESS; - pam_reply(pctx->preq); - talloc_free(pk_data); - return; - } - - req = pam_passkey_auth_send(pctx, pctx->ev, timeout, debug_libfido2, - verification, pctx->pd, pk_data, - PASSKEY_LOCAL_AUTH); - if (req == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_send failed [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - tevent_req_set_callback(req, pam_forwarder_passkey_cb, pctx->preq); - -done: - if (pk_data != NULL) { - talloc_free(pk_data); - } - - if (ret == ENOENT) { - /* No passkey data, continue through to typical auth flow */ - DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found, skipping passkey auth\n"); - pctx->preq->passkey_data_exists = false; - pam_check_user_search(pctx->preq); - } else if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Unexpected passkey error [%d]: %s.\n", - ret, sss_strerror(ret)); - pctx->preq->passkey_data_exists = false; - pctx->preq->pd->pam_status = PAM_SYSTEM_ERR; - pam_reply(pctx->preq); - } - - return; -} - -void pam_passkey_get_devinfo(struct tevent_req *req) -{ - int ret; - struct passkey_ctx *pctx; - bool debug_libfido2 = false; - char *domain_name = NULL; - int timeout; - struct cache_req_result *result = NULL; - struct pk_child_user_data *pk_data = NULL; - enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; - - - DEBUG(SSSDBG_TRACE_ALL, "pam_passkey_get_devinfo... \n"); - - pctx = tevent_req_callback_data(req, struct passkey_ctx); - - - ret = pam_passkey_get_mapping_recv(pctx, req, &result); - talloc_zfree(req); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_OP_FAILURE, "devinfo cache_req_user_by_name_attrs_recv failed [%d]: %s.\n", - ret, sss_strerror(ret)); - goto done; - } - - if (result == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "idevinfo cache req result == NULL\n"); - ret = ENOMEM; - goto done; - } - - pk_data = talloc_zero(pctx, struct pk_child_user_data); - if (!pk_data) { - DEBUG(SSSDBG_CRIT_FAILURE, "devinfo pk_data == NULL\n"); - ret = ENOMEM; - goto done; - } - - /* Use dns_name for AD/IPA - for LDAP fallback to domain->name */ - if (result->domain != NULL) { - domain_name = result->domain->dns_name; - if (domain_name == NULL) { - domain_name = result->domain->name; - } - } - - if (domain_name == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "devinfo Invalid or missing domain name\n"); - ret = EIO; - goto done; - } - - /* Get passkey data */ - DEBUG(SSSDBG_TRACE_ALL, "devinfo Processing passkey data\n"); - ret = process_passkey_data(pk_data, result->msgs[0], domain_name, pk_data); - if (ret != EOK) { - DEBUG(SSSDBG_TRACE_FUNC, - "process_passkey_data for devinfo failed: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - /* timeout */ - ret = confdb_get_int(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, - &timeout); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "devinfo Failed to read passkey_child_timeout from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - ret = passkey_local_verification(pctx, pctx, pctx->pam_ctx->rctx->cdb, - result->domain->sysdb, result->domain->dns_name, - pk_data, &verification, &debug_libfido2); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to check passkey devinfo verification [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - req = pam_passkey_auth_send(pctx, pctx->ev, timeout, debug_libfido2, - verification, pctx->pd, pk_data, - PASSKEY_GET_DEVINFO); - if (req == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_send devinfo failed [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - tevent_req_set_callback(req, pam_forwarder_get_devinfo_cb, pctx->preq); - - ret = EOK; - -done: - if (pk_data != NULL) { - talloc_free(pk_data); - } - - if (ret != EOK) { - DEBUG(SSSDBG_TRACE_ALL, "Unexpected passkey devinfo error [%d]: %s.\n", - ret, sss_strerror(ret)); - /* the error does not affect existence of passkey_data - * pctx->preq->passkey_data_exists = false; - */ - pctx->preq->pd->pam_status = PAM_SYSTEM_ERR; - pam_reply(pctx->preq); - } - - return; -} - -struct pam_passkey_auth_send_state { - struct pam_data *pd; - struct tevent_context *ev; - struct tevent_timer *timeout_handler; - struct sss_child_ctx_old *child_ctx; - struct child_io_fds *io; - const char *logfile; - const char **extra_args; - char *verify_opts; - int timeout; - int child_status; - enum passkey_auth_action auth_action; -}; - -static errno_t passkey_child_exec(struct tevent_req *req); -static void pam_passkey_auth_done(int child_status, - struct tevent_signal *sige, - void *pvt); - -static void pam_passkey_get_devinfo_done(int child_status, - struct tevent_signal *sige, - void *pvt); - -static int pin_destructor(void *ptr) -{ - uint8_t *pin = talloc_get_type(ptr, uint8_t); - if (pin == NULL) return EOK; - - sss_erase_talloc_mem_securely(pin); - - return EOK; -} - -errno_t get_passkey_child_write_buffer(TALLOC_CTX *mem_ctx, - struct pam_data *pd, - uint8_t **_buf, size_t *_len) -{ - int ret; - uint8_t *buf; - size_t len; - const char *pin = NULL; - - if (pd == NULL || pd->authtok == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Missing authtok.\n"); - return EINVAL; - } - - if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY || - sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { - ret = sss_authtok_get_passkey_pin(pd->authtok, &pin, &len); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_passkey_pin failed [%d]: %s\n", - ret, sss_strerror(ret)); - return ret; - } - - if (pin == NULL || len == 0) { - DEBUG(SSSDBG_OP_FAILURE, "Missing PIN.\n"); - return EINVAL; - } - - buf = talloc_size(mem_ctx, len); - if (buf == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); - return ENOMEM; - } - - talloc_set_destructor((void *) buf, pin_destructor); - - safealign_memcpy(buf, pin, len, NULL); - } else { - DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported authtok type [%d].\n", - sss_authtok_get_type(pd->authtok)); - return EINVAL; - } - - *_len = len; - *_buf = buf; - - return EOK; -} - -static void pam_passkey_child_read_data(struct tevent_req *subreq) -{ - uint8_t *buf; - ssize_t buf_len; - char *str; - struct tevent_req *req = tevent_req_callback_data(subreq, - struct tevent_req); - struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); - int ret; - - ret = read_pipe_recv(subreq, state, &buf, &buf_len); - talloc_zfree(subreq); - if (ret != EOK) { - tevent_req_error(req, ret); - return; - } - - str = malloc(sizeof(char) * buf_len); - if (str == NULL) { - return; - } - - snprintf(str, buf_len, "%s", buf); - - sss_authtok_set_passkey_reply(state->pd->authtok, str, 0); - - free(str); - - tevent_req_done(req); - return; -} - -static void passkey_child_write_done(struct tevent_req *subreq) -{ - struct tevent_req *req = tevent_req_callback_data(subreq, - struct tevent_req); - struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); - - int ret; - - DEBUG(SSSDBG_TRACE_LIBS, "Sending passkey data complete\n"); - - ret = write_pipe_recv(subreq); - talloc_zfree(subreq); - if (ret != EOK) { - tevent_req_error(req, ret); - return; - } - - PIPE_FD_CLOSE(state->io->write_to_child_fd); - - if (state->auth_action == PASSKEY_KERBEROS_AUTH) { - /* Read data back from passkey child */ - subreq = read_pipe_send(state, state->ev, state->io->read_from_child_fd); - if (subreq == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "read_pipe_send failed.\n"); - return; - } - - tevent_req_set_callback(subreq, pam_passkey_child_read_data, req); - } -} - -errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, - struct pk_child_user_data *pk_data, - enum passkey_auth_action auth_action, - char **_result_kh, - char **_result_pk) -{ - errno_t ret; - char *result_kh = NULL; - char *result_pk = NULL; - - int index = pk_data->num_credentials - 1; - /* start we last credential because it is the newer one - * therefore te mos accurate */ - - if (index < 0) { - ret = EINVAL; - goto done; - } - - result_kh = talloc_strdup(mem_ctx, pk_data->key_handles[index]); - if (auth_action == PASSKEY_LOCAL_AUTH) { - result_pk = talloc_strdup(mem_ctx, pk_data->public_keys[index]); - if (result_pk == NULL) { - ret = ENOMEM; - goto done; - } - } - - for (int i = index - 1; i >= 0; i--) { - result_kh = talloc_strdup_append(result_kh, ","); - if (result_kh == NULL) { - ret = ENOMEM; - goto done; - } - - result_kh = talloc_strdup_append(result_kh, pk_data->key_handles[i]); - if (result_kh == NULL) { - ret = ENOMEM; - goto done; - } - - if (auth_action == PASSKEY_LOCAL_AUTH) { - result_pk = talloc_strdup_append(result_pk, ","); - if (result_pk == NULL) { - ret = ENOMEM; - goto done; - } - - result_pk = talloc_strdup_append(result_pk, pk_data->public_keys[i]); - if (result_kh == NULL || result_pk == NULL) { - ret = ENOMEM; - goto done; - } - } - } - - *_result_kh = result_kh; - *_result_pk = result_pk; - - ret = EOK; -done: - return ret; -} - -struct tevent_req * -pam_passkey_auth_send(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - int timeout, - bool debug_libfido2, - enum passkey_user_verification verification, - struct pam_data *pd, - struct pk_child_user_data *pk_data, - enum passkey_auth_action auth_action) -{ - struct tevent_req *req; - struct pam_passkey_auth_send_state *state; - size_t arg_c = 0; - char *result_kh; - char *result_pk; - int num_args; - int ret; - - req = tevent_req_create(mem_ctx, &state, struct pam_passkey_auth_send_state); - if (req == NULL) { - return NULL; - } - - state->pd = pd; - state->ev = ev; - - state->timeout = timeout; - state->auth_action = auth_action; - state->logfile = PASSKEY_CHILD_LOG_FILE; - state->io = talloc(state, struct child_io_fds); - if (state->io == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc child fds failed.\n"); - ret = ENOMEM; - goto done; - } - state->io->write_to_child_fd = -1; - state->io->read_from_child_fd = -1; - talloc_set_destructor((void *) state->io, child_io_destructor); - - num_args = 11; - state->extra_args = talloc_zero_array(state, const char *, num_args + 1); - if (state->extra_args == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); - ret = ENOMEM; - goto done; - } - - switch (verification) { - case PAM_PASSKEY_VERIFICATION_ON: - state->extra_args[arg_c++] = "--user-verification=true"; - DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification true\n"); - break; - case PAM_PASSKEY_VERIFICATION_OFF: - state->extra_args[arg_c++] = "--user-verification=false"; - DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification false\n"); - break; - default: - DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification unset\n"); - break; - } - - ret = pam_passkey_concatenate_keys(state, pk_data, state->auth_action, - &result_kh, &result_pk); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_concatenate keys failed - [%d]: [%s]\n", - ret, sss_strerror(ret)); - goto done; - } - - switch (auth_action) { - - case PASSKEY_KERBEROS_AUTH: { - DEBUG(SSSDBG_TRACE_FUNC, "auth_action: PASSKEY_KERBEROS_AUTH\n"); - state->extra_args[arg_c++] = pk_data->crypto_challenge; - state->extra_args[arg_c++] = "--cryptographic-challenge"; - state->extra_args[arg_c++] = result_kh; - state->extra_args[arg_c++] = "--key-handle"; - state->extra_args[arg_c++] = pk_data->domain; - state->extra_args[arg_c++] = "--domain"; - state->extra_args[arg_c++] = "--get-assert"; - break; - } - case PASSKEY_LOCAL_AUTH: { - DEBUG(SSSDBG_TRACE_FUNC, "auth_action: PASSKEY_LOCAL_AUTH\n"); - state->extra_args[arg_c++] = result_pk; - state->extra_args[arg_c++] = "--public-key"; - state->extra_args[arg_c++] = result_kh; - state->extra_args[arg_c++] = "--key-handle"; - state->extra_args[arg_c++] = pk_data->domain; - state->extra_args[arg_c++] = "--domain"; - state->extra_args[arg_c++] = state->pd->user; - state->extra_args[arg_c++] = "--username"; - state->extra_args[arg_c++] = "--authenticate"; - break; - } - /* ADDED VALMIDO */ - case PASSKEY_GET_DEVINFO: { - DEBUG(SSSDBG_TRACE_FUNC, "auth_action: PASSKEY_GET_DEVINFO\n"); - state->extra_args[arg_c++] = result_kh; - state->extra_args[arg_c++] = "--key-handle"; - state->extra_args[arg_c++] = pk_data->domain; - state->extra_args[arg_c++] = "--domain"; - state->extra_args[arg_c++] = "--get-device-info"; - break; - } - /* END VALMIDO */ - default: { - ret = ERR_INTERNAL; - DEBUG(SSSDBG_OP_FAILURE, "auth_send invalid action]\n"); - goto done; - } - } - - ret = passkey_child_exec(req); - -done: - if (ret == EOK) { - tevent_req_done(req); - tevent_req_post(req, ev); - } else if (ret != EAGAIN) { - tevent_req_error(req, ret); - tevent_req_post(req, ev); - } - - return req; -} - -static void -passkey_child_timeout(struct tevent_context *ev, - struct tevent_timer *te, - struct timeval tv, void *pvt) -{ - struct tevent_req *req = - talloc_get_type(pvt, struct tevent_req); - struct pam_passkey_auth_send_state *state = - tevent_req_data(req, struct pam_passkey_auth_send_state); - - DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for passkey child, " - "consider increasing passkey_child_timeout\n"); - child_handler_destroy(state->child_ctx); - state->child_ctx = NULL; - state->child_status = ETIMEDOUT; - tevent_req_error(req, ERR_PASSKEY_CHILD_TIMEOUT); -} - -static errno_t passkey_child_exec(struct tevent_req *req) -{ - struct pam_passkey_auth_send_state *state; - struct tevent_req *subreq; - int pipefd_from_child[2] = PIPE_INIT; - int pipefd_to_child[2] = PIPE_INIT; - pid_t child_pid; - uint8_t *write_buf = NULL; - size_t write_buf_len = 0; - struct timeval tv; - int ret; - - state = tevent_req_data(req, struct pam_passkey_auth_send_state); - - ret = pipe(pipefd_from_child); - if (ret == -1) { - ret = errno; - DEBUG(SSSDBG_CRIT_FAILURE, - "pipe failed [%d][%s].\n", ret, strerror(ret)); - goto done; - } - ret = pipe(pipefd_to_child); - if (ret == -1) { - ret = errno; - DEBUG(SSSDBG_CRIT_FAILURE, - "pipe failed [%d][%s].\n", ret, strerror(ret)); - goto done; - } - - child_pid = fork(); - if (child_pid == 0) { /* child */ - exec_child_ex(state, pipefd_to_child, pipefd_from_child, - PASSKEY_CHILD_PATH, state->logfile, state->extra_args, - false, STDIN_FILENO, STDOUT_FILENO); - /* We should never get here */ - ret = errno; - DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec passkey child\n"); - goto done; - } else if (child_pid > 0) { /* parent */ - state->io->read_from_child_fd = pipefd_from_child[0]; - PIPE_FD_CLOSE(pipefd_from_child[1]); - sss_fd_nonblocking(state->io->read_from_child_fd); - - state->io->write_to_child_fd = pipefd_to_child[1]; - PIPE_FD_CLOSE(pipefd_to_child[0]); - sss_fd_nonblocking(state->io->write_to_child_fd); - - /* Set up SIGCHLD handler */ - if (state->auth_action == PASSKEY_KERBEROS_AUTH) - ret = child_handler_setup(state->ev, child_pid, NULL, req, &state->child_ctx); - else if (state->auth_action == PASSKEY_LOCAL_AUTH) - ret = child_handler_setup(state->ev, child_pid, - pam_passkey_auth_done, req, - &state->child_ctx); - else if (state->auth_action == PASSKEY_GET_DEVINFO) - ret = child_handler_setup(state->ev, child_pid, - pam_passkey_get_devinfo_done, req, - &state->child_ctx); - else { - DEBUG(SSSDBG_CRIT_FAILURE, "BUG: internal error - invalid auth_action\n"); - ret = ERR_INTERNAL; - goto done; - } - - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", - ret, sss_strerror(ret)); - ret = ERR_PASSKEY_CHILD; - goto done; - } - - /* Set up timeout handler */ - tv = tevent_timeval_current_ofs(state->timeout, 0); - state->timeout_handler = tevent_add_timer(state->ev, req, tv, - passkey_child_timeout, req); - if (state->timeout_handler == NULL) { - ret = ERR_PASSKEY_CHILD; - goto done; - } - - if (state->auth_action != PASSKEY_GET_DEVINFO) { - if (sss_authtok_get_type(state->pd->authtok) != SSS_AUTHTOK_TYPE_EMPTY) { - /* PIN is needed */ - ret = get_passkey_child_write_buffer(state, state->pd, &write_buf, - &write_buf_len); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "get_passkey_child_write_buffer failed [%d]: %s.\n", - ret, sss_strerror(ret)); - goto done; - } - } - } - - if (write_buf_len != 0) { - subreq = write_pipe_send(state, state->ev, write_buf, write_buf_len, - state->io->write_to_child_fd); - if (subreq == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "write_pipe_send failed.\n"); - ret = ERR_PASSKEY_CHILD; - goto done; - } - tevent_req_set_callback(subreq, passkey_child_write_done, req); - } - /* Now either wait for the timeout to fire or the child to finish */ - } else { /* error */ - ret = errno; - DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d][%s].\n", - ret, sss_strerror(ret)); - goto done; - } - - return EAGAIN; - -done: - if (ret != EOK) { - PIPE_CLOSE(pipefd_from_child); - PIPE_CLOSE(pipefd_to_child); - } - - return ret; -} - -static void -pam_passkey_get_devinfo_done(int child_status, - struct tevent_signal *sige, - void *pvt) -{ - struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); - - struct pam_passkey_auth_send_state *state = - tevent_req_data(req, struct pam_passkey_auth_send_state); - state->child_status = WEXITSTATUS(child_status); - if (WIFEXITED(child_status)) { - if (WEXITSTATUS(child_status) != 0) { - DEBUG(SSSDBG_OP_FAILURE, - PASSKEY_CHILD_PATH " devinfo failed with status [%d]. Check passkey_child" - " logs for more information.\n", - WEXITSTATUS(child_status)); - tevent_req_error(req, ERR_PASSKEY_CHILD); - return; - } - } else if (WIFSIGNALED(child_status)) { - DEBUG(SSSDBG_OP_FAILURE, - PASSKEY_CHILD_PATH " devinfo was terminated by signal [%d]. Check passkey_child" - " logs for more information.\n", - WTERMSIG(child_status)); - tevent_req_error(req, ECHILD); - return; - } - - DEBUG(SSSDBG_TRACE_FUNC, "devinfo data is valid. Mark done\n"); - tevent_req_done(req); - return; -} - -errno_t pam_passkey_auth_recv(struct tevent_req *req, - int *child_status) -{ - struct pam_passkey_auth_send_state *state = - tevent_req_data(req, struct pam_passkey_auth_send_state); - - *child_status = state->child_status; - - TEVENT_REQ_RETURN_ON_ERROR(req); - - return EOK; -} - -errno_t decode_pam_passkey_msg(TALLOC_CTX *mem_ctx, - uint8_t *buf, - size_t len, - struct pk_child_user_data **_data) -{ - - size_t p = 0; - size_t pctr = 0; - errno_t ret; - size_t offset; - struct pk_child_user_data *data = NULL; - TALLOC_CTX *tmp_ctx; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - return ENOMEM; - } - - data = talloc_zero(tmp_ctx, struct pk_child_user_data); - if (data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to talloc passkey data.\n"); - ret = ENOMEM; - goto done; - } - - data->user_verification = talloc_strdup(data, (char *) &buf[p]); - if (data->user_verification == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey prompt.\n"); - ret = ENOMEM; - goto done; - } - - offset = strlen(data->user_verification) + 1; - if (offset >= len) { - DEBUG(SSSDBG_OP_FAILURE, "passkey prompt offset failure.\n"); - ret = EIO; - goto done; - } - - data->crypto_challenge = talloc_strdup(data, (char *) &buf[p + offset]); - if (data->crypto_challenge == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey challenge.\n"); - ret = ENOMEM; - goto done; - } - - offset += strlen(data->crypto_challenge) + 1; - if (offset >= len) { - DEBUG(SSSDBG_OP_FAILURE, "passkey challenge offset failure.\n"); - ret = EIO; - goto done; - } - - - data->domain = talloc_strdup(data, (char *) &buf[p] + offset); - if (data->domain == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey domain.\n"); - ret = ENOMEM; - goto done; - } - - offset += strlen(data->domain) + 1; - if (offset >= len) { - DEBUG(SSSDBG_OP_FAILURE, "passkey domain offset failure.\n"); - ret = EIO; - goto done; - } - - SAFEALIGN_COPY_UINT32(&data->num_credentials, &buf[p + offset], &pctr); - size_t list_sz = (size_t) data->num_credentials; - - offset += sizeof(uint32_t); - - data->key_handles = talloc_zero_array(data, const char *, list_sz); - - for (int i = 0; i < list_sz; i++) { - data->key_handles[i] = talloc_strdup(data->key_handles, (char *) &buf[p + offset]); - if (data->key_handles[i] == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey list.\n"); - ret = ENOMEM; - goto done; - } - - offset += strlen(data->key_handles[i]) + 1; - } - - *_data = talloc_steal(mem_ctx, data); - - ret = EOK; -done: - talloc_free(tmp_ctx); - return ret; -} - -errno_t save_passkey_data(TALLOC_CTX *mem_ctx, - struct pam_ctx *pctx, - struct pk_child_user_data *data, - struct pam_auth_req *preq) -{ - char *pk_key; - errno_t ret; - TALLOC_CTX *tmp_ctx; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - return ENOMEM; - } - - /* Passkey data (pk_table_data) is stolen onto client ctx, it will - * be freed when the client closes, and the sss_ptr_hash interface - * takes care of automatically removing it from the hash table then */ - pctx->pk_table_data = talloc_zero(tmp_ctx, struct pam_passkey_table_data); - if (pctx->pk_table_data == NULL) { - return ENOMEM; - } - - if (pctx->pk_table_data->table == NULL) { - pctx->pk_table_data->table = sss_ptr_hash_create(pctx->pk_table_data, - NULL, NULL); - if (pctx->pk_table_data->table == NULL) { - ret = ENOMEM; - goto done; - } - } - - pk_key = talloc_asprintf(tmp_ctx, "%s", data->crypto_challenge); - if (pk_key == NULL) { - ret = ENOMEM; - goto done; - } - - pctx->pk_table_data->key = talloc_strdup(pctx->pk_table_data, pk_key); - if (pctx->pk_table_data->key == NULL) { - ret = ENOMEM; - goto done; - } - - ret = sss_ptr_hash_add(pctx->pk_table_data->table, pk_key, data, - struct pk_child_user_data); - if (ret == EEXIST) { - DEBUG(SSSDBG_TRACE_FUNC, "pk_table key [%s] already exists\n", - pk_key); - goto done; - } else if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Unable to add pk data to hash table " - "[%d]: %s\n", ret, sss_strerror(ret)); - goto done; - } - - talloc_steal(mem_ctx, pctx->pk_table_data); - pctx->pk_table_data->data = talloc_steal(mem_ctx, data); - - ret = EOK; - -done: - talloc_free(tmp_ctx); - - return ret; -} - -errno_t pam_eval_passkey_response(struct pam_ctx *pctx, - struct pam_data *pd, - struct pam_auth_req *preq, - bool *_pk_preauth_done) -{ - struct response_data *pk_resp; - struct pk_child_user_data *pk_data; - errno_t ret; - TALLOC_CTX *tmp_ctx; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - return ENOMEM; - } - - pk_resp = pd->resp_list; - - while (pk_resp != NULL) { - switch (pk_resp->type) { - case SSS_PAM_PASSKEY_KRB_INFO: - if (!pctx->passkey_auth) { - /* Passkey auth is disabled. To avoid passkey prompts appearing, - * don't send SSS_PAM_PASSKEY_KRB_INFO to the client and - * add a dummy response to fallback to normal auth */ - pk_resp->do_not_send_to_client = true; - ret = pam_add_response(pd, SSS_OTP, 0, NULL); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); - goto done; - } - break; - } - ret = decode_pam_passkey_msg(tmp_ctx, pk_resp->data, pk_resp->len, &pk_data); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to decode passkey msg\n"); - ret = EIO; - goto done; - } - - ret = save_passkey_data(preq->cctx, pctx, pk_data, preq); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to save passkey msg\n"); - ret = EIO; - goto done; - } - break; - /* Passkey non-kerberos preauth has already run */ - case SSS_PAM_PASSKEY_INFO: - *_pk_preauth_done = true; - default: - break; - } - pk_resp = pk_resp->next; - } - - ret = EOK; -done: - talloc_free(tmp_ctx); - - return ret; -} - - -static void -pam_passkey_auth_done(int child_status, - struct tevent_signal *sige, - void *pvt) -{ - struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); - - struct pam_passkey_auth_send_state *state = - tevent_req_data(req, struct pam_passkey_auth_send_state); - state->child_status = WEXITSTATUS(child_status); - if (WIFEXITED(child_status)) { - if (WEXITSTATUS(child_status) != 0) { - DEBUG(SSSDBG_OP_FAILURE, - PASSKEY_CHILD_PATH " failed with status [%d]. Check passkey_child" - " logs for more information.\n", - WEXITSTATUS(child_status)); - tevent_req_error(req, ERR_PASSKEY_CHILD); - return; - } - } else if (WIFSIGNALED(child_status)) { - DEBUG(SSSDBG_OP_FAILURE, - PASSKEY_CHILD_PATH " was terminated by signal [%d]. Check passkey_child" - " logs for more information.\n", - WTERMSIG(child_status)); - tevent_req_error(req, ECHILD); - return; - } - - DEBUG(SSSDBG_TRACE_FUNC, "passkey data is valid. Mark done\n"); - - tevent_req_done(req); - return; -} - -bool may_do_passkey_auth(struct pam_ctx *pctx, - struct pam_data *pd) -{ -#ifndef BUILD_PASSKEY - DEBUG(SSSDBG_TRACE_FUNC, "Passkey auth not possible, SSSD built without passkey support!\n"); - return false; -#else - if (!pctx->passkey_auth) { - return false; - } - - if (pd->cmd != SSS_PAM_PREAUTH && - pd->cmd != SSS_PAM_AUTHENTICATE && - pd->cmd != SSS_PAM_PASSKEY_PREAUTH) { - return false; - } - - if (pd->service == NULL || *pd->service == '\0') { - return false; - } - - return true; -#endif /* BUILD_PASSKEY */ -} +/* + SSSD + + PAM Responder - passkey related requests + + Copyright (C) Justin Stephenson 2022 + + 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 . +*/ + +#include +#include "util/child_common.h" +#include "util/authtok.h" +#include "db/sysdb.h" +#include "db/sysdb_passkey_user_verification.h" +#include "responder/pam/pamsrv.h" + +#include "responder/pam/pamsrv_passkey.h" + +struct pam_passkey_verification_enum_str { + enum passkey_user_verification verification; + const char *option; +}; + +struct pam_passkey_table_data { + hash_table_t *table; + char *key; + struct pk_child_user_data *data; +}; + +struct pam_passkey_verification_enum_str pam_passkey_verification_enum_str[] = { + { PAM_PASSKEY_VERIFICATION_ON, "on" }, + { PAM_PASSKEY_VERIFICATION_OFF, "off" }, + { PAM_PASSKEY_VERIFICATION_OMIT, "unset" }, + { PAM_PASSKEY_VERIFICATION_INVALID, NULL } +}; + +#define PASSKEY_PREFIX "passkey:" +#define USER_VERIFICATION "user_verification=" +#define USER_VERIFICATION_LEN (sizeof(USER_VERIFICATION) -1) + +const char *pam_passkey_verification_enum_to_string(enum passkey_user_verification verification) +{ + size_t c; + + for (c = 0 ; pam_passkey_verification_enum_str[c].option != NULL; c++) { + if (pam_passkey_verification_enum_str[c].verification == verification) { + return pam_passkey_verification_enum_str[c].option; + } + } + + return "(NULL)"; +} + +struct passkey_ctx { + struct pam_ctx *pam_ctx; + struct tevent_context *ev; + struct pam_data *pd; + struct pam_auth_req *preq; +}; + +void pam_forwarder_passkey_cb(struct tevent_req *req); + +void pam_forwarder_get_devinfo_cb(struct tevent_req *req); +void pam_passkey_get_devinfo(struct tevent_req *req); + +errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, + struct pk_child_user_data *pk_data, + enum passkey_auth_action auth_action, + char **_result_kh, + char **_result_ph); + +struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct passkey_ctx *pctx); +void pam_passkey_get_user_done(struct tevent_req *req); +void pam_passkey_get_mapping_done(struct tevent_req *req); +errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req_result **_result); + +struct passkey_get_mapping_state { + struct pam_data *pd; + struct cache_req_result *result; +}; + +void passkey_kerberos_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + errno_t ret = EOK; + int child_status; + + ret = pam_passkey_auth_recv(req, &child_status); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); + + pam_check_user_search(preq); + +done: + pam_check_user_done(preq, ret); +} + +errno_t passkey_kerberos(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq) +{ + errno_t ret; + const char *prompt; + const char *key; + const char *pin; + size_t pin_len; + struct pk_child_user_data *data; + struct tevent_req *req; + int timeout; + char *verify_opts; + bool debug_libfido2; + enum passkey_user_verification verification; + + ret = sss_authtok_get_passkey(preq, preq->pd->authtok, + &prompt, &key, &pin, &pin_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failure to get passkey authtok\n"); + return EIO; + } + + if (prompt == NULL || key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Passkey prompt and key are missing or invalid.\n"); + return EIO; + } + + data = sss_ptr_hash_lookup(pctx->pk_table_data->table, key, + struct pk_child_user_data); + if (data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to lookup passkey authtok\n"); + return EIO; + } + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_string(pctx->rctx->cdb, preq, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, + &verify_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Always use verification sent from passkey krb5 plugin */ + if (strcasecmp(data->user_verification, "false") == 0) { + verification = PAM_PASSKEY_VERIFICATION_OFF; + } else { + verification = PAM_PASSKEY_VERIFICATION_ON; + } + + ret = confdb_get_bool(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, + &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + req = pam_passkey_auth_send(preq->cctx, preq->cctx->ev, timeout, debug_libfido2, + verification, pd, data, + PASSKEY_KERBEROS_AUTH ); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "passkey auth send failed [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, passkey_kerberos_cb, preq); + + ret = EAGAIN; + +done: + + return ret; + +} + + +errno_t passkey_kerberos_get_devinfo(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq) +{ + errno_t ret; + const char *prompt; + const char *key; + const char *pin; + size_t pin_len; + struct pk_child_user_data *data; + struct tevent_req *req; + int timeout; + char *verify_opts; + bool debug_libfido2; + enum passkey_user_verification verification; + + if (pd->cmd != SSS_PAM_PASSKEY_PREAUTH) { + DEBUG(SSSDBG_OP_FAILURE, "passkey_kerberos_get_devinfo : invalid command\n"); + ret = PAM_SYSTEM_ERR; + goto done; + } + + ret = sss_authtok_get_passkey(preq, preq->pd->authtok, + &prompt, &key, &pin, &pin_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failure to get passkey authtok\n"); + return EIO; + } + + if (prompt == NULL || key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Passkey prompt and key are missing or invalid.\n"); + return EIO; + } + + DEBUG(SSSDBG_TRACE_FUNC, "lookup passkey auth keys: %s\nprompt: %s\n", + key ? key : "NULL", prompt ? prompt : "NULL"); + + data = sss_ptr_hash_lookup(pctx->pk_table_data->table, key, + struct pk_child_user_data); + if (data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to lookup passkey authtok\n"); + return EIO; + } + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_string(pctx->rctx->cdb, preq, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, + &verify_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Always use verification sent from passkey krb5 plugin */ + if (strcasecmp(data->user_verification, "false") == 0) { + verification = PAM_PASSKEY_VERIFICATION_OFF; + } else { + verification = PAM_PASSKEY_VERIFICATION_ON; + } + + ret = confdb_get_bool(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, + &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + req = pam_passkey_auth_send(preq->cctx, preq->cctx->ev, timeout, debug_libfido2, + verification, pd, data, + PASSKEY_GET_DEVINFO); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "passkey auth send failed [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, pam_forwarder_get_devinfo_cb, preq); + + ret = EAGAIN; + +done: + + return ret; + +} + +errno_t passkey_local(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_ctx *pam_ctx, + struct pam_auth_req *preq, + struct pam_data *pd) +{ + struct tevent_req *req; + struct passkey_ctx *pctx; + errno_t ret; + + pctx = talloc_zero(mem_ctx, struct passkey_ctx); + if (pctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pctx == NULL\n"); + return ENOMEM; + } + + pctx->pd = pd; + pctx->pam_ctx = pam_ctx; + pctx->ev = ev; + pctx->preq = preq; + + DEBUG(SSSDBG_TRACE_FUNC, "Checking for passkey authentication data\n"); + + req = pam_passkey_get_mapping_send(mem_ctx, pctx->ev, pctx); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_get_mapping_send failed.\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, pam_passkey_get_user_done, pctx); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + talloc_free(pctx); + } + + return ret; +} + +errno_t passkey_local_get_devinfo(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_ctx *pam_ctx, + struct pam_auth_req *preq, + struct pam_data *pd) +{ + struct tevent_req *req; + struct passkey_ctx *pctx; + errno_t ret; + + pctx = talloc_zero(mem_ctx, struct passkey_ctx); + if (pctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pctx == NULL\n"); + return ENOMEM; + } + + pctx->pd = pd; + pctx->pam_ctx = pam_ctx; + pctx->ev = ev; + pctx->preq = preq; + + if (pctx->pd->cmd != SSS_PAM_PASSKEY_PREAUTH) { + DEBUG(SSSDBG_OP_FAILURE, "passkey_local_get_devinfo : invalid command\n"); + ret = PAM_SYSTEM_ERR; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Checking for passkey authentication data\n"); + + req = pam_passkey_get_mapping_send(mem_ctx, pctx->ev, pctx); + if (req == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "pam_passkey_get_mapping_send failed.\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, pam_passkey_get_devinfo, pctx); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + talloc_free(pctx); + } + + return ret; +} +struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct passkey_ctx *pk_ctx) +{ + + struct passkey_get_mapping_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + int ret; + static const char *attrs[] = { SYSDB_NAME, SYSDB_USER_PASSKEY, NULL }; + + req = tevent_req_create(mem_ctx, &state, struct passkey_get_mapping_state); + if (req == NULL) { + ret = ENOMEM; + goto done; + } + + subreq = cache_req_user_by_name_attrs_send(state, pk_ctx->ev, + pk_ctx->pam_ctx->rctx, + pk_ctx->pam_ctx->rctx->ncache, 0, + pk_ctx->pd->domain, + pk_ctx->pd->user, attrs); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, pam_passkey_get_mapping_done, req); + + return req; + +done: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +void pam_passkey_get_mapping_done(struct tevent_req *subreq) +{ + struct cache_req_result *result; + struct tevent_req *req; + struct passkey_get_mapping_state *state; + + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct passkey_get_mapping_state); + + ret = cache_req_user_by_name_attrs_recv(state, subreq, &result); + state->result = result; + + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req_result **_result) +{ + struct passkey_get_mapping_state *state = NULL; + + state = tevent_req_data(req, struct passkey_get_mapping_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_result = talloc_steal(mem_ctx, state->result); + return EOK; +} + +errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, + const char *verify_opts, + enum passkey_user_verification *_user_verification) +{ + int ret; + TALLOC_CTX *tmp_ctx; + char **opts; + size_t c; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + if (verify_opts == NULL) { + ret = EOK; + goto done; + } + + ret = split_on_separator(tmp_ctx, verify_opts, ',', true, true, &opts, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d], %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + for (c = 0; opts[c] != NULL; c++) { + if (strncasecmp(opts[c], USER_VERIFICATION, USER_VERIFICATION_LEN) == 0) { + if (strcasecmp("true", &opts[c][USER_VERIFICATION_LEN]) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "user_verification set to true.\n"); + *_user_verification = PAM_PASSKEY_VERIFICATION_ON; + } else if (strcasecmp("false", &opts[c][USER_VERIFICATION_LEN]) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "user_verification set to false.\n"); + *_user_verification = PAM_PASSKEY_VERIFICATION_OFF; + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unsupported passkey verification option [%s], " \ + "skipping.\n", opts[c]); + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t passkey_local_verification(TALLOC_CTX *mem_ctx, + struct passkey_ctx *pctx, + struct confdb_ctx *cdb, + struct sysdb_ctx *sysdb, + const char *domain_name, + struct pk_child_user_data *pk_data, + enum passkey_user_verification *_user_verification, + bool *_debug_libfido2) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char *verification_from_ldap; + char *verify_opts = NULL; + bool debug_libfido2 = false; + enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_domain_get_passkey_user_verification(tmp_ctx, sysdb, domain_name, + &verification_from_ldap); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkeyUserVerification from sysdb: [%d]: %s\n", + ret, sss_strerror(ret)); + /* This is expected for AD and LDAP */ + ret = EOK; + goto done; + } + + ret = confdb_get_bool(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, + &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* If require user verification setting is set in LDAP, use it */ + if (verification_from_ldap != NULL) { + if (strcasecmp(verification_from_ldap, "true") == 0) { + verification = PAM_PASSKEY_VERIFICATION_ON; + } else if (strcasecmp(verification_from_ldap, "false") == 0) { + verification = PAM_PASSKEY_VERIFICATION_OFF; + } + DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification is being enforced from LDAP\n"); + } else { + /* No verification set in LDAP, fallback to local sssd.conf setting */ + ret = confdb_get_string(pctx->pam_ctx->rctx->cdb, tmp_ctx, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, + &verify_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + + ret = read_passkey_conf_verification(tmp_ctx, verify_opts, &verification); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to parse passkey verificaton options.\n"); + /* Continue anyway */ + } + DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification is being enforced from local configuration\n"); + } + DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification setting [%s]\n", + pam_passkey_verification_enum_to_string(verification)); + + *_user_verification = verification; + *_debug_libfido2 = debug_libfido2; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static bool mapping_is_passkey(TALLOC_CTX *tmp_ctx, + const char *mapping_str) +{ + int ret; + char **mappings; + + if (mapping_str == NULL) { + return false; + } + + ret = split_on_separator(tmp_ctx, (const char *) mapping_str, ':', true, true, + &mappings, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Incorrectly formatted passkey data [%d]: %s\n", + ret, sss_strerror(ret)); + return false; + } + + if (strcasecmp(mappings[0], "passkey") != 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Mapping data found is not passkey related\n"); + return false; + } + + return true; +} + +errno_t process_passkey_data(TALLOC_CTX *mem_ctx, + struct ldb_message *user_mesg, + const char *domain, + struct pk_child_user_data *_data) +{ + struct ldb_message_element *el; + TALLOC_CTX *tmp_ctx; + int ret; + int num_creds = 0; + char **mappings; + const char **kh_mappings; + const char **public_keys; + const char *domain_name; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ERROR("talloc_new() failed\n"); + return ENOMEM; + } + + el = ldb_msg_find_element(user_mesg, SYSDB_USER_PASSKEY); + if (el == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found\n"); + ret = ENOENT; + goto done; + } + + kh_mappings = talloc_zero_array(tmp_ctx, const char *, el->num_values + 1); + if (kh_mappings == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + public_keys = talloc_zero_array(tmp_ctx, const char *, el->num_values + 1); + if (public_keys == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + for (int i = 0; i < el->num_values; i++) { + /* This attribute may contain other mapping data unrelated to passkey. In that case + * let's skip it. For example, AD user altSecurityIdentities may store ssh public key + * or smart card mapping data */ + if ((mapping_is_passkey(tmp_ctx, (const char *)el->values[i].data)) == false) { + continue; + } + ret = split_on_separator(tmp_ctx, (const char *) el->values[i].data, ',', true, true, + &mappings, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Incorrectly formatted passkey data [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + kh_mappings[num_creds] = talloc_strdup(kh_mappings, mappings[0] + strlen(PASSKEY_PREFIX)); + if (kh_mappings[num_creds] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup key handle failed.\n"); + ret = ENOMEM; + goto done; + } + + public_keys[num_creds] = talloc_strdup(public_keys, mappings[1]); + if (public_keys[num_creds] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup public key failed.\n"); + ret = ENOMEM; + goto done; + } + + num_creds++; + } + + if (num_creds == 0) { + ret = ENOENT; + goto done; + } + + domain_name = talloc_strdup(tmp_ctx, domain); + if (domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup domain failed.\n"); + ret = ENOMEM; + goto done; + } + + _data->domain = talloc_steal(mem_ctx, domain_name); + _data->key_handles = talloc_steal(mem_ctx, kh_mappings); + _data->public_keys = talloc_steal(mem_ctx, public_keys); + _data->num_credentials = num_creds; + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + +void pam_forwarder_passkey_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + errno_t ret = EOK; + int child_status; + + ret = pam_passkey_auth_recv(req, &child_status); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + preq->pd->passkey_local_done = true; + + DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); + preq->pd->pam_status = PAM_SUCCESS; + pam_reply(preq); + + return; + +done: + pam_check_user_done(preq, ret); +} + +#define PASSKEY_PINUV "/var/run/passkey-pinuv" +#define PASSKEY_PINONLY "/var/run/passkey-pinonly" +#define PASSKEY_NODEV "/var/run/passkey-nodev" + +void pam_forwarder_get_devinfo_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + errno_t ret = EOK; + const char* devinfo = "errdev"; + int child_status; + + ret = pam_passkey_auth_recv(req, &child_status); + talloc_free(req); + if (ret != EOK) + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_recv for devinfo failed [%d]: %s\n", + ret, sss_strerror(ret)); + + DEBUG(SSSDBG_TRACE_FUNC, "passkey child devinfo finished with status [%d]\n", child_status); + + if (child_status != 0) { + int fd; + devinfo = "nodev"; + fd = creat(PASSKEY_NODEV, 0000); + close(fd); + } else { + (void)remove (PASSKEY_NODEV); + if (access(PASSKEY_PINUV, F_OK) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "devinfo pinuv file indicator\n"); + devinfo = "pinuv"; + } else if (access(PASSKEY_PINONLY, F_OK) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "devinfo pinonly file indicator\n"); + devinfo = "pinonly"; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "devinfo invalid file indicator\n"); + devinfo = "errdev"; + } + } + + /* remove indicators + * They will be rebuild according the device capabilities + * (see passkey_child) + * during authentication request. + */ + (void)remove (PASSKEY_PINUV); + (void)remove (PASSKEY_PINONLY); + + ret = pam_add_response(preq->pd, SSS_PAM_PASSKEY_DEVINFO, strlen(devinfo) + 1, + (const uint8_t *) devinfo); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "devinfo: pam_add_response failed. [%d]: %s\n", + ret, sss_strerror(ret)); + } + + + preq->pd->pam_status = PAM_SUCCESS; + + pam_reply(preq); + + return; +} + +void pam_passkey_get_user_done(struct tevent_req *req) +{ + int ret; + struct passkey_ctx *pctx; + bool debug_libfido2 = false; + char *domain_name = NULL; + int timeout; + struct cache_req_result *result = NULL; + struct pk_child_user_data *pk_data = NULL; + enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; + + pctx = tevent_req_callback_data(req, struct passkey_ctx); + + ret = pam_passkey_get_mapping_recv(pctx, req, &result); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_name_attrs_recv failed [%d]: %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + if (result == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "cache req result == NULL\n"); + ret = ENOMEM; + goto done; + } + + pk_data = talloc_zero(pctx, struct pk_child_user_data); + if (!pk_data) { + DEBUG(SSSDBG_CRIT_FAILURE, "pk_data == NULL\n"); + ret = ENOMEM; + goto done; + } + + /* Use dns_name for AD/IPA - for LDAP fallback to domain->name */ + if (result->domain != NULL) { + domain_name = result->domain->dns_name; + if (domain_name == NULL) { + domain_name = result->domain->name; + } + } + + if (domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid or missing domain name\n"); + ret = EIO; + goto done; + } + + /* Get passkey data */ + DEBUG(SSSDBG_TRACE_ALL, "Processing passkey data\n"); + ret = process_passkey_data(pk_data, result->msgs[0], domain_name, pk_data); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "process_passkey_data failed: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* timeout */ + ret = confdb_get_int(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = passkey_local_verification(pctx, pctx, pctx->pam_ctx->rctx->cdb, + result->domain->sysdb, result->domain->dns_name, + pk_data, &verification, &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to check passkey verification [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Preauth respond with prompt_pin true or false based on user verification */ + if (pctx->pd->cmd == SSS_PAM_PREAUTH) { + const char *prompt_pin = verification == PAM_PASSKEY_VERIFICATION_OFF ? "false" : "true"; + + ret = pam_add_response(pctx->pd, SSS_PAM_PASSKEY_INFO, strlen(prompt_pin) + 1, + (const uint8_t *) prompt_pin); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed. [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + pctx->pd->pam_status = PAM_SUCCESS; + pam_reply(pctx->preq); + talloc_free(pk_data); + return; + } + + req = pam_passkey_auth_send(pctx, pctx->ev, timeout, debug_libfido2, + verification, pctx->pd, pk_data, + PASSKEY_LOCAL_AUTH); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_send failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, pam_forwarder_passkey_cb, pctx->preq); + +done: + if (pk_data != NULL) { + talloc_free(pk_data); + } + + if (ret == ENOENT) { + /* No passkey data, continue through to typical auth flow */ + DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found, skipping passkey auth\n"); + pctx->preq->passkey_data_exists = false; + pam_check_user_search(pctx->preq); + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected passkey error [%d]: %s.\n", + ret, sss_strerror(ret)); + pctx->preq->passkey_data_exists = false; + pctx->preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(pctx->preq); + } + + return; +} + +void pam_passkey_get_devinfo(struct tevent_req *req) +{ + int ret; + struct passkey_ctx *pctx; + bool debug_libfido2 = false; + char *domain_name = NULL; + int timeout; + struct cache_req_result *result = NULL; + struct pk_child_user_data *pk_data = NULL; + enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; + + DEBUG(SSSDBG_TRACE_ALL, "pam_passkey_get_devinfo... \n"); + + pctx = tevent_req_callback_data(req, struct passkey_ctx); + + + ret = pam_passkey_get_mapping_recv(pctx, req, &result); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "devinfo cache_req_user_by_name_attrs_recv failed [%d]: %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + if (result == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "idevinfo cache req result == NULL\n"); + ret = ENOMEM; + goto done; + } + + pk_data = talloc_zero(pctx, struct pk_child_user_data); + if (!pk_data) { + DEBUG(SSSDBG_CRIT_FAILURE, "devinfo pk_data == NULL\n"); + ret = ENOMEM; + goto done; + } + + /* Use dns_name for AD/IPA - for LDAP fallback to domain->name */ + if (result->domain != NULL) { + domain_name = result->domain->dns_name; + if (domain_name == NULL) { + domain_name = result->domain->name; + } + } + + if (domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "devinfo Invalid or missing domain name\n"); + ret = EIO; + goto done; + } + + /* Get passkey data */ + DEBUG(SSSDBG_TRACE_ALL, "devinfo Processing passkey data\n"); + ret = process_passkey_data(pk_data, result->msgs[0], domain_name, pk_data); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "process_passkey_data for devinfo failed: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* timeout */ + ret = confdb_get_int(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "devinfo Failed to read passkey_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = passkey_local_verification(pctx, pctx, pctx->pam_ctx->rctx->cdb, + result->domain->sysdb, result->domain->dns_name, + pk_data, &verification, &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to check passkey devinfo verification [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + req = pam_passkey_auth_send(pctx, pctx->ev, timeout, debug_libfido2, + verification, pctx->pd, pk_data, + PASSKEY_GET_DEVINFO); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_send devinfo failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, pam_forwarder_get_devinfo_cb, pctx->preq); + + ret = EOK; + +done: + if (pk_data != NULL) { + talloc_free(pk_data); + } + + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_ALL, "Unexpected passkey devinfo error [%d]: %s.\n", + ret, sss_strerror(ret)); + /* the error does not affect existence of passkey_data + * pctx->preq->passkey_data_exists = false; + */ + pctx->preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(pctx->preq); + } + + return; +} + +struct pam_passkey_auth_send_state { + struct pam_data *pd; + struct tevent_context *ev; + struct tevent_timer *timeout_handler; + struct sss_child_ctx_old *child_ctx; + struct child_io_fds *io; + const char *logfile; + const char **extra_args; + char *verify_opts; + int timeout; + int child_status; + enum passkey_auth_action auth_action; +}; + +static errno_t passkey_child_exec(struct tevent_req *req); +static void pam_passkey_auth_done(int child_status, + struct tevent_signal *sige, + void *pvt); + +static void pam_passkey_get_devinfo_done(int child_status, + struct tevent_signal *sige, + void *pvt); + +static int pin_destructor(void *ptr) +{ + uint8_t *pin = talloc_get_type(ptr, uint8_t); + if (pin == NULL) return EOK; + + sss_erase_talloc_mem_securely(pin); + + return EOK; +} + +errno_t get_passkey_child_write_buffer(TALLOC_CTX *mem_ctx, + struct pam_data *pd, + uint8_t **_buf, size_t *_len) +{ + int ret; + uint8_t *buf; + size_t len; + const char *pin = NULL; + + if (pd == NULL || pd->authtok == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing authtok.\n"); + return EINVAL; + } + + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY || + sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + ret = sss_authtok_get_passkey_pin(pd->authtok, &pin, &len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_passkey_pin failed [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (pin == NULL || len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "Missing PIN.\n"); + return EINVAL; + } + + buf = talloc_size(mem_ctx, len); + if (buf == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + talloc_set_destructor((void *) buf, pin_destructor); + + safealign_memcpy(buf, pin, len, NULL); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported authtok type [%d].\n", + sss_authtok_get_type(pd->authtok)); + return EINVAL; + } + + *_len = len; + *_buf = buf; + + return EOK; +} + +static void pam_passkey_child_read_data(struct tevent_req *subreq) +{ + uint8_t *buf; + ssize_t buf_len; + char *str; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); + int ret; + + ret = read_pipe_recv(subreq, state, &buf, &buf_len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + str = malloc(sizeof(char) * buf_len); + if (str == NULL) { + return; + } + + snprintf(str, buf_len, "%s", buf); + + sss_authtok_set_passkey_reply(state->pd->authtok, str, 0); + + free(str); + + tevent_req_done(req); + return; +} + +static void passkey_child_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); + + int ret; + + DEBUG(SSSDBG_TRACE_LIBS, "Sending passkey data complete\n"); + + ret = write_pipe_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + PIPE_FD_CLOSE(state->io->write_to_child_fd); + + if (state->auth_action == PASSKEY_KERBEROS_AUTH) { + /* Read data back from passkey child */ + subreq = read_pipe_send(state, state->ev, state->io->read_from_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "read_pipe_send failed.\n"); + return; + } + + tevent_req_set_callback(subreq, pam_passkey_child_read_data, req); + } +} + +errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, + struct pk_child_user_data *pk_data, + enum passkey_auth_action auth_action, + char **_result_kh, + char **_result_pk) +{ + errno_t ret; + char *result_kh = NULL; + char *result_pk = NULL; + + int index = pk_data->num_credentials - 1; + /* start we last credential because it is the newer one + * therefore te mos accurate */ + + if (index < 0) { + ret = EINVAL; + goto done; + } + + result_kh = talloc_strdup(mem_ctx, pk_data->key_handles[index]); + if (auth_action == PASSKEY_LOCAL_AUTH) { + result_pk = talloc_strdup(mem_ctx, pk_data->public_keys[index]); + if (result_pk == NULL) { + ret = ENOMEM; + goto done; + } + } + + for (int i = index - 1; i >= 0; i--) { + result_kh = talloc_strdup_append(result_kh, ","); + if (result_kh == NULL) { + ret = ENOMEM; + goto done; + } + + result_kh = talloc_strdup_append(result_kh, pk_data->key_handles[i]); + if (result_kh == NULL) { + ret = ENOMEM; + goto done; + } + + if (auth_action == PASSKEY_LOCAL_AUTH) { + result_pk = talloc_strdup_append(result_pk, ","); + if (result_pk == NULL) { + ret = ENOMEM; + goto done; + } + + result_pk = talloc_strdup_append(result_pk, pk_data->public_keys[i]); + if (result_kh == NULL || result_pk == NULL) { + ret = ENOMEM; + goto done; + } + } + } + + *_result_kh = result_kh; + *_result_pk = result_pk; + + ret = EOK; +done: + return ret; +} + +struct tevent_req * +pam_passkey_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int timeout, + bool debug_libfido2, + enum passkey_user_verification verification, + struct pam_data *pd, + struct pk_child_user_data *pk_data, + enum passkey_auth_action auth_action) +{ + struct tevent_req *req; + struct pam_passkey_auth_send_state *state; + size_t arg_c = 0; + char *result_kh; + char *result_pk; + int num_args; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct pam_passkey_auth_send_state); + if (req == NULL) { + return NULL; + } + + state->pd = pd; + state->ev = ev; + + state->timeout = timeout; + state->auth_action = auth_action; + state->logfile = PASSKEY_CHILD_LOG_FILE; + state->io = talloc(state, struct child_io_fds); + if (state->io == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc child fds failed.\n"); + ret = ENOMEM; + goto done; + } + state->io->write_to_child_fd = -1; + state->io->read_from_child_fd = -1; + talloc_set_destructor((void *) state->io, child_io_destructor); + + num_args = 11; + state->extra_args = talloc_zero_array(state, const char *, num_args + 1); + if (state->extra_args == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + switch (verification) { + case PAM_PASSKEY_VERIFICATION_ON: + state->extra_args[arg_c++] = "--user-verification=true"; + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification true\n"); + break; + case PAM_PASSKEY_VERIFICATION_OFF: + state->extra_args[arg_c++] = "--user-verification=false"; + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification false\n"); + break; + default: + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification unset\n"); + break; + } + + ret = pam_passkey_concatenate_keys(state, pk_data, state->auth_action, + &result_kh, &result_pk); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_concatenate keys failed - [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto done; + } + + switch (auth_action) { + + case PASSKEY_KERBEROS_AUTH: { + DEBUG(SSSDBG_TRACE_FUNC, "auth_action: PASSKEY_KERBEROS_AUTH\n"); + state->extra_args[arg_c++] = pk_data->crypto_challenge; + state->extra_args[arg_c++] = "--cryptographic-challenge"; + state->extra_args[arg_c++] = result_kh; + state->extra_args[arg_c++] = "--key-handle"; + state->extra_args[arg_c++] = pk_data->domain; + state->extra_args[arg_c++] = "--domain"; + state->extra_args[arg_c++] = "--get-assert"; + break; + } + case PASSKEY_LOCAL_AUTH: { + DEBUG(SSSDBG_TRACE_FUNC, "auth_action: PASSKEY_LOCAL_AUTH\n"); + state->extra_args[arg_c++] = result_pk; + state->extra_args[arg_c++] = "--public-key"; + state->extra_args[arg_c++] = result_kh; + state->extra_args[arg_c++] = "--key-handle"; + state->extra_args[arg_c++] = pk_data->domain; + state->extra_args[arg_c++] = "--domain"; + state->extra_args[arg_c++] = state->pd->user; + state->extra_args[arg_c++] = "--username"; + state->extra_args[arg_c++] = "--authenticate"; + break; + } + /* ADDED VALMIDO */ + case PASSKEY_GET_DEVINFO: { + DEBUG(SSSDBG_TRACE_FUNC, "auth_action: PASSKEY_GET_DEVINFO\n"); + state->extra_args[arg_c++] = result_kh; + state->extra_args[arg_c++] = "--key-handle"; + state->extra_args[arg_c++] = pk_data->domain; + state->extra_args[arg_c++] = "--domain"; + state->extra_args[arg_c++] = "--get-device-info"; + break; + } + /* END VALMIDO */ + default: { + ret = ERR_INTERNAL; + DEBUG(SSSDBG_OP_FAILURE, "auth_send invalid action]\n"); + goto done; + } + } + + ret = passkey_child_exec(req); + +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void +passkey_child_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = + talloc_get_type(pvt, struct tevent_req); + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + + DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for passkey child, " + "consider increasing passkey_child_timeout\n"); + child_handler_destroy(state->child_ctx); + state->child_ctx = NULL; + state->child_status = ETIMEDOUT; + tevent_req_error(req, ERR_PASSKEY_CHILD_TIMEOUT); +} + +static errno_t passkey_child_exec(struct tevent_req *req) +{ + struct pam_passkey_auth_send_state *state; + struct tevent_req *subreq; + int pipefd_from_child[2] = PIPE_INIT; + int pipefd_to_child[2] = PIPE_INIT; + pid_t child_pid; + uint8_t *write_buf = NULL; + size_t write_buf_len = 0; + struct timeval tv; + int ret; + + state = tevent_req_data(req, struct pam_passkey_auth_send_state); + + ret = pipe(pipefd_from_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + child_pid = fork(); + if (child_pid == 0) { /* child */ + exec_child_ex(state, pipefd_to_child, pipefd_from_child, + PASSKEY_CHILD_PATH, state->logfile, state->extra_args, + false, STDIN_FILENO, STDOUT_FILENO); + /* We should never get here */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec passkey child\n"); + goto done; + } else if (child_pid > 0) { /* parent */ + state->io->read_from_child_fd = pipefd_from_child[0]; + PIPE_FD_CLOSE(pipefd_from_child[1]); + sss_fd_nonblocking(state->io->read_from_child_fd); + + state->io->write_to_child_fd = pipefd_to_child[1]; + PIPE_FD_CLOSE(pipefd_to_child[0]); + sss_fd_nonblocking(state->io->write_to_child_fd); + + /* Set up SIGCHLD handler */ + if (state->auth_action == PASSKEY_KERBEROS_AUTH) + ret = child_handler_setup(state->ev, child_pid, NULL, req, &state->child_ctx); + else if (state->auth_action == PASSKEY_LOCAL_AUTH) + ret = child_handler_setup(state->ev, child_pid, + pam_passkey_auth_done, req, + &state->child_ctx); + else if (state->auth_action == PASSKEY_GET_DEVINFO) + ret = child_handler_setup(state->ev, child_pid, + pam_passkey_get_devinfo_done, req, + &state->child_ctx); + else { + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: internal error - invalid auth_action\n"); + ret = ERR_INTERNAL; + goto done; + } + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_PASSKEY_CHILD; + goto done; + } + + /* Set up timeout handler */ + tv = tevent_timeval_current_ofs(state->timeout, 0); + state->timeout_handler = tevent_add_timer(state->ev, req, tv, + passkey_child_timeout, req); + if (state->timeout_handler == NULL) { + ret = ERR_PASSKEY_CHILD; + goto done; + } + + if (state->auth_action != PASSKEY_GET_DEVINFO) { + if (sss_authtok_get_type(state->pd->authtok) != SSS_AUTHTOK_TYPE_EMPTY) { + /* PIN is needed */ + ret = get_passkey_child_write_buffer(state, state->pd, &write_buf, + &write_buf_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "get_passkey_child_write_buffer failed [%d]: %s.\n", + ret, sss_strerror(ret)); + goto done; + } + } + } + + if (write_buf_len != 0) { + subreq = write_pipe_send(state, state->ev, write_buf, write_buf_len, + state->io->write_to_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "write_pipe_send failed.\n"); + ret = ERR_PASSKEY_CHILD; + goto done; + } + tevent_req_set_callback(subreq, passkey_child_write_done, req); + } + /* Now either wait for the timeout to fire or the child to finish */ + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + return EAGAIN; + +done: + if (ret != EOK) { + PIPE_CLOSE(pipefd_from_child); + PIPE_CLOSE(pipefd_to_child); + } + + return ret; +} + +static void +pam_passkey_get_devinfo_done(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + state->child_status = WEXITSTATUS(child_status); + if (WIFEXITED(child_status)) { + if (WEXITSTATUS(child_status) != 0) { + DEBUG(SSSDBG_OP_FAILURE, + PASSKEY_CHILD_PATH " devinfo failed with status [%d]. Check passkey_child" + " logs for more information.\n", + WEXITSTATUS(child_status)); + tevent_req_error(req, ERR_PASSKEY_CHILD); + return; + } + } else if (WIFSIGNALED(child_status)) { + DEBUG(SSSDBG_OP_FAILURE, + PASSKEY_CHILD_PATH " devinfo was terminated by signal [%d]. Check passkey_child" + " logs for more information.\n", + WTERMSIG(child_status)); + tevent_req_error(req, ECHILD); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "devinfo data is valid. Mark done\n"); + tevent_req_done(req); + return; +} + +errno_t pam_passkey_auth_recv(struct tevent_req *req, + int *child_status) +{ + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + + *child_status = state->child_status; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +errno_t decode_pam_passkey_msg(TALLOC_CTX *mem_ctx, + uint8_t *buf, + size_t len, + struct pk_child_user_data **_data) +{ + + size_t p = 0; + size_t pctr = 0; + errno_t ret; + size_t offset; + struct pk_child_user_data *data = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + data = talloc_zero(tmp_ctx, struct pk_child_user_data); + if (data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to talloc passkey data.\n"); + ret = ENOMEM; + goto done; + } + + data->user_verification = talloc_strdup(data, (char *) &buf[p]); + if (data->user_verification == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey prompt.\n"); + ret = ENOMEM; + goto done; + } + + offset = strlen(data->user_verification) + 1; + if (offset >= len) { + DEBUG(SSSDBG_OP_FAILURE, "passkey prompt offset failure.\n"); + ret = EIO; + goto done; + } + + data->crypto_challenge = talloc_strdup(data, (char *) &buf[p + offset]); + if (data->crypto_challenge == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey challenge.\n"); + ret = ENOMEM; + goto done; + } + + offset += strlen(data->crypto_challenge) + 1; + if (offset >= len) { + DEBUG(SSSDBG_OP_FAILURE, "passkey challenge offset failure.\n"); + ret = EIO; + goto done; + } + + + data->domain = talloc_strdup(data, (char *) &buf[p] + offset); + if (data->domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey domain.\n"); + ret = ENOMEM; + goto done; + } + + offset += strlen(data->domain) + 1; + if (offset >= len) { + DEBUG(SSSDBG_OP_FAILURE, "passkey domain offset failure.\n"); + ret = EIO; + goto done; + } + + SAFEALIGN_COPY_UINT32(&data->num_credentials, &buf[p + offset], &pctr); + size_t list_sz = (size_t) data->num_credentials; + + offset += sizeof(uint32_t); + + data->key_handles = talloc_zero_array(data, const char *, list_sz); + + for (int i = 0; i < list_sz; i++) { + data->key_handles[i] = talloc_strdup(data->key_handles, (char *) &buf[p + offset]); + if (data->key_handles[i] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey list.\n"); + ret = ENOMEM; + goto done; + } + + offset += strlen(data->key_handles[i]) + 1; + } + + *_data = talloc_steal(mem_ctx, data); + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t save_passkey_data(TALLOC_CTX *mem_ctx, + struct pam_ctx *pctx, + struct pk_child_user_data *data, + struct pam_auth_req *preq) +{ + char *pk_key; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Passkey data (pk_table_data) is stolen onto client ctx, it will + * be freed when the client closes, and the sss_ptr_hash interface + * takes care of automatically removing it from the hash table then */ + pctx->pk_table_data = talloc_zero(tmp_ctx, struct pam_passkey_table_data); + if (pctx->pk_table_data == NULL) { + return ENOMEM; + } + + if (pctx->pk_table_data->table == NULL) { + pctx->pk_table_data->table = sss_ptr_hash_create(pctx->pk_table_data, + NULL, NULL); + if (pctx->pk_table_data->table == NULL) { + ret = ENOMEM; + goto done; + } + } + + pk_key = talloc_asprintf(tmp_ctx, "%s", data->crypto_challenge); + if (pk_key == NULL) { + ret = ENOMEM; + goto done; + } + + pctx->pk_table_data->key = talloc_strdup(pctx->pk_table_data, pk_key); + if (pctx->pk_table_data->key == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_ptr_hash_add(pctx->pk_table_data->table, pk_key, data, + struct pk_child_user_data); + if (ret == EEXIST) { + DEBUG(SSSDBG_TRACE_FUNC, "pk_table key [%s] already exists\n", + pk_key); + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to add pk data to hash table " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + talloc_steal(mem_ctx, pctx->pk_table_data); + pctx->pk_table_data->data = talloc_steal(mem_ctx, data); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t pam_eval_passkey_response(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq, + bool *_pk_preauth_done) +{ + struct response_data *pk_resp; + struct pk_child_user_data *pk_data; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + pk_resp = pd->resp_list; + + while (pk_resp != NULL) { + switch (pk_resp->type) { + case SSS_PAM_PASSKEY_KRB_INFO: + if (!pctx->passkey_auth) { + /* Passkey auth is disabled. To avoid passkey prompts appearing, + * don't send SSS_PAM_PASSKEY_KRB_INFO to the client and + * add a dummy response to fallback to normal auth */ + pk_resp->do_not_send_to_client = true; + ret = pam_add_response(pd, SSS_OTP, 0, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + break; + } + ret = decode_pam_passkey_msg(tmp_ctx, pk_resp->data, pk_resp->len, &pk_data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to decode passkey msg\n"); + ret = EIO; + goto done; + } + + ret = save_passkey_data(preq->cctx, pctx, pk_data, preq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to save passkey msg\n"); + ret = EIO; + goto done; + } + break; + /* Passkey non-kerberos preauth has already run */ + case SSS_PAM_PASSKEY_INFO: + *_pk_preauth_done = true; + default: + break; + } + pk_resp = pk_resp->next; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + + +static void +pam_passkey_auth_done(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + state->child_status = WEXITSTATUS(child_status); + if (WIFEXITED(child_status)) { + if (WEXITSTATUS(child_status) != 0) { + DEBUG(SSSDBG_OP_FAILURE, + PASSKEY_CHILD_PATH " failed with status [%d]. Check passkey_child" + " logs for more information.\n", + WEXITSTATUS(child_status)); + tevent_req_error(req, ERR_PASSKEY_CHILD); + return; + } + } else if (WIFSIGNALED(child_status)) { + DEBUG(SSSDBG_OP_FAILURE, + PASSKEY_CHILD_PATH " was terminated by signal [%d]. Check passkey_child" + " logs for more information.\n", + WTERMSIG(child_status)); + tevent_req_error(req, ECHILD); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "passkey data is valid. Mark done\n"); + + tevent_req_done(req); + return; +} + +bool may_do_passkey_auth(struct pam_ctx *pctx, + struct pam_data *pd) +{ +#ifndef BUILD_PASSKEY + DEBUG(SSSDBG_TRACE_FUNC, "Passkey auth not possible, SSSD built without passkey support!\n"); + return false; +#else + if (!pctx->passkey_auth) { + return false; + } + + if (pd->cmd != SSS_PAM_PREAUTH && + pd->cmd != SSS_PAM_AUTHENTICATE && + pd->cmd != SSS_PAM_PASSKEY_PREAUTH) { + return false; + } + + if (pd->service == NULL || *pd->service == '\0') { + return false; + } + + return true; +#endif /* BUILD_PASSKEY */ +} diff --git a/src/responder/pam/pamsrv_passkey.h b/src/responder/pam/pamsrv_passkey.h index dad5ea0ad3f..2182183cef4 100644 --- a/src/responder/pam/pamsrv_passkey.h +++ b/src/responder/pam/pamsrv_passkey.h @@ -1,102 +1,102 @@ -/* - Authors: - Justin Stephenson - - Copyright (C) 2022 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 . -*/ - -#ifndef __PAMSRV_PASSKEY_H__ -#define __PAMSRV_PASSKEY_H__ - -#include -#include "util/util.h" -#include "util/sss_ptr_hash.h" -#include "responder/common/responder.h" -#include "responder/common/cache_req/cache_req.h" -#include "responder/pam/pamsrv.h" -#include "lib/certmap/sss_certmap.h" - -enum passkey_user_verification { - PAM_PASSKEY_VERIFICATION_ON, - PAM_PASSKEY_VERIFICATION_OFF, - PAM_PASSKEY_VERIFICATION_OMIT, - PAM_PASSKEY_VERIFICATION_INVALID -}; - -enum passkey_auth_action { - PASSKEY_KERBEROS_AUTH, - PASSKEY_LOCAL_AUTH, - PASSKEY_GET_DEVINFO -}; - -errno_t passkey_local(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - struct pam_ctx *pam_ctx, - struct pam_auth_req *preq, - struct pam_data *pd); -errno_t passkey_kerberos(struct pam_ctx *pctx, - struct pam_data *pd, - struct pam_auth_req *preq); - -errno_t passkey_local_get_devinfo(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - struct pam_ctx *pam_ctx, - struct pam_auth_req *preq, - struct pam_data *pd); - -errno_t passkey_kerberos_get_devinfo(struct pam_ctx *pctx, - struct pam_data *pd, - struct pam_auth_req *preq); -struct pk_child_user_data { - /* Both Kerberos and non-kerberos */ - const char *domain; - size_t num_credentials; - const char *user_verification; - const char **key_handles; - /* Kerberos PA only */ - const char *crypto_challenge; - /* Non-kerberos only */ - const char *user; - const char **public_keys; -}; - -errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, - const char *verify_opts, - enum passkey_user_verification *_user_verification); - -void pam_forwarder_passkey_cb(struct tevent_req *req); -struct tevent_req *pam_passkey_auth_send(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - int timeout, - bool debug_libfido2, - enum passkey_user_verification verification, - struct pam_data *pd, - struct pk_child_user_data *pk_data, - enum passkey_auth_action auth_action); -errno_t pam_passkey_auth_recv(struct tevent_req *req, - int *child_status); -errno_t pam_eval_passkey_response(struct pam_ctx *pctx, - struct pam_data *pd, - struct pam_auth_req *preq, - bool *_pk_preauth_done); -errno_t process_passkey_data(TALLOC_CTX *mem_ctx, - struct ldb_message *user_mesg, - const char *domain, - struct pk_child_user_data *_data); -bool may_do_passkey_auth(struct pam_ctx *pctx, - struct pam_data *pd); - -#endif /* __PAMSRV_PASSKEY_H__ */ +/* + Authors: + Justin Stephenson + + Copyright (C) 2022 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 . +*/ + +#ifndef __PAMSRV_PASSKEY_H__ +#define __PAMSRV_PASSKEY_H__ + +#include +#include "util/util.h" +#include "util/sss_ptr_hash.h" +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/pam/pamsrv.h" +#include "lib/certmap/sss_certmap.h" + +enum passkey_user_verification { + PAM_PASSKEY_VERIFICATION_ON, + PAM_PASSKEY_VERIFICATION_OFF, + PAM_PASSKEY_VERIFICATION_OMIT, + PAM_PASSKEY_VERIFICATION_INVALID +}; + +enum passkey_auth_action { + PASSKEY_KERBEROS_AUTH, + PASSKEY_LOCAL_AUTH, + PASSKEY_GET_DEVINFO +}; + +errno_t passkey_local(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_ctx *pam_ctx, + struct pam_auth_req *preq, + struct pam_data *pd); +errno_t passkey_kerberos(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq); + +errno_t passkey_local_get_devinfo(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_ctx *pam_ctx, + struct pam_auth_req *preq, + struct pam_data *pd); + +errno_t passkey_kerberos_get_devinfo(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq); +struct pk_child_user_data { + /* Both Kerberos and non-kerberos */ + const char *domain; + size_t num_credentials; + const char *user_verification; + const char **key_handles; + /* Kerberos PA only */ + const char *crypto_challenge; + /* Non-kerberos only */ + const char *user; + const char **public_keys; +}; + +errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, + const char *verify_opts, + enum passkey_user_verification *_user_verification); + +void pam_forwarder_passkey_cb(struct tevent_req *req); +struct tevent_req *pam_passkey_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int timeout, + bool debug_libfido2, + enum passkey_user_verification verification, + struct pam_data *pd, + struct pk_child_user_data *pk_data, + enum passkey_auth_action auth_action); +errno_t pam_passkey_auth_recv(struct tevent_req *req, + int *child_status); +errno_t pam_eval_passkey_response(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq, + bool *_pk_preauth_done); +errno_t process_passkey_data(TALLOC_CTX *mem_ctx, + struct ldb_message *user_mesg, + const char *domain, + struct pk_child_user_data *_data); +bool may_do_passkey_auth(struct pam_ctx *pctx, + struct pam_data *pd); + +#endif /* __PAMSRV_PASSKEY_H__ */ diff --git a/src/sss_client/pam_message.h b/src/sss_client/pam_message.h index 57d34b23daa..4ebbb515d55 100644 --- a/src/sss_client/pam_message.h +++ b/src/sss_client/pam_message.h @@ -1,81 +1,81 @@ -/* - Authors: - Sumit Bose - - Copyright (C) 2015 Red Hat - - PAM client - create message blob - - 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 . -*/ - -#ifndef _PAM_MESSAGE_H_ -#define _PAM_MESSAGE_H_ - -#include -#include -#include - -#include "sss_client/sss_cli.h" - -struct cert_auth_info; - -struct pam_items { - const char *pam_service; - const char *pam_user; - const char *pam_tty; - const char *pam_ruser; - const char *pam_rhost; - char *pam_authtok; - char *pam_newauthtok; - const char *pamstack_authtok; - const char *pamstack_oldauthtok; - size_t pam_service_size; - size_t pam_user_size; - size_t pam_tty_size; - size_t pam_ruser_size; - size_t pam_rhost_size; - enum sss_authtok_type pam_authtok_type; - size_t pam_authtok_size; - enum sss_authtok_type pam_newauthtok_type; - size_t pam_newauthtok_size; - pid_t cli_pid; - pid_t child_pid; - uint32_t flags; - const char *login_name; - char *domain_name; - const char *requested_domains; - size_t requested_domains_size; - char *otp_vendor; - char *otp_token_id; - char *otp_challenge; - char *oauth2_url; - char *oauth2_url_complete; - char *oauth2_pin; - char *first_factor; - char *passkey_key; - char *passkey_prompt_pin; - char *passkey_devinfo; - bool password_prompting; - - 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); - -#endif /* _PAM_MESSAGE_H_ */ +/* + Authors: + Sumit Bose + + Copyright (C) 2015 Red Hat + + PAM client - create message blob + + 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 . +*/ + +#ifndef _PAM_MESSAGE_H_ +#define _PAM_MESSAGE_H_ + +#include +#include +#include + +#include "sss_client/sss_cli.h" + +struct cert_auth_info; + +struct pam_items { + const char *pam_service; + const char *pam_user; + const char *pam_tty; + const char *pam_ruser; + const char *pam_rhost; + char *pam_authtok; + char *pam_newauthtok; + const char *pamstack_authtok; + const char *pamstack_oldauthtok; + size_t pam_service_size; + size_t pam_user_size; + size_t pam_tty_size; + size_t pam_ruser_size; + size_t pam_rhost_size; + enum sss_authtok_type pam_authtok_type; + size_t pam_authtok_size; + enum sss_authtok_type pam_newauthtok_type; + size_t pam_newauthtok_size; + pid_t cli_pid; + pid_t child_pid; + uint32_t flags; + const char *login_name; + char *domain_name; + const char *requested_domains; + size_t requested_domains_size; + char *otp_vendor; + char *otp_token_id; + char *otp_challenge; + char *oauth2_url; + char *oauth2_url_complete; + char *oauth2_pin; + char *first_factor; + char *passkey_key; + char *passkey_prompt_pin; + char *passkey_devinfo; + bool password_prompting; + + 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); + +#endif /* _PAM_MESSAGE_H_ */ diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c index d1b3f246ca1..07402a242f5 100644 --- a/src/sss_client/pam_sss.c +++ b/src/sss_client/pam_sss.c @@ -1,3438 +1,3431 @@ -/* - Authors: - Sumit Bose - - Copyright (C) 2009 Red Hat - Copyright (C) 2010, rhafer@suse.de, Novell Inc. - - 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 . -*/ - -#include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#ifdef HAVE_GDM_PAM_EXTENSIONS -#include -#endif - -#include "sss_pam_compat.h" -#include "sss_pam_macros.h" - -#include "sss_cli.h" -#include "pam_message.h" -#include "util/atomic_io.h" -#include "util/authtok-utils.h" -#include "util/dlinklist.h" -#include "util/memory_erase.h" - -#include -#define _(STRING) dgettext (PACKAGE, STRING) -#define _n(SINGULAR, PLURAL, VALUE) dngettext(PACKAGE, SINGULAR, PLURAL, VALUE) - -#define PWEXP_FLAG "pam_sss:password_expired_flag" -#define FD_DESTRUCTOR "pam_sss:fd_destructor" -#define PAM_SSS_AUTHOK_TYPE "pam_sss:authtok_type" -#define PAM_SSS_AUTHOK_SIZE "pam_sss:authtok_size" -#define PAM_SSS_AUTHOK_DATA "pam_sss:authtok_data" - -#define PW_RESET_MSG_FILENAME_TEMPLATE SSSD_CONF_DIR"/customize/%s/pam_sss_pw_reset_message.%s" -#define PW_RESET_MSG_MAX_SIZE 4096 - -#define OPT_RETRY_KEY "retry=" -#define OPT_DOMAINS_KEY "domains=" - -#define EXP_ACC_MSG _("Permission denied. ") -#define SRV_MSG _("Server message: ") -#define PASSKEY_LOCAL_AUTH_MSG _("Kerberos TGT will not be granted upon login, user experience will be affected.") -#define PASSKEY_DEFAULT_PIN_MSG _("Enter PIN:") - -#define DEBUG_MGS_LEN 1024 -#define MAX_AUTHTOK_SIZE (1024*1024) -#define CHECK_AND_RETURN_PI_STRING(s) ((s != NULL && *s != '\0')? s : "(not available)") -#define SERVICE_IS_GDM_SMARTCARD(pitem) (strcmp((pitem)->pam_service, \ - "gdm-smartcard") == 0) - -static void logger(pam_handle_t *pamh, int level, const char *fmt, ...) { - va_list ap; - - va_start(ap, fmt); - -#ifdef DEBUG - va_list apd; - char debug_msg[DEBUG_MGS_LEN]; - int ret; - va_copy(apd, ap); - - ret = vsnprintf(debug_msg, DEBUG_MGS_LEN, fmt, apd); - if (ret >= DEBUG_MGS_LEN) { - D(("the following message is truncated: %s", debug_msg)); - } else if (ret < 0) { - D(("vsnprintf failed to format debug message!")); - } else { - D((debug_msg)); - } - - va_end(apd); -#endif - - pam_vsyslog(pamh, LOG_AUTHPRIV|level, fmt, ap); - - va_end(ap); -} - -static void free_exp_data(pam_handle_t *pamh, void *ptr, int err) -{ - free(ptr); -} - -static void close_fd(pam_handle_t *pamh, void *ptr, int err) -{ -#ifdef PAM_DATA_REPLACE - if (err & PAM_DATA_REPLACE) { - /* Nothing to do */ - return; - } -#endif /* PAM_DATA_REPLACE */ - - D(("Closing the fd")); - - sss_pam_lock(); - sss_cli_close_socket(); - sss_pam_unlock(); -} - -struct cert_auth_info { - char *cert_user; - char *cert; - char *token_name; - char *module_name; - char *key_id; - char *label; - char *prompt_str; - char *pam_cert_user; - char *choice_list_id; - struct cert_auth_info *prev; - struct cert_auth_info *next; -}; - -static void free_cai(struct cert_auth_info *cai) -{ - if (cai != NULL) { - free(cai->cert_user); - free(cai->cert); - free(cai->token_name); - free(cai->module_name); - free(cai->key_id); - free(cai->label); - free(cai->prompt_str); - free(cai->choice_list_id); - free(cai); - } -} - -static void free_cert_list(struct cert_auth_info *list) -{ - struct cert_auth_info *cai; - struct cert_auth_info *cai_next; - - if (list != NULL) { - DLIST_FOR_EACH_SAFE(cai, cai_next, list) { - DLIST_REMOVE(list, cai); - free_cai(cai); - } - } -} - -static void overwrite_and_free_authtoks(struct pam_items *pi) -{ - if (pi->pam_authtok != NULL) { - sss_erase_mem_securely((void *)pi->pam_authtok, pi->pam_authtok_size); - free((void *)pi->pam_authtok); - pi->pam_authtok = NULL; - } - - if (pi->pam_newauthtok != NULL) { - sss_erase_mem_securely((void *)pi->pam_newauthtok, pi->pam_newauthtok_size); - free((void *)pi->pam_newauthtok); - pi->pam_newauthtok = NULL; - } - - if (pi->first_factor != NULL) { - sss_erase_mem_securely((void *)pi->first_factor, strlen(pi->first_factor)); - free((void *)pi->first_factor); - pi->first_factor = NULL; - } - - pi->pamstack_authtok = NULL; - pi->pamstack_oldauthtok = NULL; -} - -static void overwrite_and_free_pam_items(struct pam_items *pi) -{ - overwrite_and_free_authtoks(pi); - - free(pi->domain_name); - pi->domain_name = NULL; - - free(pi->otp_vendor); - pi->otp_vendor = NULL; - - free(pi->otp_token_id); - pi->otp_token_id = NULL; - - free(pi->otp_challenge); - pi->otp_challenge = NULL; - - free(pi->passkey_key); - pi->passkey_key = NULL; - - free(pi->passkey_prompt_pin); - pi->passkey_prompt_pin = NULL; - - free(pi->passkey_devinfo); - pi->passkey_devinfo = NULL; - - - 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) { - if (s1 == NULL && s2 == NULL) return 0; - if (s1 == NULL && s2 != NULL) return -1; - if (s1 != NULL && s2 == NULL) return 1; - return strcmp(s1, s2); -} - -enum { - SSS_PAM_CONV_DONE = 0, - SSS_PAM_CONV_STD, - SSS_PAM_CONV_REENTER, -}; - -static int do_pam_conversation(pam_handle_t *pamh, const int msg_style, - const char *msg, - const char *reenter_msg, - char **_answer) -{ - int ret; - int state = SSS_PAM_CONV_STD; - const struct pam_conv *conv; - const struct pam_message *mesg[1]; - struct pam_message *pam_msg; - struct pam_response *resp=NULL; - char *answer = NULL; - - if ((msg_style == PAM_TEXT_INFO || msg_style == PAM_ERROR_MSG) && - msg == NULL) return PAM_SYSTEM_ERR; - - if ((msg_style == PAM_PROMPT_ECHO_OFF || - msg_style == PAM_PROMPT_ECHO_ON) && - (msg == NULL || _answer == NULL)) return PAM_SYSTEM_ERR; - - if (msg_style == PAM_TEXT_INFO || msg_style == PAM_ERROR_MSG) { - logger(pamh, LOG_INFO, "User %s message: %s", - msg_style == PAM_TEXT_INFO ? "info" : "error", - msg); - } - - ret=pam_get_item(pamh, PAM_CONV, (const void **) &conv); - if (ret != PAM_SUCCESS) return ret; - if (conv == NULL || conv->conv == NULL) { - logger(pamh, LOG_ERR, "No conversation function"); - return PAM_SYSTEM_ERR; - } - - do { - pam_msg = malloc(sizeof(struct pam_message)); - if (pam_msg == NULL) { - D(("Malloc failed.")); - ret = PAM_SYSTEM_ERR; - goto failed; - } - - pam_msg->msg_style = msg_style; - if (state == SSS_PAM_CONV_REENTER) { - pam_msg->msg = reenter_msg; - } else { - pam_msg->msg = msg; - } - - mesg[0] = (const struct pam_message *) pam_msg; - - ret=conv->conv(1, mesg, &resp, - conv->appdata_ptr); - free(pam_msg); - if (ret != PAM_SUCCESS) { - D(("Conversation failure: %s.", pam_strerror(pamh,ret))); - goto failed; - } - - if (msg_style == PAM_PROMPT_ECHO_OFF || - msg_style == PAM_PROMPT_ECHO_ON) { - if (resp == NULL) { - D(("response expected, but resp==NULL")); - ret = PAM_SYSTEM_ERR; - goto failed; - } - - if (state == SSS_PAM_CONV_REENTER) { - if (null_strcmp(answer, resp[0].resp) != 0) { - logger(pamh, LOG_NOTICE, "Passwords do not match."); - sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); - free(resp[0].resp); - if (answer != NULL) { - sss_erase_mem_securely((void *) answer, strlen(answer)); - free(answer); - answer = NULL; - } - ret = do_pam_conversation(pamh, PAM_ERROR_MSG, - _("Passwords do not match"), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - ret = PAM_SYSTEM_ERR; - goto failed; - } - ret = PAM_CRED_ERR; - goto failed; - } - sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); - free(resp[0].resp); - } else { - if (resp[0].resp == NULL) { - D(("Empty password")); - answer = NULL; - } else { - answer = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); - sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); - free(resp[0].resp); - if(answer == NULL) { - D(("strndup failed")); - ret = PAM_BUF_ERR; - goto failed; - } - } - } - free(resp); - resp = NULL; - } - - if (reenter_msg != NULL && state == SSS_PAM_CONV_STD) { - state = SSS_PAM_CONV_REENTER; - } else { - state = SSS_PAM_CONV_DONE; - } - } while (state != SSS_PAM_CONV_DONE); - - if (_answer) *_answer = answer; - return PAM_SUCCESS; - -failed: - free(answer); - return ret; - -} - -static errno_t display_pw_reset_message(pam_handle_t *pamh, - const char *domain_name, - const char *suffix) -{ - int ret; - struct stat stat_buf; - char *msg_buf = NULL; - int fd = -1; - size_t size; - size_t total_len; - char *filename = NULL; - - if (strchr(suffix, '/') != NULL || strchr(domain_name, '/') != NULL) { - D(("Suffix [%s] or domain name [%s] contain illegal character.", suffix, - domain_name)); - return EINVAL; - } - - size = sizeof(PW_RESET_MSG_FILENAME_TEMPLATE) + strlen(domain_name) + - strlen(suffix); - filename = malloc(size); - if (filename == NULL) { - D(("malloc failed.")); - ret = ENOMEM; - goto done; - } - ret = snprintf(filename, size, PW_RESET_MSG_FILENAME_TEMPLATE, domain_name, - suffix); - if (ret < 0 || ret >= size) { - D(("snprintf failed.")); - ret = EFAULT; - goto done; - } - - fd = open(filename, O_RDONLY); - if (fd == -1) { - ret = errno; - D(("open failed [%d][%s].\n", ret, strerror(ret))); - goto done; - } - - ret = fstat(fd, &stat_buf); - if (ret == -1) { - ret = errno; - D(("fstat failed [%d][%s].", ret, strerror(ret))); - goto done; - } - - if (!S_ISREG(stat_buf.st_mode)) { - logger(pamh, LOG_ERR, - "Password reset message file is not a regular file."); - ret = EINVAL; - goto done; - } - - if (stat_buf.st_uid != 0 || stat_buf.st_gid != 0 || - (stat_buf.st_mode & ~S_IFMT) != 0644) { - logger(pamh, LOG_ERR,"Permission error, " - "file [%s] must be owned by root with permissions 0644.", - filename); - ret = EPERM; - goto done; - } - - if (stat_buf.st_size > PW_RESET_MSG_MAX_SIZE) { - logger(pamh, LOG_ERR, "Password reset message file is too large."); - ret = EFBIG; - goto done; - } - - msg_buf = malloc(stat_buf.st_size + 1); - if (msg_buf == NULL) { - D(("malloc failed.")); - ret = ENOMEM; - goto done; - } - - errno = 0; - total_len = sss_atomic_read_s(fd, msg_buf, stat_buf.st_size); - if (total_len == -1) { - ret = errno; - D(("read failed [%d][%s].", ret, strerror(ret))); - goto done; - } - - ret = close(fd); - fd = -1; - if (ret == -1) { - ret = errno; - D(("close failed [%d][%s].", ret, strerror(ret))); - } - - if (total_len != stat_buf.st_size) { - D(("read fewer bytes [%d] than expected [%d].", total_len, - stat_buf.st_size)); - ret = EIO; - goto done; - } - - msg_buf[stat_buf.st_size] = '\0'; - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, msg_buf, NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - } - -done: - if (fd != -1) { - close(fd); - } - free(msg_buf); - free(filename); - - return ret; -} - -static errno_t select_pw_reset_message(pam_handle_t *pamh, struct pam_items *pi) -{ - int ret; - char *locale; - const char *domain_name; - - domain_name = pi->domain_name; - if (domain_name == NULL || *domain_name == '\0') { - D(("Domain name is unknown.")); - return EINVAL; - } - - locale = setlocale(LC_MESSAGES, NULL); - - ret = -1; - if (locale != NULL) { - ret = display_pw_reset_message(pamh, domain_name, locale); - } - - if (ret != 0) { - ret = display_pw_reset_message(pamh, domain_name, "txt"); - } - - if (ret != 0) { - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, - _("Password reset by root is not supported."), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - } - } - - return ret; -} - -static int user_info_offline_auth(pam_handle_t *pamh, size_t buflen, - uint8_t *buf) -{ - int ret; - int64_t expire_date; - struct tm tm; - char expire_str[128]; - char user_msg[256]; - - expire_str[0] = '\0'; - - if (buflen != sizeof(uint32_t) + sizeof(int64_t)) { - D(("User info response data has the wrong size")); - return PAM_BUF_ERR; - } - - memcpy(&expire_date, buf + sizeof(uint32_t), sizeof(int64_t)); - - if (expire_date > 0) { - if (localtime_r((time_t *) &expire_date, &tm) != NULL) { - ret = strftime(expire_str, sizeof(expire_str), "%c", &tm); - if (ret == 0) { - D(("strftime failed.")); - expire_str[0] = '\0'; - } - } else { - D(("localtime_r failed")); - } - } - - ret = snprintf(user_msg, sizeof(user_msg), "%s%s%s.", - _("Authenticated with cached credentials"), - expire_str[0] ? _(", your cached password will expire at: ") : "", - expire_str[0] ? expire_str : ""); - if (ret < 0 || ret >= sizeof(user_msg)) { - D(("snprintf failed.")); - return PAM_SYSTEM_ERR; - } - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_grace_login(pam_handle_t *pamh, - size_t buflen, - uint8_t *buf) -{ - int ret; - uint32_t grace; - char user_msg[256]; - - if (buflen != 2* sizeof(uint32_t)) { - D(("User info response data has the wrong size")); - return PAM_BUF_ERR; - } - memcpy(&grace, buf + sizeof(uint32_t), sizeof(uint32_t)); - ret = snprintf(user_msg, sizeof(user_msg), - _("Your password has expired. " - "You have %1$d grace login(s) remaining."), - grace); - if (ret < 0 || ret >= sizeof(user_msg)) { - D(("snprintf failed.")); - return PAM_SYSTEM_ERR; - } - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); - - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -#define MINSEC 60 -#define HOURSEC (60*MINSEC) -#define DAYSEC (24*HOURSEC) -static int user_info_expire_warn(pam_handle_t *pamh, - size_t buflen, - uint8_t *buf) -{ - int ret; - uint32_t expire; - char user_msg[256]; - const char* unit; - - if (buflen != 2* sizeof(uint32_t)) { - D(("User info response data has the wrong size")); - return PAM_BUF_ERR; - } - memcpy(&expire, buf + sizeof(uint32_t), sizeof(uint32_t)); - /* expire == 0 indicates the password expired */ - if (expire != 0) { - if (expire >= DAYSEC) { - expire /= DAYSEC; - unit = _n("day", "days", expire); - } else if (expire >= HOURSEC) { - expire /= HOURSEC; - unit = _n("hour", "hours", expire); - } else if (expire >= MINSEC) { - expire /= MINSEC; - unit = _n("minute", "minutes", expire); - } else { - unit = _n("second", "seconds", expire); - } - - ret = snprintf(user_msg, sizeof(user_msg), - _("Your password will expire in %1$d %2$s."), expire, unit); - } else { - ret = snprintf(user_msg, sizeof(user_msg), - _("Your password has expired.")); - } - - if (ret < 0 || ret >= sizeof(user_msg)) { - D(("snprintf failed.")); - return PAM_SYSTEM_ERR; - } - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); - - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_offline_auth_delayed(pam_handle_t *pamh, size_t buflen, - uint8_t *buf) -{ - int ret; - int64_t delayed_until; - struct tm tm; - char delay_str[128]; - char user_msg[256]; - - delay_str[0] = '\0'; - - if (buflen != sizeof(uint32_t) + sizeof(int64_t)) { - D(("User info response data has the wrong size")); - return PAM_BUF_ERR; - } - - memcpy(&delayed_until, buf + sizeof(uint32_t), sizeof(int64_t)); - - if (delayed_until <= 0) { - D(("User info response data has an invalid value")); - return PAM_BUF_ERR; - } - - if (localtime_r((time_t *) &delayed_until, &tm) != NULL) { - ret = strftime(delay_str, sizeof(delay_str), "%c", &tm); - if (ret == 0) { - D(("strftime failed.")); - delay_str[0] = '\0'; - } - } else { - D(("localtime_r failed")); - } - - ret = snprintf(user_msg, sizeof(user_msg), "%s%s.", - _("Authentication is denied until: "), - delay_str); - if (ret < 0 || ret >= sizeof(user_msg)) { - D(("snprintf failed.")); - return PAM_SYSTEM_ERR; - } - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_offline_chpass(pam_handle_t *pamh) -{ - int ret; - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, - _("System is offline, password change not possible"), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_otp_chpass(pam_handle_t *pamh) -{ - int ret; - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, - _("After changing the OTP password, you need to " - "log out and back in order to acquire a ticket"), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_pin_locked(pam_handle_t *pamh) -{ - int ret; - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, _("PIN locked"), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_no_krb_tgt(pam_handle_t *pamh) -{ - int ret; - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, - _("No Kerberos TGT granted as " - "the server does not support this method. " - "Your single-sign on(SSO) experience will " - "be affected."), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_account_expired(pam_handle_t *pamh, size_t buflen, - uint8_t *buf) -{ - int ret; - uint32_t msg_len; - char *user_msg; - size_t bufsize = 0; - - /* resp_type and length of message are expected to be in buf */ - if (buflen < 2* sizeof(uint32_t)) { - D(("User info response data is too short")); - return PAM_BUF_ERR; - } - - /* msg_len = legth of message */ - memcpy(&msg_len, buf + sizeof(uint32_t), sizeof(uint32_t)); - - if (buflen != 2* sizeof(uint32_t) + msg_len) { - D(("User info response data has the wrong size")); - return PAM_BUF_ERR; - } - - bufsize = strlen(EXP_ACC_MSG) + 1; - - if (msg_len > 0) { - bufsize += strlen(SRV_MSG) + msg_len; - } - - user_msg = (char *)malloc(sizeof(char) * bufsize); - if (!user_msg) { - D(("Out of memory.")); - return PAM_SYSTEM_ERR; - } - - ret = snprintf(user_msg, bufsize, "%s%s%.*s", - EXP_ACC_MSG, - msg_len > 0 ? SRV_MSG : "", - (int)msg_len, - msg_len > 0 ? (char *)(buf + 2 * sizeof(uint32_t)) : "" ); - if (ret < 0 || ret > bufsize) { - D(("snprintf failed.")); - - free(user_msg); - return PAM_SYSTEM_ERR; - } - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); - free(user_msg); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int user_info_chpass_error(pam_handle_t *pamh, size_t buflen, - uint8_t *buf) -{ - int ret; - uint32_t msg_len; - char *user_msg; - size_t bufsize = 0; - - if (buflen < 2* sizeof(uint32_t)) { - D(("User info response data is too short")); - return PAM_BUF_ERR; - } - - memcpy(&msg_len, buf + sizeof(uint32_t), sizeof(uint32_t)); - - if (buflen != 2* sizeof(uint32_t) + msg_len) { - D(("User info response data has the wrong size")); - return PAM_BUF_ERR; - } - - bufsize = strlen(_("Password change failed. ")) + 1; - - if (msg_len > 0) { - bufsize += strlen(_("Server message: ")) + msg_len; - } - - user_msg = (char *)malloc(sizeof(char) * bufsize); - if (!user_msg) { - D(("Out of memory.")); - return PAM_SYSTEM_ERR; - } - - ret = snprintf(user_msg, bufsize, "%s%s%.*s", - _("Password change failed. "), - msg_len > 0 ? _("Server message: ") : "", - (int)msg_len, - msg_len > 0 ? (char *)(buf + 2 * sizeof(uint32_t)) : "" ); - if (ret < 0 || ret > bufsize) { - D(("snprintf failed.")); - - free(user_msg); - return PAM_SYSTEM_ERR; - } - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); - free(user_msg); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - - return PAM_SYSTEM_ERR; - } - - return PAM_SUCCESS; -} - -static int eval_user_info_response(pam_handle_t *pamh, size_t buflen, - uint8_t *buf) -{ - int ret; - uint32_t type; - - if (buflen < sizeof(uint32_t)) { - D(("User info response data is too short")); - return PAM_BUF_ERR; - } - - memcpy(&type, buf, sizeof(uint32_t)); - - switch(type) { - case SSS_PAM_USER_INFO_OFFLINE_AUTH: - ret = user_info_offline_auth(pamh, buflen, buf); - break; - case SSS_PAM_USER_INFO_GRACE_LOGIN: - ret = user_info_grace_login(pamh, buflen, buf); - break; - case SSS_PAM_USER_INFO_EXPIRE_WARN: - ret = user_info_expire_warn(pamh, buflen, buf); - break; - case SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED: - ret = user_info_offline_auth_delayed(pamh, buflen, buf); - break; - case SSS_PAM_USER_INFO_OFFLINE_CHPASS: - ret = user_info_offline_chpass(pamh); - break; - case SSS_PAM_USER_INFO_OTP_CHPASS: - ret = user_info_otp_chpass(pamh); - break; - case SSS_PAM_USER_INFO_CHPASS_ERROR: - ret = user_info_chpass_error(pamh, buflen, buf); - break; - case SSS_PAM_USER_INFO_PIN_LOCKED: - ret = user_info_pin_locked(pamh); - break; - case SSS_PAM_USER_INFO_ACCOUNT_EXPIRED: - ret = user_info_account_expired(pamh, buflen, buf); - break; - case SSS_PAM_USER_INFO_NO_KRB_TGT: - ret = user_info_no_krb_tgt(pamh); - break; - default: - D(("Unknown user info type [%d]", type)); - ret = PAM_SYSTEM_ERR; - } - - return ret; -} - -static int parse_cert_info(struct pam_items *pi, uint8_t *buf, size_t len, - size_t *p, const char **cert_user, - const char **pam_cert_user) -{ - struct cert_auth_info *cai = NULL; - size_t offset; - int ret; - - if (buf[*p + (len - 1)] != '\0') { - D(("cert info does not end with \\0.")); - return EINVAL; - } - - cai = calloc(1, sizeof(struct cert_auth_info)); - if (cai == NULL) { - return ENOMEM; - } - - cai->cert_user = strdup((char *) &buf[*p]); - if (cai->cert_user == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - if (cert_user != NULL) { - *cert_user = cai->cert_user; - } - - offset = strlen(cai->cert_user) + 1; - if (offset >= len) { - D(("Cert message size mismatch")); - ret = EINVAL; - goto done; - } - - cai->token_name = strdup((char *) &buf[*p + offset]); - if (cai->token_name == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - - offset += strlen(cai->token_name) + 1; - if (offset >= len) { - D(("Cert message size mismatch")); - ret = EINVAL; - goto done; - } - - cai->module_name = strdup((char *) &buf[*p + offset]); - if (cai->module_name == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - - offset += strlen(cai->module_name) + 1; - if (offset >= len) { - D(("Cert message size mismatch")); - ret = EINVAL; - goto done; - } - - cai->key_id = strdup((char *) &buf[*p + offset]); - if (cai->key_id == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - - offset += strlen(cai->key_id) + 1; - if (offset >= len) { - D(("Cert message size mismatch")); - ret = EINVAL; - goto done; - } - - cai->label = strdup((char *) &buf[*p + offset]); - if (cai->label == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - - offset += strlen(cai->label) + 1; - if (offset >= len) { - D(("Cert message size mismatch")); - ret = EINVAL; - goto done; - } - - cai->prompt_str = strdup((char *) &buf[*p + offset]); - if (cai->prompt_str == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - - offset += strlen(cai->prompt_str) + 1; - if (offset >= len) { - D(("Cert message size mismatch")); - ret = EINVAL; - goto done; - } - - cai->pam_cert_user = strdup((char *) &buf[*p + offset]); - if (cai->pam_cert_user == NULL) { - D(("strdup failed")); - ret = ENOMEM; - goto done; - } - if (pam_cert_user != NULL) { - *pam_cert_user = cai->pam_cert_user; - } - - D(("cert user: [%s] token name: [%s] module: [%s] key id: [%s] " - "prompt: [%s] pam cert user: [%s]", - cai->cert_user, cai->token_name, cai->module_name, - cai->key_id, cai->prompt_str, cai->pam_cert_user)); - - DLIST_ADD(pi->cert_list, cai); - ret = 0; - -done: - if (ret != 0) { - free_cai(cai); - } - - return ret; -} - -static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf, - struct pam_items *pi) -{ - int ret; - size_t p=0; - char *env_item; - int32_t c; - int32_t type; - int32_t len; - int32_t pam_status; - size_t offset; - const char *cert_user; - const char *pam_cert_user; - - if (buflen < (2*sizeof(int32_t))) { - D(("response buffer is too small")); - return PAM_BUF_ERR; - } - - memcpy(&pam_status, buf+p, sizeof(int32_t)); - p += sizeof(int32_t); - - - memcpy(&c, buf+p, sizeof(int32_t)); - p += sizeof(int32_t); - - while(c>0) { - if (buflen < (p+2*sizeof(int32_t))) { - D(("response buffer is too small")); - return PAM_BUF_ERR; - } - - memcpy(&type, buf+p, sizeof(int32_t)); - p += sizeof(int32_t); - - memcpy(&len, buf+p, sizeof(int32_t)); - p += sizeof(int32_t); - - if (buflen < (p + len)) { - D(("response buffer is too small")); - return PAM_BUF_ERR; - } - - switch(type) { - case SSS_PAM_SYSTEM_INFO: - if (buf[p + (len -1)] != '\0') { - D(("system info does not end with \\0.")); - break; - } - logger(pamh, LOG_INFO, "system info: [%s]", &buf[p]); - break; - case SSS_PAM_DOMAIN_NAME: - if (buf[p + (len -1)] != '\0') { - D(("domain name does not end with \\0.")); - break; - } - D(("domain name: [%s]", &buf[p])); - free(pi->domain_name); - pi->domain_name = strdup((char *) &buf[p]); - if (pi->domain_name == NULL) { - D(("strdup failed")); - } - break; - case SSS_ENV_ITEM: - case SSS_PAM_ENV_ITEM: - case SSS_ALL_ENV_ITEM: - if (buf[p + (len -1)] != '\0') { - D(("env item does not end with \\0.")); - break; - } - - D(("env item: [%s]", &buf[p])); - if (type == SSS_PAM_ENV_ITEM || type == SSS_ALL_ENV_ITEM) { - ret = pam_putenv(pamh, (char *)&buf[p]); - if (ret != PAM_SUCCESS) { - D(("pam_putenv failed.")); - break; - } - } - - if (type == SSS_ENV_ITEM || type == SSS_ALL_ENV_ITEM) { - env_item = strdup((char *)&buf[p]); - if (env_item == NULL) { - D(("strdup failed")); - break; - } - ret = putenv(env_item); - if (ret == -1) { - D(("putenv failed.")); - break; - } - } - break; - case SSS_PAM_USER_INFO: - ret = eval_user_info_response(pamh, len, &buf[p]); - if (ret != PAM_SUCCESS) { - D(("eval_user_info_response failed")); - } - break; - case SSS_PAM_TEXT_MSG: - if (buf[p + (len -1)] != '\0') { - D(("system info does not end with \\0.")); - break; - } - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, (char *) &buf[p], - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - } - break; - case SSS_OTP: - D(("OTP was used, removing authtokens.")); - overwrite_and_free_authtoks(pi); - ret = pam_set_item(pamh, PAM_AUTHTOK, NULL); - if (ret != PAM_SUCCESS) { - D(("Failed to remove PAM_AUTHTOK after using otp [%s]", - pam_strerror(pamh,ret))); - } - break; - case SSS_PAM_OTP_INFO: - if (buf[p + (len - 1)] != '\0') { - D(("otp info does not end with \\0.")); - break; - } - - free(pi->otp_vendor); - pi->otp_vendor = strdup((char *) &buf[p]); - if (pi->otp_vendor == NULL) { - D(("strdup failed")); - break; - } - - offset = strlen(pi->otp_vendor) + 1; - if (offset >= len) { - D(("OTP message size mismatch")); - free(pi->otp_vendor); - pi->otp_vendor = NULL; - break; - } - free(pi->otp_token_id); - pi->otp_token_id = strdup((char *) &buf[p + offset]); - if (pi->otp_token_id == NULL) { - D(("strdup failed")); - break; - } - - offset += strlen(pi->otp_token_id) + 1; - if (offset >= len) { - D(("OTP message size mismatch")); - free(pi->otp_token_id); - pi->otp_token_id = NULL; - break; - } - free(pi->otp_challenge); - pi->otp_challenge = strdup((char *) &buf[p + offset]); - if (pi->otp_challenge == NULL) { - D(("strdup failed")); - break; - } - - break; - case SSS_PAM_CERT_INFO: - case SSS_PAM_CERT_INFO_WITH_HINT: - if (buf[p + (len - 1)] != '\0') { - D(("cert info does not end with \\0.")); - break; - } - - if (type == SSS_PAM_CERT_INFO_WITH_HINT) { - pi->user_name_hint = true; - } else { - pi->user_name_hint = false; - } - - ret = parse_cert_info(pi, buf, len, &p, &cert_user, - &pam_cert_user); - if (ret != 0) { - D(("Failed to parse cert info")); - break; - } - - if ((pi->pam_user == NULL || *(pi->pam_user) == '\0') - && *cert_user != '\0' && *pam_cert_user != '\0') { - ret = pam_set_item(pamh, PAM_USER, pam_cert_user); - if (ret != PAM_SUCCESS) { - D(("Failed to set PAM_USER during " - "Smartcard authentication [%s]", - pam_strerror(pamh, ret))); - break; - } - - pi->pam_user = cert_user; - pi->pam_user_size = strlen(pi->pam_user) + 1; - } - break; - case SSS_PASSWORD_PROMPTING: - 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; - case SSS_CHILD_KEEP_ALIVE: - memcpy(&pi->child_pid, &buf[p], len); - break; - case SSS_PAM_OAUTH2_INFO: - if (buf[p + (len - 1)] != '\0') { - D(("oauth2 info does not end with \\0.")); - break; - } - - free(pi->oauth2_url); - pi->oauth2_url = strdup((char *) &buf[p]); - if (pi->oauth2_url == NULL) { - D(("strdup failed")); - break; - } - - offset = strlen(pi->oauth2_url) + 1; - if (offset >= len) { - D(("OAuth2 message size mismatch")); - free(pi->oauth2_url); - pi->oauth2_url = NULL; - break; - } - - free(pi->oauth2_url_complete); - pi->oauth2_url_complete = strdup((char *) &buf[p + offset]); - if (pi->oauth2_url_complete == NULL) { - D(("strdup failed")); - break; - } - - offset = offset + strlen(pi->oauth2_url_complete) + 1; - if (offset >= len) { - D(("OAuth2 message size mismatch")); - free(pi->oauth2_url_complete); - pi->oauth2_url_complete = NULL; - break; - } - - /* This field is optional. */ - if (pi->oauth2_url_complete[0] == '\0') { - free(pi->oauth2_url_complete); - pi->oauth2_url_complete = NULL; - } - - free(pi->oauth2_pin); - pi->oauth2_pin = strdup((char *) &buf[p + offset]); - if (pi->oauth2_pin == NULL) { - D(("strdup failed")); - break; - } - - break; - case SSS_PAM_PASSKEY_KRB_INFO: - free(pi->passkey_prompt_pin); - pi->passkey_prompt_pin = strdup((char *) &buf[p]); - if (pi->passkey_prompt_pin == NULL) { - D(("strdup failed")); - break; - } - - offset = strlen(pi->passkey_prompt_pin) + 1; - if (offset >= len) { - D(("Passkey message size mismatch")); - free(pi->passkey_prompt_pin); - pi->passkey_prompt_pin = NULL; - break; - } - - free(pi->passkey_key); - pi->passkey_key = strdup((char *) &buf[p + offset]); - if (pi->passkey_key == NULL) { - D(("strdup failed")); - break; - } - /* devinfo shall be retrieved according key handle(s) - */ - free (pi->passkey_devinfo); - pi->passkey_devinfo = NULL; - - break; - case SSS_PAM_PASSKEY_INFO: - if (buf[p + (len - 1)] != '\0') { - D(("passkey info does not end with \\0.")); - break; - } - - free(pi->passkey_prompt_pin); - pi->passkey_prompt_pin = strdup((char *) &buf[p]); - if (pi->passkey_prompt_pin == NULL) { - D(("strdup failed")); - break; - } - /* devinfo shall be retrieved according key handle(s) - */ - free (pi->passkey_devinfo); - pi->passkey_devinfo = NULL; - - break; - case SSS_PAM_PASSKEY_DEVINFO: - if (buf[p + (len - 1)] != '\0') { - D(("passkey devinfo does not end with \\0.")); - break; - } - - free(pi->passkey_devinfo); - pi->passkey_devinfo = strdup((char *) &buf[p]); - if (pi->passkey_devinfo == NULL) { - D(("strdup failed")); - break; - } - break; - default: - D(("Unknown response type [%d]", type)); - } - p += len; - - --c; - } - - return PAM_SUCCESS; -} - -bool is_string_empty_or_whitespace(const char *str) -{ - int i; - - if (str == NULL) { - return true; - } - - for (i = 0; str[i] != '\0'; i++) { - if (!isspace(str[i])) { - return false; - } - } - - return true; -} - -static int get_pam_items(pam_handle_t *pamh, uint32_t flags, - struct pam_items *pi) -{ - int ret; - - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; - pi->pam_authtok = NULL; - pi->pam_authtok_size = 0; - pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_EMPTY; - pi->pam_newauthtok = NULL; - pi->pam_newauthtok_size = 0; - pi->first_factor = NULL; - - ret = pam_get_item(pamh, PAM_SERVICE, (const void **) &(pi->pam_service)); - if (ret != PAM_SUCCESS) return ret; - if (pi->pam_service == NULL) pi->pam_service=""; - pi->pam_service_size=strlen(pi->pam_service)+1; - - ret = pam_get_item(pamh, PAM_USER, (const void **) &(pi->pam_user)); - if (ret == PAM_PERM_DENIED && (flags & PAM_CLI_FLAGS_ALLOW_MISSING_NAME)) { - pi->pam_user = ""; - ret = PAM_SUCCESS; - } - if (ret != PAM_SUCCESS) return ret; - if (flags & PAM_CLI_FLAGS_ALLOW_MISSING_NAME) { - if (is_string_empty_or_whitespace(pi->pam_user)) { - pi->pam_user = ""; - } - } - if (pi->pam_user == NULL) { - D(("No user found, aborting.")); - return PAM_BAD_ITEM; - } - if (strcmp(pi->pam_user, "root") == 0) { - D(("pam_sss will not handle root.")); - return PAM_USER_UNKNOWN; - } - pi->pam_user_size=strlen(pi->pam_user)+1; - - - ret = pam_get_item(pamh, PAM_TTY, (const void **) &(pi->pam_tty)); - if (ret != PAM_SUCCESS) return ret; - if (pi->pam_tty == NULL) pi->pam_tty=""; - pi->pam_tty_size=strlen(pi->pam_tty)+1; - - ret = pam_get_item(pamh, PAM_RUSER, (const void **) &(pi->pam_ruser)); - if (ret != PAM_SUCCESS) return ret; - if (pi->pam_ruser == NULL) pi->pam_ruser=""; - pi->pam_ruser_size=strlen(pi->pam_ruser)+1; - - ret = pam_get_item(pamh, PAM_RHOST, (const void **) &(pi->pam_rhost)); - if (ret != PAM_SUCCESS) return ret; - if (pi->pam_rhost == NULL) pi->pam_rhost=""; - pi->pam_rhost_size=strlen(pi->pam_rhost)+1; - - ret = pam_get_item(pamh, PAM_AUTHTOK, - (const void **) &(pi->pamstack_authtok)); - if (ret != PAM_SUCCESS) return ret; - if (pi->pamstack_authtok == NULL) pi->pamstack_authtok=""; - - ret = pam_get_item(pamh, PAM_OLDAUTHTOK, - (const void **) &(pi->pamstack_oldauthtok)); - if (ret != PAM_SUCCESS) return ret; - if (pi->pamstack_oldauthtok == NULL) pi->pamstack_oldauthtok=""; - - pi->cli_pid = getpid(); - - pi->login_name = pam_modutil_getlogin(pamh); - if (pi->login_name == NULL) pi->login_name=""; - - pi->domain_name = NULL; - - if (pi->requested_domains == NULL) pi->requested_domains = ""; - pi->requested_domains_size = strlen(pi->requested_domains) + 1; - - pi->otp_vendor = NULL; - pi->otp_token_id = NULL; - pi->otp_challenge = NULL; - pi->password_prompting = false; - - pi->cert_list = NULL; - pi->selected_cert = NULL; - - pi->pc = NULL; - - pi->flags = flags; - - return PAM_SUCCESS; -} - -static void print_pam_items(struct pam_items *pi) -{ - if (pi == NULL) return; - - D(("Service: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_service))); - D(("User: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_user))); - D(("Tty: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_tty))); - D(("Ruser: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_ruser))); - D(("Rhost: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_rhost))); - D(("Pamstack_Authtok: %s", - CHECK_AND_RETURN_PI_STRING(pi->pamstack_authtok))); - D(("Pamstack_Oldauthtok: %s", - CHECK_AND_RETURN_PI_STRING(pi->pamstack_oldauthtok))); - D(("Authtok: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_authtok))); - D(("Newauthtok: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_newauthtok))); - D(("Cli_PID: %d", pi->cli_pid)); - D(("Child_PID: %d", pi->child_pid)); - D(("Requested domains: %s", pi->requested_domains)); - D(("Flags: %d", pi->flags)); -} - -static int send_and_receive(pam_handle_t *pamh, struct pam_items *pi, - enum sss_cli_command task, bool quiet_mode) -{ - int ret; - int sret; - int errnop; - struct sss_cli_req_data rd; - uint8_t *buf = NULL; - uint8_t *repbuf = NULL; - size_t replen; - int pam_status = PAM_SYSTEM_ERR; - - print_pam_items(pi); - - ret = pack_message_v3(pi, &rd.len, &buf); - if (ret != 0) { - D(("pack_message failed.")); - pam_status = PAM_SYSTEM_ERR; - goto done; - } - rd.data = buf; - - errnop = 0; - ret = sss_pam_make_request(task, &rd, &repbuf, &replen, &errnop); - - sret = pam_set_data(pamh, FD_DESTRUCTOR, NULL, close_fd); - if (sret != PAM_SUCCESS) { - D(("pam_set_data failed, client might leaks fds")); - } - - if (ret != PAM_SUCCESS) { - /* If there is no PAM responder socket during the access control step - * we assume this is on purpose, i.e. PAM responder is not configured. - * PAM_USER_UNKNOWN is returned to the PAM stack to avoid unexpected - * denials. */ - if (errnop == ESSS_NO_SOCKET && task == SSS_PAM_ACCT_MGMT) { - pam_status = PAM_USER_UNKNOWN; - } else { - if (errnop != 0 && errnop != ESSS_NO_SOCKET) { - logger(pamh, LOG_ERR, "Request to sssd failed. %s", - ssscli_err2string(errnop)); - } - - pam_status = PAM_AUTHINFO_UNAVAIL; - } - goto done; - } - -/* FIXME: add an end signature */ - if (replen < (2*sizeof(int32_t))) { - D(("response not in expected format.")); - pam_status = PAM_SYSTEM_ERR; - goto done; - } - - SAFEALIGN_COPY_UINT32(&pam_status, repbuf, NULL); - ret = eval_response(pamh, replen, repbuf, pi); - if (ret != PAM_SUCCESS) { - D(("eval_response failed.")); - pam_status = ret; - goto done; - } - - switch (task) { - case SSS_PAM_AUTHENTICATE: - logger(pamh, (pam_status == PAM_SUCCESS ? LOG_INFO : LOG_NOTICE), - "authentication %s; logname=%s uid=%lu euid=%d tty=%s " - "ruser=%s rhost=%s user=%s", - pam_status == PAM_SUCCESS ? "success" : "failure", - pi->login_name, getuid(), (unsigned long) geteuid(), - pi->pam_tty, pi->pam_ruser, pi->pam_rhost, pi->pam_user); - if (pam_status != PAM_SUCCESS) { - /* don't log if quiet_mode is on and pam_status is - * User not known to the underlying authentication module - */ - if (!quiet_mode || pam_status != 10) { - logger(pamh, LOG_NOTICE, "received for user %s: %d (%s)", - pi->pam_user, pam_status, - pam_strerror(pamh,pam_status)); - } - } - break; - case SSS_PAM_CHAUTHTOK_PRELIM: - if (pam_status != PAM_SUCCESS) { - /* don't log if quiet_mode is on and pam_status is - * User not known to the underlying authentication module - */ - if (!quiet_mode || pam_status != 10) { - logger(pamh, LOG_NOTICE, - "Authentication failed for user %s: %d (%s)", - pi->pam_user, pam_status, - pam_strerror(pamh,pam_status)); - } - } - break; - case SSS_PAM_CHAUTHTOK: - if (pam_status != PAM_SUCCESS) { - logger(pamh, LOG_NOTICE, - "Password change failed for user %s: %d (%s)", - pi->pam_user, pam_status, - pam_strerror(pamh,pam_status)); - } - break; - case SSS_PAM_ACCT_MGMT: - if (pam_status != PAM_SUCCESS) { - /* don't log if quiet_mode is on and pam_status is - * User not known to the underlying authentication module - */ - if (!quiet_mode || pam_status != 10) { - logger(pamh, LOG_NOTICE, - "Access denied for user %s: %d (%s)", - pi->pam_user, pam_status, - pam_strerror(pamh,pam_status)); - } - } - break; - case SSS_PAM_OPEN_SESSION: - case SSS_PAM_SETCRED: - case SSS_PAM_CLOSE_SESSION: - case SSS_PAM_PREAUTH: - break; - default: - D(("Illegal task [%#x]", task)); - pam_status = PAM_SYSTEM_ERR; - } - -done: - if (buf != NULL ) { - sss_erase_mem_securely((void *)buf, rd.len); - free(buf); - } - free(repbuf); - - return pam_status; -} - -static int prompt_password(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); - sss_erase_mem_securely((void *)answer, strlen(answer)); - free(answer); - answer=NULL; - if (pi->pam_authtok == NULL) { - return PAM_BUF_ERR; - } - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD; - pi->pam_authtok_size=strlen(pi->pam_authtok); - } - - return PAM_SUCCESS; -} - -static int prompt_2fa(pam_handle_t *pamh, struct pam_items *pi, - bool second_factor_optional, - const char *prompt_fa1, const char *prompt_fa2) -{ - int ret; - const struct pam_conv *conv; - const struct pam_message *mesg[2] = { NULL, NULL }; - struct pam_message m[2] = { {0}, {0} }; - struct pam_response *resp = NULL; - size_t needed_size; - - ret = pam_get_item(pamh, PAM_CONV, (const void **) &conv); - if (ret != PAM_SUCCESS) { - return ret; - } - if (conv == NULL || conv->conv == NULL) { - logger(pamh, LOG_ERR, "No conversation function"); - return PAM_SYSTEM_ERR; - } - - m[0].msg_style = PAM_PROMPT_ECHO_OFF; - m[0].msg = prompt_fa1; - m[1].msg_style = PAM_PROMPT_ECHO_OFF; - m[1].msg = prompt_fa2; - - mesg[0] = (const struct pam_message *) m; - /* The following assignment might look a bit odd but is recommended in the - * pam_conv man page to make sure that the second argument of the PAM - * conversation function can be interpreted in two different ways. - * Basically it is important that both the actual struct pam_message and - * the pointers to the struct pam_message are arrays. Since the assignment - * makes clear that mesg[] and (*mesg)[] are arrays it should be kept this - * way and not be replaced by other equivalent assignments. */ - mesg[1] = & (( *mesg )[1]); - - ret = conv->conv(2, mesg, &resp, conv->appdata_ptr); - if (ret != PAM_SUCCESS) { - D(("Conversation failure: %s.", pam_strerror(pamh, ret))); - return ret; - } - - if (resp == NULL) { - D(("response expected, but resp==NULL")); - return PAM_SYSTEM_ERR; - } - - if (resp[0].resp == NULL || *(resp[0].resp) == '\0') { - D(("Missing factor.")); - ret = PAM_CRED_INSUFFICIENT; - goto done; - } - - if (resp[1].resp == NULL || *(resp[1].resp) == '\0') { - /* Missing second factor, assume first factor contains combined 2FA - * credentials if the second factor is not optional. If it is optional - * then it is assumed that the first factor contain the password. */ - pi->pam_authtok = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); - if (pi->pam_authtok == NULL) { - D(("strndup failed.")); - ret = PAM_BUF_ERR; - goto done; - } - pi->pam_authtok_size = strlen(pi->pam_authtok) + 1; - pi->pam_authtok_type = second_factor_optional - ? SSS_AUTHTOK_TYPE_PASSWORD - : SSS_AUTHTOK_TYPE_2FA_SINGLE; - } else if (pi->pam_service != NULL && strcmp(pi->pam_service, "sshd") == 0 - && strcmp(resp[0].resp, resp[1].resp) == 0) { - /* Special handling for SSH with password authentication (ssh's - * 'PasswordAuthentication' option. In this mode the ssh client - * directly prompts the user for a password and the prompts we are - * sending are ignored. Since we send two prompts ssh * will create two - * response as well with the same content. We assume that the combined - * 2FA credentials are used even if the second factor is optional - * because there is no indication about the intention of the user. As a - * result we prefer the more secure variant. */ - - pi->pam_authtok = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); - if (pi->pam_authtok == NULL) { - D(("strndup failed.")); - ret = PAM_BUF_ERR; - goto done; - } - pi->pam_authtok_size = strlen(pi->pam_authtok) + 1; - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_2FA_SINGLE; - } else { - - ret = sss_auth_pack_2fa_blob(resp[0].resp, 0, resp[1].resp, 0, NULL, 0, - &needed_size); - if (ret != EAGAIN) { - D(("sss_auth_pack_2fa_blob failed.")); - ret = PAM_BUF_ERR; - goto done; - } - - pi->pam_authtok = malloc(needed_size); - if (pi->pam_authtok == NULL) { - D(("malloc failed.")); - ret = PAM_BUF_ERR; - goto done; - } - - ret = sss_auth_pack_2fa_blob(resp[0].resp, 0, resp[1].resp, 0, - (uint8_t *) pi->pam_authtok, needed_size, - &needed_size); - if (ret != EOK) { - D(("sss_auth_pack_2fa_blob failed.")); - ret = PAM_BUF_ERR; - goto done; - } - - pi->pam_authtok_size = needed_size; - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_2FA; - pi->first_factor = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); - if (pi->first_factor == NULL) { - D(("strndup failed.")); - ret = PAM_BUF_ERR; - goto done; - } - } - - ret = PAM_SUCCESS; - -done: - if (resp != NULL) { - if (resp[0].resp != NULL) { - sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); - free(resp[0].resp); - } - if (resp[1].resp != NULL) { - sss_erase_mem_securely((void *)resp[1].resp, strlen(resp[1].resp)); - free(resp[1].resp); - } - - free(resp); - resp = NULL; - } - - 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); - sss_erase_mem_securely((void *)answer, strlen(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; -} - -static int prompt_oauth2(pam_handle_t *pamh, struct pam_items *pi) -{ - char *answer = NULL; - char *msg; - int ret; - - if (pi->oauth2_url_complete != NULL) { - ret = asprintf(&msg, _("Authenticate at %1$s and press ENTER."), - pi->oauth2_url_complete); - } else { - ret = asprintf(&msg, _("Authenticate with PIN %1$s at %2$s and press " - "ENTER."), pi->oauth2_pin, pi->oauth2_url); - } - if (ret == -1) { - return PAM_SYSTEM_ERR; - } - - ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, msg, NULL, &answer); - free(msg); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return ret; - } - - /* We don't care about answer here. We just need to notify that the - * authentication has finished. */ - free(answer); - - pi->pam_authtok = strdup(pi->oauth2_pin); - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_OAUTH2; - pi->pam_authtok_size=strlen(pi->oauth2_pin); - - return PAM_SUCCESS; -} - -static int prompt_passkey(pam_handle_t *pamh, struct pam_items *pi) -{ - int ret; - const struct pam_conv *conv; - const struct pam_message *mesg[4] = { NULL, NULL, NULL, NULL }; - struct pam_message m[4] = { {0}, {0}, {0}, {0} }; - struct pam_response *resp = NULL; - bool kerberos_preauth; - bool prompt_pin = false; - int pin_idx = 0; - int msg_idx = 0; - size_t needed_size; - - ret = pam_get_item(pamh, PAM_CONV, (const void **) &conv); - if (ret != PAM_SUCCESS) { - return ret; - } - if (conv == NULL || conv->conv == NULL) { - logger(pamh, LOG_ERR, "No conversation function"); - return PAM_SYSTEM_ERR; - } - - /* check device capabilities - */ - if ((strcasecmp(pi->passkey_devinfo, "dopin") != 0) && - (strcasecmp(pi->passkey_devinfo, "pinonly") != 0) && - (strcasecmp(pi->passkey_devinfo, "pinuv") != 0)) { - /* invalid device or no connected device - * fallback to passord - */ - return EIO; - } - kerberos_preauth = pi->passkey_key != NULL ? true : false; - if (!kerberos_preauth) { - m[msg_idx].msg_style = PAM_TEXT_INFO; - m[msg_idx].msg = PASSKEY_LOCAL_AUTH_MSG; - msg_idx++; - } - - if ( strcasecmp(pi->passkey_prompt_pin, "true") == 0) { - if ((strcasecmp(pi->passkey_devinfo, "dopin") == 0) || - (strcasecmp(pi->passkey_devinfo, "pinonly") == 0)) { - prompt_pin = true; - - m[msg_idx].msg_style = PAM_PROMPT_ECHO_OFF; - m[msg_idx].msg = PASSKEY_DEFAULT_PIN_MSG; - pin_idx = msg_idx; - msg_idx++; - } else { - /* let try UV according device verification capabilities */ - prompt_pin = false; - - /* Prompt to remind the user to perform verification (.eg fingerprint) */ - m[msg_idx].msg_style = PAM_TEXT_INFO; - m[msg_idx].msg = _("Perform User Verification on your device"); - msg_idx++; - } - } - - mesg[0] = (const struct pam_message *) m; - /* The following assignment might look a bit odd but is recommended in the - * pam_conv man page to make sure that the second argument of the PAM - * conversation function can be interpreted in two different ways. - * Basically it is important that both the actual struct pam_message and - * the pointers to the struct pam_message are arrays. Since the assignment - * makes clear that mesg[] and (*mesg)[] are arrays it should be kept this - * way and not be replaced by other equivalent assignments. */ - for (int i = 1; i < msg_idx; i++) { - mesg[i] = & (( *mesg )[i]); - } - - ret = conv->conv(msg_idx, mesg, &resp, conv->appdata_ptr); - if (ret != PAM_SUCCESS) { - D(("Conversation failure: %s.", pam_strerror(pamh, ret))); - return ret; - } - - if (kerberos_preauth) { - if (!prompt_pin) { - resp[pin_idx].resp = NULL; - /* fix passkey_child_credentials.c when NO PIN */ - resp[pin_idx].resp = strdup ("NULL"); - /* end fix */ -} - - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY_KRB; - sss_auth_passkey_calc_size(pi->passkey_prompt_pin, - pi->passkey_key, - resp[pin_idx].resp, - &needed_size); - - pi->pam_authtok = malloc(needed_size); - if (pi->pam_authtok == NULL) { - D(("malloc failed.")); - ret = PAM_BUF_ERR; - goto done; - } - - sss_auth_pack_passkey_blob((uint8_t *)pi->pam_authtok, - pi->passkey_prompt_pin, pi->passkey_key, - resp[pin_idx].resp); - - } else { - if (!prompt_pin) { - /* user verification = false, SSS_AUTHTOK_TYPE_PASSKEY will be reset to - * SSS_AUTHTOK_TYPE_NULL in PAM responder - */ - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY; - pi->pam_authtok = NULL; - pi->pam_authtok_size = 0; - /* fix passkey_child_credentials.c when NO PIN */ - pi->pam_authtok = strdup("null"); - pi->pam_authtok_size = strlen(pi->pam_authtok); - /* end fix */ - ret = PAM_SUCCESS; - goto done; - } else { - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY; - pi->pam_authtok = strdup(resp[pin_idx].resp); - needed_size = strlen(pi->pam_authtok); - } - } - - pi->pam_authtok_size = needed_size; - - /* Fallback to password auth if no PIN was entered */ - if (prompt_pin) { - if (resp[pin_idx].resp == NULL || resp[pin_idx].resp[0] == '\0') { - ret = EIO; - goto done; - } - } - - ret = PAM_SUCCESS; - -done: - if (resp != NULL) { - if (resp[pin_idx].resp != NULL) { - sss_erase_mem_securely((void *)resp[pin_idx].resp, - strlen(resp[pin_idx].resp)); - free(resp[pin_idx].resp); - } - - free(resp); - resp = NULL; - } - - return ret; -} - -static int passkey_get_current_device_info(pam_handle_t *pamh, struct pam_items *pi, - bool quiet_mode) -{ - int ret; - // const char* prompt = _("Connect your passkey device, then press ENTER."); - const char* prompt = "Connect your passkey device, then press ENTER."; - char* answer; - - int ori_flags = pi->flags; - - pi->flags |= PAM_CLI_FLAGS_REQUIRE_PASSKEY_CACHED_DEVINFO; - - ret = send_and_receive(pamh, pi, SSS_PAM_PASSKEY_PREAUTH, quiet_mode); - if (ret != PAM_SUCCESS) { - D(("send_and_receive returned [%d] (ignored) during passkey_preauth", ret)); - ret = PAM_SUCCESS; - } - pi->flags = ori_flags; - - if (pi->passkey_devinfo == NULL) { - ret = PAM_SYSTEM_ERR; - goto done; - } - - if (strcasecmp(pi->passkey_devinfo, "dopin") == 0) { - /* this is an authentication retry (after a wrong UV or PIN) - * which assumes the device is connected - * the user will be prompted for a PIN - */ - goto done; - } - if (strcasecmp(pi->passkey_devinfo, "nodev") == 0) { - /* the cache indicates that no valid device has been - * connected, there we shall fallback to password authentication - */ - goto done; - } - - /* user shall confirm or reconfirm its device is ready */ - ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, prompt, NULL, &answer); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed ignored - we do inot need response but just ENTER")); - ret = PAM_SUCCESS; - } - - - if ((strcasecmp(pi->passkey_devinfo, "pinuv") == 0) || - (strcasecmp(pi->passkey_devinfo, "pinonly") == 0)) { - /* OK */ - goto done; - } - /* cache is empty or indicate invalid info - * request fresh info - */ - - if (pi->passkey_key != NULL) { - - size_t needed_size; - - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY_KRB; - sss_auth_passkey_calc_size(pi->passkey_prompt_pin, - pi->passkey_key, - "", // empty PIN but unsued - &needed_size); - - pi->pam_authtok = malloc(needed_size); - if (pi->pam_authtok == NULL) { - D(("malloc failed.")); - ret = PAM_BUF_ERR; - goto done; - } - - sss_auth_pack_passkey_blob((uint8_t *)pi->pam_authtok, - pi->passkey_prompt_pin, - pi->passkey_key, - ""); - pi->pam_authtok_size = needed_size; - - } else { - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; - pi->pam_authtok = NULL; - pi->pam_authtok_size = 0; - } - ret = send_and_receive(pamh, pi, SSS_PAM_PASSKEY_PREAUTH, quiet_mode); - if (ret != PAM_SUCCESS) { - D(("send_and_receive returned [%d] (ignored) during passkey_preauth", ret)); - ret = PAM_SUCCESS; - } - - if (pi->passkey_devinfo != NULL && - ((strcasecmp(pi->passkey_devinfo, "pinuv") == 0) || - (strcasecmp(pi->passkey_devinfo, "pinonly") == 0))) { - /* OK */ - goto done; - } - - /* inform user she/he shall fallback to password authntication and wait its ENTER - */ - { - char* _prompt; - int r = asprintf(&_prompt, "%s %s", - pi->passkey_devinfo == NULL ? "SYSTEM ERROR DEVINFO" : pi->passkey_devinfo, - " Fallback to password authentication"); - if (r == -1) { - ret = ENOMEM; - goto done; - } - - ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, _prompt, NULL, &answer); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed ignored - we doi not need response but just ENTER")); - ret = PAM_SUCCESS; - } - free (_prompt); - } - - if (pi->passkey_devinfo == NULL) { - ret = PAM_SYSTEM_ERR; - goto done; - } - - ret = PAM_SUCCESS; - - done: - return ret; -} - -#define SC_PROMPT_FMT "PIN for %s: " - -#ifndef discard_const -#define discard_const(ptr) ((void *)((uintptr_t)(ptr))) -#endif - -#define CERT_SEL_PROMPT_FMT "%s" -#define SEL_TITLE discard_const("Please select a certificate") - -static int prompt_multi_cert_gdm(pam_handle_t *pamh, struct pam_items *pi) -{ -#ifdef HAVE_GDM_PAM_EXTENSIONS - int ret; - size_t cert_count = 0; - size_t c; - const struct pam_conv *conv; - struct cert_auth_info *cai; - GdmPamExtensionChoiceListRequest *request = NULL; - GdmPamExtensionChoiceListResponse *response = NULL; - struct pam_message prompt_message; - const struct pam_message *prompt_messages[1]; - struct pam_response *reply = NULL; - char *prompt; - - if (!GDM_PAM_EXTENSION_SUPPORTED(GDM_PAM_EXTENSION_CHOICE_LIST)) { - return ENOTSUP; - } - - if (pi->cert_list == NULL) { - return EINVAL; - } - - DLIST_FOR_EACH(cai, pi->cert_list) { - cert_count++; - } - - ret = pam_get_item(pamh, PAM_CONV, (const void **)&conv); - if (ret != PAM_SUCCESS) { - ret = EIO; - return ret; - } - - request = calloc(1, GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_SIZE(cert_count)); - if (request == NULL) { - ret = ENOMEM; - goto done; - } - GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_INIT(request, SEL_TITLE, cert_count); - - c = 0; - DLIST_FOR_EACH(cai, pi->cert_list) { - ret = asprintf(&prompt, CERT_SEL_PROMPT_FMT, cai->prompt_str); - if (ret == -1) { - ret = ENOMEM; - goto done; - } - free(cai->choice_list_id); - ret = asprintf(&cai->choice_list_id, "%zu", c); - if (ret == -1) { - cai->choice_list_id = NULL; - free(prompt); - ret = ENOMEM; - goto done; - } - - request->list.items[c].key = cai->choice_list_id; - request->list.items[c++].text = prompt; - } - - GDM_PAM_EXTENSION_MESSAGE_TO_BINARY_PROMPT_MESSAGE(request, - &prompt_message); - prompt_messages[0] = &prompt_message; - - ret = conv->conv(1, prompt_messages, &reply, conv->appdata_ptr); - if (ret != PAM_SUCCESS) { - ret = EIO; - goto done; - } - - ret = EIO; - response = GDM_PAM_EXTENSION_REPLY_TO_CHOICE_LIST_RESPONSE(reply); - if (response->key == NULL) { - goto done; - } - - DLIST_FOR_EACH(cai, pi->cert_list) { - if (strcmp(response->key, cai->choice_list_id) == 0) { - pam_info(pamh, "Certificate ‘%s’ selected", cai->key_id); - pi->selected_cert = cai; - ret = 0; - break; - } - } - -done: - if (request != NULL) { - for (c = 0; c < cert_count; c++) { - free(discard_const(request->list.items[c++].text)); - } - free(request); - } - free(response); - - return ret; -#else - return ENOTSUP; -#endif -} - -#define TEXT_CERT_SEL_PROMPT_FMT "%s\n[%zu]:\n%s\n" -#define TEXT_SEL_TITLE discard_const("Please select a certificate by typing " \ - "the corresponding number\n") - -static int prompt_multi_cert(pam_handle_t *pamh, struct pam_items *pi) -{ - int ret; - size_t cert_count = 0; - size_t tries = 0; - long int resp = -1; - struct cert_auth_info *cai; - char *prompt; - char *tmp; - char *answer; - char *ep; - - /* First check if gdm extension is supported */ - ret = prompt_multi_cert_gdm(pamh, pi); - if (ret != ENOTSUP) { - return ret; - } - - if (pi->cert_list == NULL) { - return EINVAL; - } - - prompt = strdup(TEXT_SEL_TITLE); - if (prompt == NULL) { - return ENOMEM; - } - - DLIST_FOR_EACH(cai, pi->cert_list) { - cert_count++; - ret = asprintf(&tmp, TEXT_CERT_SEL_PROMPT_FMT, prompt, cert_count, - cai->prompt_str); - free(prompt); - if (ret == -1) { - return ENOMEM; - } - - prompt = tmp; - } - - do { - ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_ON, prompt, NULL, - &answer); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - break; - } - - errno = 0; - resp = strtol(answer, &ep, 10); - if (errno == 0 && *ep == '\0' && resp > 0 && resp <= cert_count) { - /* do not free answer ealier because ep is pointing to it */ - free(answer); - break; - } - free(answer); - resp = -1; - } while (++tries < 5); - free(prompt); - - pi->selected_cert = NULL; - ret = ENOENT; - if (resp > 0 && resp <= cert_count) { - cert_count = 0; - DLIST_FOR_EACH(cai, pi->cert_list) { - cert_count++; - if (resp == cert_count) { - pam_info(pamh, "Certificate ‘%s’ selected", cai->key_id); - pi->selected_cert = cai; - ret = 0; - break; - } - } - } - - return ret; -} - -#define SC_INSERT_PROMPT _("Please (re)insert (different) Smartcard") - -static int prompt_sc_pin(pam_handle_t *pamh, struct pam_items *pi) -{ - int ret; - char *answer = NULL; - char *prompt = NULL; - size_t needed_size; - const struct pam_conv *conv; - const struct pam_message *mesg[2] = { NULL, NULL }; - struct pam_message m[2] = { { 0 }, { 0 } }; - struct pam_response *resp = NULL; - struct cert_auth_info *cai = pi->selected_cert; - - if (cai == NULL && (SERVICE_IS_GDM_SMARTCARD(pi) - || (pi->flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH))) { - ret = asprintf(&prompt, SC_INSERT_PROMPT); - } else if (cai == NULL || cai->token_name == NULL - || *cai->token_name == '\0') { - return PAM_SYSTEM_ERR; - } else { - ret = asprintf(&prompt, SC_PROMPT_FMT, cai->token_name); - } - - if (ret == -1) { - D(("asprintf failed.")); - return PAM_SYSTEM_ERR; - } - - if (cai == NULL) { - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, prompt, NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("Conversation failure: %s, ignored", pam_strerror(pamh, ret))); - } - } - - if (pi->user_name_hint) { - ret = pam_get_item(pamh, PAM_CONV, (const void **)&conv); - if (ret != PAM_SUCCESS) { - free(prompt); - return ret; - } - if (conv == NULL || conv->conv == NULL) { - logger(pamh, LOG_ERR, "No conversation function"); - free(prompt); - return PAM_SYSTEM_ERR; - } - - m[0].msg_style = PAM_PROMPT_ECHO_OFF; - m[0].msg = prompt; - m[1].msg_style = PAM_PROMPT_ECHO_ON; - m[1].msg = "User name hint: "; - - mesg[0] = (const struct pam_message *)m; - /* The following assignment might look a bit odd but is recommended in the - * pam_conv man page to make sure that the second argument of the PAM - * conversation function can be interpreted in two different ways. - * Basically it is important that both the actual struct pam_message and - * the pointers to the struct pam_message are arrays. Since the assignment - * makes clear that mesg[] and (*mesg)[] are arrays it should be kept this - * way and not be replaced by other equivalent assignments. */ - mesg[1] = &((*mesg)[1]); - - ret = conv->conv(2, mesg, &resp, conv->appdata_ptr); - free(prompt); - if (ret != PAM_SUCCESS) { - D(("Conversation failure: %s.", pam_strerror(pamh, ret))); - return ret; - } - - if (resp == NULL) { - D(("response expected, but resp==NULL")); - return PAM_SYSTEM_ERR; - } - - if (resp[0].resp == NULL || *(resp[0].resp) == '\0') { - D(("Missing PIN.")); - ret = PAM_CRED_INSUFFICIENT; - goto done; - } - - answer = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); - sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); - free(resp[0].resp); - resp[0].resp = NULL; - if (answer == NULL) { - D(("strndup failed")); - ret = PAM_BUF_ERR; - goto done; - } - - if (resp[1].resp != NULL && *(resp[1].resp) != '\0') { - ret = pam_set_item(pamh, PAM_USER, resp[1].resp); - free(resp[1].resp); - resp[1].resp = NULL; - if (ret != PAM_SUCCESS) { - D(("Failed to set PAM_USER with user name hint [%s]", - pam_strerror(pamh, ret))); - goto done; - } - - ret = pam_get_item(pamh, PAM_USER, (const void **)&(pi->pam_user)); - if (ret != PAM_SUCCESS) { - D(("Failed to get PAM_USER with user name hint [%s]", - pam_strerror(pamh, ret))); - goto done; - } - - pi->pam_user_size = strlen(pi->pam_user) + 1; - } - } else { - ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, prompt, NULL, - &answer); - free(prompt); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return ret; - } - } - - if (cai == NULL) { - /* it is expected that the user just replaces the Smartcard which - * would trigger gdm to restart the PAM module, so it is not - * expected that this part of the code is reached. */ - ret = PAM_AUTHINFO_UNAVAIL; - goto done; - } - - if (answer == NULL || *answer == '\0') { - D(("Missing PIN.")); - ret = PAM_CRED_INSUFFICIENT; - goto done; - } else { - - ret = sss_auth_pack_sc_blob(answer, 0, cai->token_name, 0, - cai->module_name, 0, - cai->key_id, 0, - cai->label, 0, - NULL, 0, &needed_size); - if (ret != EAGAIN) { - D(("sss_auth_pack_sc_blob failed.")); - ret = PAM_BUF_ERR; - goto done; - } - - pi->pam_authtok = malloc(needed_size); - if (pi->pam_authtok == NULL) { - D(("malloc failed.")); - ret = PAM_BUF_ERR; - goto done; - } - - ret = sss_auth_pack_sc_blob(answer, 0, cai->token_name, 0, - cai->module_name, 0, - cai->key_id, 0, - cai->label, 0, - (uint8_t *) pi->pam_authtok, needed_size, - &needed_size); - if (ret != EOK) { - D(("sss_auth_pack_sc_blob failed.")); - free((void *)pi->pam_authtok); - ret = PAM_BUF_ERR; - goto done; - } - - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_SC_PIN; - pi->pam_authtok_size = needed_size; - } - - ret = PAM_SUCCESS; - -done: - if (answer != NULL) { - sss_erase_mem_securely((void *)answer, strlen(answer)); - free(answer); - answer=NULL; - } - - if (resp != NULL) { - if (resp[0].resp != NULL) { - sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); - free(resp[0].resp); - } - if (resp[1].resp != NULL) { - sss_erase_mem_securely((void *)resp[1].resp, strlen(resp[1].resp)); - free(resp[1].resp); - } - - free(resp); - resp = NULL; - } - - return ret; -} - -static int prompt_new_password(pam_handle_t *pamh, struct pam_items *pi) -{ - int ret; - char *answer = NULL; - - ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, - _("New Password: "), - _("Reenter new Password: "), - &answer); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - return ret; - } - if (answer == NULL) { - pi->pam_newauthtok = NULL; - pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_EMPTY; - pi->pam_newauthtok_size=0; - } else { - pi->pam_newauthtok = strdup(answer); - sss_erase_mem_securely((void *)answer, strlen(answer)); - free(answer); - answer=NULL; - if (pi->pam_newauthtok == NULL) { - return PAM_BUF_ERR; - } - pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_PASSWORD; - pi->pam_newauthtok_size=strlen(pi->pam_newauthtok); - } - - return PAM_SUCCESS; -} - -static void eval_argv(pam_handle_t *pamh, int argc, const char **argv, - uint32_t *flags, int *retries, bool *quiet_mode, - const char **domains) -{ - char *ep; - - *quiet_mode = false; - - for (; argc-- > 0; ++argv) { - if (strcmp(*argv, "forward_pass") == 0) { - *flags |= PAM_CLI_FLAGS_FORWARD_PASS; - } else if (strcmp(*argv, "use_first_pass") == 0) { - *flags |= PAM_CLI_FLAGS_USE_FIRST_PASS; - } else if (strcmp(*argv, "use_authtok") == 0) { - *flags |= PAM_CLI_FLAGS_USE_AUTHTOK; - } else if (strncmp(*argv, OPT_DOMAINS_KEY, strlen(OPT_DOMAINS_KEY)) == 0) { - if (*(*argv+strlen(OPT_DOMAINS_KEY)) == '\0') { - logger(pamh, LOG_ERR, "Missing argument to option domains."); - *domains = ""; - } else { - *domains = *argv+strlen(OPT_DOMAINS_KEY); - } - - } else if (strncmp(*argv, OPT_RETRY_KEY, strlen(OPT_RETRY_KEY)) == 0) { - if (*(*argv+6) == '\0') { - logger(pamh, LOG_ERR, "Missing argument to option retry."); - *retries = 0; - } else { - errno = 0; - *retries = strtol(*argv+6, &ep, 10); - if (errno != 0) { - D(("strtol failed [%d][%s]", errno, strerror(errno))); - *retries = 0; - } - if (*ep != '\0') { - logger(pamh, LOG_ERR, "Argument to option retry contains " - "extra characters."); - *retries = 0; - } - if (*retries < 0) { - logger(pamh, LOG_ERR, "Argument to option retry must not " - "be negative."); - *retries = 0; - } - } - } else if (strcmp(*argv, "quiet") == 0) { - *quiet_mode = true; - } else if (strcmp(*argv, "allow_chauthtok_by_root") == 0) { - *flags |= PAM_CLI_FLAGS_ALLOW_CHAUTHTOK_BY_ROOT; - } else if (strcmp(*argv, "ignore_unknown_user") == 0) { - *flags |= PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER; - } else if (strcmp(*argv, "ignore_authinfo_unavail") == 0) { - *flags |= PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL; - } else if (strcmp(*argv, "use_2fa") == 0) { - *flags |= PAM_CLI_FLAGS_USE_2FA; - } else if (strcmp(*argv, "allow_missing_name") == 0) { - *flags |= PAM_CLI_FLAGS_ALLOW_MISSING_NAME; - } else if (strcmp(*argv, "prompt_always") == 0) { - *flags |= PAM_CLI_FLAGS_PROMPT_ALWAYS; - } else if (strcmp(*argv, "try_cert_auth") == 0) { - *flags |= PAM_CLI_FLAGS_TRY_CERT_AUTH; - } else if (strcmp(*argv, "require_cert_auth") == 0) { - *flags |= PAM_CLI_FLAGS_REQUIRE_CERT_AUTH; - } else { - logger(pamh, LOG_WARNING, "unknown option: %s", *argv); - } - } - - return; -} - -static int prompt_by_config(pam_handle_t *pamh, struct pam_items *pi) -{ - size_t c; - int ret = PAM_SUCCESS; - - if (pi->pc == NULL || *pi->pc == NULL) { - return PAM_SYSTEM_ERR; - } - - 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: - if (pi->password_prompting) { - ret = prompt_2fa(pamh, pi, true, pc_get_2fa_1st_prompt(pi->pc[c]), - pc_get_2fa_2nd_prompt(pi->pc[c])); - } else { - ret = prompt_2fa(pamh, pi, false, 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_PASSKEY: - ret = prompt_passkey(pamh, pi); - /* no more supported. need devinfo. Cannot be done here - pc_get_passkey_inter_prompt(pi->pc[c]), - pc_get_passkey_touch_prompt(pi->pc[c])); - */ - break; - case PC_TYPE_SC_PIN: - ret = prompt_sc_pin(pamh, pi); - /* Todo: add extra string option */ - break; - default: - ret = PAM_SYSTEM_ERR; - } - - /* 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) -{ - int ret; - const char *pin = NULL; - - if ((flags & PAM_CLI_FLAGS_USE_FIRST_PASS) - || ( pi->pamstack_authtok != NULL - && *(pi->pamstack_authtok) != '\0' - && !(flags & PAM_CLI_FLAGS_PROMPT_ALWAYS))) { - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PAM_STACKED; - pi->pam_authtok = strdup(pi->pamstack_authtok); - if (pi->pam_authtok == NULL) { - D(("option use_first_pass set, but no password found")); - return PAM_BUF_ERR; - } - pi->pam_authtok_size = strlen(pi->pam_authtok); - } else { - if (pi->oauth2_url != NULL) { - /* Prompt config is not supported for OAuth2. */ - ret = prompt_oauth2(pamh, pi); - } else if (pi->pc != NULL) { - ret = prompt_by_config(pamh, pi); - } 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 if (SERVICE_IS_GDM_SMARTCARD(pi) - || (pi->flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH)) { - /* Use pin prompt as fallback for gdm-smartcard */ - ret = prompt_sc_pin(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, true, _("First Factor: "), - _("Second Factor (optional): ")); - } else { - ret = prompt_2fa(pamh, pi, false, _("First Factor: "), - _("Second Factor: ")); - } - } else if (pi->passkey_prompt_pin != NULL && pi->passkey_devinfo != NULL) { - ret = prompt_passkey(pamh, pi); - /* Fallback to password auth if no PIN was entered */ - if (ret == EIO) { - ret = prompt_password(pamh, pi, _("Password: ")); - if (pi->pam_authtok_size == 0) { - D(("Empty password failure")); - pi->passkey_prompt_pin = NULL; - return PAM_AUTHTOK_ERR; - } - } - } else { - ret = prompt_password(pamh, pi, _("Password: ")); - } - } - if (ret != PAM_SUCCESS) { - D(("failed to get password from user")); - return ret; - } - - if (flags & PAM_CLI_FLAGS_FORWARD_PASS) { - if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_PASSWORD - || pi->pam_authtok_type == SSS_AUTHTOK_TYPE_PAM_STACKED) { - ret = pam_set_item(pamh, PAM_AUTHTOK, pi->pam_authtok); - } else if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_SC_PIN) { - pin = sss_auth_get_pin_from_sc_blob((uint8_t *) pi->pam_authtok, - pi->pam_authtok_size); - if (pin != NULL) { - ret = pam_set_item(pamh, PAM_AUTHTOK, pin); - } else { - ret = PAM_SYSTEM_ERR; - } - } else if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_2FA - && pi->first_factor != NULL) { - ret = pam_set_item(pamh, PAM_AUTHTOK, pi->first_factor); - } else { - ret = PAM_SYSTEM_ERR; - } - if (ret != PAM_SUCCESS) { - D(("Failed to set PAM_AUTHTOK [%s], " - "authtok may not be available for other modules", - pam_strerror(pamh,ret))); - } - } - } - - return PAM_SUCCESS; -} - -static int check_authtok_data(pam_handle_t *pamh, struct pam_items *pi) -{ - int pam_status; - int *authtok_type; - size_t *authtok_size; - char *authtok_data; - - pam_status = pam_get_data(pamh, PAM_SSS_AUTHOK_TYPE, - (const void **) &authtok_type); - if (pam_status != PAM_SUCCESS) { - D(("pam_get_data failed.")); - return EIO; - } - - pam_status = pam_get_data(pamh, PAM_SSS_AUTHOK_SIZE, - (const void **) &authtok_size); - if (pam_status != PAM_SUCCESS) { - D(("pam_get_data failed.")); - return EIO; - } - - pam_status = pam_get_data(pamh, PAM_SSS_AUTHOK_DATA, - (const void **) &authtok_data); - if (pam_status != PAM_SUCCESS) { - D(("pam_get_data failed.")); - return EIO; - } - - pi->pam_authtok = malloc(*authtok_size); - if (pi->pam_authtok == NULL) { - D(("malloc failed.")); - return ENOMEM; - } - memcpy(pi->pam_authtok, authtok_data, *authtok_size); - - pi->pam_authtok_type = *authtok_type; - pi->pam_authtok_size = *authtok_size; - - return 0; -} - -static int keep_authtok_data(pam_handle_t *pamh, struct pam_items *pi) -{ - int pam_status; - int *authtok_type; - size_t *authtok_size; - char *authtok_data; - - authtok_type = malloc(sizeof(int)); - if (authtok_type == NULL) { - D(("malloc failed.")); - return ENOMEM; - } - *authtok_type = pi->pam_authtok_type; - - pam_status = pam_set_data(pamh, PAM_SSS_AUTHOK_TYPE, authtok_type, - free_exp_data); - if (pam_status != PAM_SUCCESS) { - free(authtok_type); - D(("pam_set_data failed.")); - return EIO; - } - - authtok_size = malloc(sizeof(size_t)); - if (authtok_size == NULL) { - D(("malloc failed.")); - return ENOMEM; - } - *authtok_size = pi->pam_authtok_size; - - pam_status = pam_set_data(pamh, PAM_SSS_AUTHOK_SIZE, authtok_size, - free_exp_data); - if (pam_status != PAM_SUCCESS) { - free(authtok_size); - D(("pam_set_data failed.")); - return EIO; - } - - authtok_data = malloc(pi->pam_authtok_size); - if (authtok_data == NULL) { - D(("malloc failed.")); - return ENOMEM; - } - memcpy(authtok_data, pi->pam_authtok, pi->pam_authtok_size); - - pam_status = pam_set_data(pamh, PAM_SSS_AUTHOK_DATA, authtok_data, - free_exp_data); - if (pam_status != PAM_SUCCESS) { - free(authtok_data); - D(("pam_set_data failed.")); - return EIO; - } - - return 0; -} - -static int get_authtok_for_password_change(pam_handle_t *pamh, - struct pam_items *pi, - uint32_t flags, - int pam_flags) -{ - int ret; - const int *exp_data = NULL; - ret = pam_get_data(pamh, PWEXP_FLAG, (const void **) &exp_data); - if (ret != PAM_SUCCESS) { - exp_data = NULL; - } - - if (pam_flags & PAM_PRELIM_CHECK) { - if (!(flags & PAM_CLI_FLAGS_ALLOW_CHAUTHTOK_BY_ROOT) && getuid() == 0 && !exp_data ) - return PAM_SUCCESS; - - 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, true, - _("First Factor (Current Password): "), - _("Second Factor (optional): ")); - } else { - ret = prompt_2fa(pamh, pi, false, - _("First Factor (Current Password): "), - _("Second Factor: ")); - } - } else if ((flags & PAM_CLI_FLAGS_USE_FIRST_PASS) - && check_authtok_data(pamh, pi) != 0) { - if (pi->pamstack_oldauthtok == NULL) { - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; - pi->pam_authtok = NULL; - pi->pam_authtok_size = 0; - } else { - pi->pam_authtok = strdup(pi->pamstack_oldauthtok); - if (pi->pam_authtok == NULL) { - D(("strdup failed")); - return PAM_BUF_ERR; - } - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD; - pi->pam_authtok_size = strlen(pi->pam_authtok); - } - ret = PAM_SUCCESS; - } else { - ret = prompt_password(pamh, pi, _("Current Password: ")); - } - if (ret != PAM_SUCCESS) { - D(("failed to get credentials from user")); - return ret; - } - - ret = pam_set_item(pamh, PAM_OLDAUTHTOK, pi->pam_authtok); - if (ret != PAM_SUCCESS) { - D(("Failed to set PAM_OLDAUTHTOK [%s], " - "oldauthtok may not be available", - pam_strerror(pamh,ret))); - return ret; - } - - if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_2FA) { - ret = keep_authtok_data(pamh, pi); - if (ret != 0) { - D(("Failed to store authtok data to pam handle. Password " - "change might fail.")); - } - } - - return PAM_SUCCESS; - } - - if (check_authtok_data(pamh, pi) != 0) { - if (pi->pamstack_oldauthtok == NULL) { - if (getuid() != 0) { - D(("no password found for chauthtok")); - return PAM_BUF_ERR; - } else { - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; - pi->pam_authtok = NULL; - pi->pam_authtok_size = 0; - } - } else { - pi->pam_authtok = strdup(pi->pamstack_oldauthtok); - if (pi->pam_authtok == NULL) { - D(("strdup failed")); - return PAM_BUF_ERR; - } - pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD; - pi->pam_authtok_size = strlen(pi->pam_authtok); - } - } - - if (flags & PAM_CLI_FLAGS_USE_AUTHTOK) { - pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_PASSWORD; - pi->pam_newauthtok = strdup(pi->pamstack_authtok); - if (pi->pam_newauthtok == NULL) { - D(("option use_authtok set, but no new password found")); - return PAM_BUF_ERR; - } - pi->pam_newauthtok_size = strlen(pi->pam_newauthtok); - } else { - ret = prompt_new_password(pamh, pi); - if (ret != PAM_SUCCESS) { - D(("failed to get new password from user")); - return ret; - } - - if (flags & PAM_CLI_FLAGS_FORWARD_PASS) { - ret = pam_set_item(pamh, PAM_AUTHTOK, pi->pam_newauthtok); - if (ret != PAM_SUCCESS) { - D(("Failed to set PAM_AUTHTOK [%s], " - "oldauthtok may not be available", - pam_strerror(pamh,ret))); - } - } - } - - return PAM_SUCCESS; -} - -#define SC_ENTER_LABEL_FMT "Please insert smart card labeled\n %s" -#define SC_ENTER_FMT "Please insert smart card" - -static int check_login_token_name(pam_handle_t *pamh, struct pam_items *pi, - int retries, bool quiet_mode) -{ - int ret; - int pam_status; - char *login_token_name; - char *prompt = NULL; - uint32_t orig_flags = pi->flags; - - login_token_name = getenv("PKCS11_LOGIN_TOKEN_NAME"); - if (login_token_name == NULL - && !(pi->flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH)) { - return PAM_SUCCESS; - } - - if (login_token_name == NULL) { - ret = asprintf(&prompt, SC_ENTER_FMT); - } else { - ret = asprintf(&prompt, SC_ENTER_LABEL_FMT, login_token_name); - } - if (ret == -1) { - return ENOMEM; - } - - pi->flags |= PAM_CLI_FLAGS_REQUIRE_CERT_AUTH; - - /* TODO: check multiple cert case */ - while (pi->cert_list == NULL || pi->cert_list->token_name == NULL - || (login_token_name != NULL - && strcmp(login_token_name, - pi->cert_list->token_name) != 0)) { - - free_cert_list(pi->cert_list); - pi->cert_list = NULL; - if (retries < 0) { - ret = PAM_AUTHINFO_UNAVAIL; - goto done; - } - retries--; - - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, prompt, NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - goto done; - } - - pam_status = send_and_receive(pamh, pi, SSS_PAM_PREAUTH, quiet_mode); - if (pam_status != PAM_SUCCESS) { - D(("send_and_receive returned [%d] during pre-auth", pam_status)); - /* - * Since we are waiting for the right Smartcard to be inserted errors - * can be ignored here. - */ - } - } - - ret = PAM_SUCCESS; - -done: - - pi->flags = orig_flags; - free(prompt); - - return ret; -} - -static int pam_sss(enum sss_cli_command task, pam_handle_t *pamh, - int pam_flags, int argc, const char **argv) -{ - int ret; - int pam_status; - struct pam_items pi = { 0 }; - uint32_t flags = 0; - const int *exp_data; - int *pw_exp_data; - bool retry = false; - bool quiet_mode = false; - int retries = 0; - const char *domains = NULL; - - bindtextdomain(PACKAGE, LOCALEDIR); - - D(("Hello pam_sssd: %#x", task)); - - eval_argv(pamh, argc, argv, &flags, &retries, &quiet_mode, &domains); - - /* Fail all authentication on misconfigured domains= parameter. The admin - * probably wanted to restrict authentication, so it's safer to fail */ - if (domains && strcmp(domains, "") == 0) { - return PAM_SYSTEM_ERR; - } - - pi.requested_domains = domains; - - ret = get_pam_items(pamh, flags, &pi); - if (ret != PAM_SUCCESS) { - D(("get items returned error: %s", pam_strerror(pamh,ret))); - if ((flags & PAM_CLI_FLAGS_TRY_CERT_AUTH) - || (flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) ) { - return PAM_AUTHINFO_UNAVAIL; - } - if (flags & PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER && ret == PAM_USER_UNKNOWN) { - ret = PAM_IGNORE; - } - if (flags & PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL - && ret == PAM_AUTHINFO_UNAVAIL) { - ret = PAM_IGNORE; - } - return ret; - } - - do { - retry = false; - - switch(task) { - case SSS_PAM_AUTHENTICATE: - /* - * Only do preauth if - * - PAM_CLI_FLAGS_USE_FIRST_PASS is not set - * - no password is on the stack or PAM_CLI_FLAGS_PROMPT_ALWAYS is set - * - preauth indicator file exists. - */ - if ( !(flags & PAM_CLI_FLAGS_USE_FIRST_PASS) - && (pi.pam_authtok == NULL - || (flags & PAM_CLI_FLAGS_PROMPT_ALWAYS)) - && access(PAM_PREAUTH_INDICATOR, F_OK) == 0) { - - if (flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) { - /* Do not use PAM_CLI_FLAGS_REQUIRE_CERT_AUTH in the first - * SSS_PAM_PREAUTH run. In case a card is already inserted - * we do not have to prompt to insert a card. */ - pi.flags &= ~PAM_CLI_FLAGS_REQUIRE_CERT_AUTH; - pi.flags |= PAM_CLI_FLAGS_TRY_CERT_AUTH; - } - - pam_status = send_and_receive(pamh, &pi, SSS_PAM_PREAUTH, - quiet_mode); - - pi.flags = flags; - if (pam_status != PAM_SUCCESS) { - D(("send_and_receive returned [%d] during pre-auth", - pam_status)); - /* - * Since we are only interested in the result message - * and will always use password authentication - * as a fallback (except for gdm-smartcard), - * errors can be ignored here. - */ - } - } - - if (flags & PAM_CLI_FLAGS_TRY_CERT_AUTH - && pi.cert_list == NULL) { - D(("No certificates for authentication available.")); - overwrite_and_free_pam_items(&pi); - return PAM_AUTHINFO_UNAVAIL; - } - - if (SERVICE_IS_GDM_SMARTCARD(&pi) - || (flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH)) { - ret = check_login_token_name(pamh, &pi, retries, - quiet_mode); - if (ret != PAM_SUCCESS) { - D(("check_login_token_name failed.\n")); - } - } - - if (pi.passkey_prompt_pin != NULL && pi.passkey_devinfo == NULL) { - ret = passkey_get_current_device_info(pamh, &pi, quiet_mode); - if (ret != PAM_SUCCESS) { - D(("passkey_get_current_device_info failed.\n")); - overwrite_and_free_pam_items(&pi); - return ret; - } - } - - ret = get_authtok_for_authentication(pamh, &pi, flags); - if (ret != PAM_SUCCESS) { - D(("failed to get authentication token: %s", - pam_strerror(pamh, ret))); - overwrite_and_free_pam_items(&pi); - return ret; - } - break; - case SSS_PAM_CHAUTHTOK: - /* - * Even if we only want to change the (long term) password - * there are cases where more than the password is needed to - * get the needed privileges in a backend to change the - * password. - * - * E.g. with mandatory 2-factor authentication we have to ask - * not only for the current password but for the second - * factor, e.g. the one-time token value, as well. - * - * The means the preauth step has to be done here as well but - * only if - * - PAM_PRELIM_CHECK is set - * - PAM_CLI_FLAGS_USE_FIRST_PASS is not set - * - no password is on the stack or PAM_CLI_FLAGS_PROMPT_ALWAYS is set - * - preauth indicator file exists. - */ - if ( (pam_flags & PAM_PRELIM_CHECK) - && !(flags & PAM_CLI_FLAGS_USE_FIRST_PASS) - && (pi.pam_authtok == NULL - || (flags & PAM_CLI_FLAGS_PROMPT_ALWAYS)) - && access(PAM_PREAUTH_INDICATOR, F_OK) == 0) { - pam_status = send_and_receive(pamh, &pi, SSS_PAM_PREAUTH, - quiet_mode); - if (pam_status != PAM_SUCCESS) { - D(("send_and_receive returned [%d] during pre-auth", - pam_status)); - /* - * Since we are only interested in the result message - * and will always use password authentication - * as a fallback, errors can be ignored here. - */ - } - } - - ret = get_authtok_for_password_change(pamh, &pi, flags, pam_flags); - if (ret != PAM_SUCCESS) { - D(("failed to get tokens for password change: %s", - pam_strerror(pamh, ret))); - overwrite_and_free_pam_items(&pi); - return ret; - } - - if (pam_flags & PAM_PRELIM_CHECK) { - if (pi.pam_authtok_type == SSS_AUTHTOK_TYPE_2FA) { - /* We cannot validate the credentials with an OTP - * token value during PAM_PRELIM_CHECK because it - * would be invalid for the actual password change. So - * we are done. */ - - overwrite_and_free_pam_items(&pi); - return PAM_SUCCESS; - } - task = SSS_PAM_CHAUTHTOK_PRELIM; - } - break; - case SSS_PAM_ACCT_MGMT: - case SSS_PAM_SETCRED: - case SSS_PAM_OPEN_SESSION: - case SSS_PAM_CLOSE_SESSION: - break; - default: - D(("Illegal task [%#x]", task)); - overwrite_and_free_pam_items(&pi); - return PAM_SYSTEM_ERR; - } - - pam_status = send_and_receive(pamh, &pi, task, quiet_mode); - - if (flags & PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER - && pam_status == PAM_USER_UNKNOWN) { - pam_status = PAM_IGNORE; - } - if (flags & PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL - && pam_status == PAM_AUTHINFO_UNAVAIL) { - pam_status = PAM_IGNORE; - } - - switch (task) { - case SSS_PAM_AUTHENTICATE: - /* We allow sssd to send the return code PAM_NEW_AUTHTOK_REQD during - * authentication, see sss_cli.h for details */ - if (pam_status == PAM_NEW_AUTHTOK_REQD) { - D(("Authtoken expired, trying to change it")); - - pw_exp_data = malloc(sizeof(int)); - if (pw_exp_data == NULL) { - D(("malloc failed.")); - pam_status = PAM_BUF_ERR; - break; - } - *pw_exp_data = 1; - - pam_status = pam_set_data(pamh, PWEXP_FLAG, pw_exp_data, - free_exp_data); - if (pam_status != PAM_SUCCESS) { - D(("pam_set_data failed.")); - } - } - break; - case SSS_PAM_ACCT_MGMT: - if (pam_status == PAM_SUCCESS && - pam_get_data(pamh, PWEXP_FLAG, (const void **) &exp_data) == - PAM_SUCCESS) { - ret = do_pam_conversation(pamh, PAM_TEXT_INFO, - _("Password expired. Change your password now."), - NULL, NULL); - if (ret != PAM_SUCCESS) { - D(("do_pam_conversation failed.")); - } - pam_status = PAM_NEW_AUTHTOK_REQD; - } - break; - case SSS_PAM_CHAUTHTOK: - if (pam_status != PAM_SUCCESS && pam_status != PAM_USER_UNKNOWN) { - ret = pam_set_item(pamh, PAM_AUTHTOK, NULL); - if (ret != PAM_SUCCESS) { - D(("Failed to unset PAM_AUTHTOK [%s]", - pam_strerror(pamh,ret))); - } - ret = pam_set_item(pamh, PAM_OLDAUTHTOK, NULL); - if (ret != PAM_SUCCESS) { - D(("Failed to unset PAM_OLDAUTHTOK [%s]", - pam_strerror(pamh,ret))); - } - } - break; - case SSS_PAM_CHAUTHTOK_PRELIM: - if (pam_status == PAM_PERM_DENIED && pi.pam_authtok_size == 0 && - getuid() == 0 && - pam_get_data(pamh, PWEXP_FLAG, (const void **) &exp_data) != - PAM_SUCCESS) { - - ret = select_pw_reset_message(pamh, &pi); - if (ret != 0) { - D(("select_pw_reset_message failed.\n")); - } - } - default: - /* nothing to do */ - break; - } - - overwrite_and_free_pam_items(&pi); - - D(("retries [%d].", retries)); - - if (pam_status != PAM_SUCCESS && - (task == SSS_PAM_AUTHENTICATE || task == SSS_PAM_CHAUTHTOK_PRELIM) && - retries > 0) { - retry = true; - retries--; - - flags &= ~PAM_CLI_FLAGS_USE_FIRST_PASS; - ret = pam_set_item(pamh, PAM_AUTHTOK, NULL); - if (ret != PAM_SUCCESS) { - D(("Failed to unset PAM_AUTHTOK [%s]", - pam_strerror(pamh,ret))); - } - ret = pam_set_item(pamh, PAM_OLDAUTHTOK, NULL); - if (ret != PAM_SUCCESS) { - D(("Failed to unset PAM_OLDAUTHTOK [%s]", - pam_strerror(pamh,ret))); - } - } - } while(retry); - - return pam_status; -} - -PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, - const char **argv ) -{ - return pam_sss(SSS_PAM_AUTHENTICATE, pamh, flags, argc, argv); -} - - -PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, - const char **argv ) -{ - return pam_sss(SSS_PAM_SETCRED, pamh, flags, argc, argv); -} - -PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, - const char **argv ) -{ - return pam_sss(SSS_PAM_ACCT_MGMT, pamh, flags, argc, argv); -} - -PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, - const char **argv ) -{ - return pam_sss(SSS_PAM_CHAUTHTOK, pamh, flags, argc, argv); -} - -PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, - const char **argv ) -{ - return pam_sss(SSS_PAM_OPEN_SESSION, pamh, flags, argc, argv); -} - -PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, - const char **argv ) -{ - return pam_sss(SSS_PAM_CLOSE_SESSION, pamh, flags, argc, argv); -} - - -#ifdef PAM_STATIC - -/* static module data */ - -struct pam_module _pam_sssd_modstruct ={ - "pam_sssd", - pam_sm_authenticate, - pam_sm_setcred, - pam_sm_acct_mgmt, - pam_sm_open_session, - pam_sm_close_session, - pam_sm_chauthtok -}; - -#endif +/* + Authors: + Sumit Bose + + Copyright (C) 2009 Red Hat + Copyright (C) 2010, rhafer@suse.de, Novell Inc. + + 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 . +*/ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef HAVE_GDM_PAM_EXTENSIONS +#include +#endif + +#include "sss_pam_compat.h" +#include "sss_pam_macros.h" + +#include "sss_cli.h" +#include "pam_message.h" +#include "util/atomic_io.h" +#include "util/authtok-utils.h" +#include "util/dlinklist.h" +#include "util/memory_erase.h" + +#include +#define _(STRING) dgettext (PACKAGE, STRING) +#define _n(SINGULAR, PLURAL, VALUE) dngettext(PACKAGE, SINGULAR, PLURAL, VALUE) + +#define PWEXP_FLAG "pam_sss:password_expired_flag" +#define FD_DESTRUCTOR "pam_sss:fd_destructor" +#define PAM_SSS_AUTHOK_TYPE "pam_sss:authtok_type" +#define PAM_SSS_AUTHOK_SIZE "pam_sss:authtok_size" +#define PAM_SSS_AUTHOK_DATA "pam_sss:authtok_data" + +#define PW_RESET_MSG_FILENAME_TEMPLATE SSSD_CONF_DIR"/customize/%s/pam_sss_pw_reset_message.%s" +#define PW_RESET_MSG_MAX_SIZE 4096 + +#define OPT_RETRY_KEY "retry=" +#define OPT_DOMAINS_KEY "domains=" + +#define EXP_ACC_MSG _("Permission denied. ") +#define SRV_MSG _("Server message: ") +#define PASSKEY_LOCAL_AUTH_MSG _("Kerberos TGT will not be granted upon login, user experience will be affected.") +#define PASSKEY_DEFAULT_PIN_MSG _("Enter PIN:") + +#define DEBUG_MGS_LEN 1024 +#define MAX_AUTHTOK_SIZE (1024*1024) +#define CHECK_AND_RETURN_PI_STRING(s) ((s != NULL && *s != '\0')? s : "(not available)") +#define SERVICE_IS_GDM_SMARTCARD(pitem) (strcmp((pitem)->pam_service, \ + "gdm-smartcard") == 0) + +static void logger(pam_handle_t *pamh, int level, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + +#ifdef DEBUG + va_list apd; + char debug_msg[DEBUG_MGS_LEN]; + int ret; + va_copy(apd, ap); + + ret = vsnprintf(debug_msg, DEBUG_MGS_LEN, fmt, apd); + if (ret >= DEBUG_MGS_LEN) { + D(("the following message is truncated: %s", debug_msg)); + } else if (ret < 0) { + D(("vsnprintf failed to format debug message!")); + } else { + D((debug_msg)); + } + + va_end(apd); +#endif + + pam_vsyslog(pamh, LOG_AUTHPRIV|level, fmt, ap); + + va_end(ap); +} + +static void free_exp_data(pam_handle_t *pamh, void *ptr, int err) +{ + free(ptr); +} + +static void close_fd(pam_handle_t *pamh, void *ptr, int err) +{ +#ifdef PAM_DATA_REPLACE + if (err & PAM_DATA_REPLACE) { + /* Nothing to do */ + return; + } +#endif /* PAM_DATA_REPLACE */ + + D(("Closing the fd")); + + sss_pam_lock(); + sss_cli_close_socket(); + sss_pam_unlock(); +} + +struct cert_auth_info { + char *cert_user; + char *cert; + char *token_name; + char *module_name; + char *key_id; + char *label; + char *prompt_str; + char *pam_cert_user; + char *choice_list_id; + struct cert_auth_info *prev; + struct cert_auth_info *next; +}; + +static void free_cai(struct cert_auth_info *cai) +{ + if (cai != NULL) { + free(cai->cert_user); + free(cai->cert); + free(cai->token_name); + free(cai->module_name); + free(cai->key_id); + free(cai->label); + free(cai->prompt_str); + free(cai->choice_list_id); + free(cai); + } +} + +static void free_cert_list(struct cert_auth_info *list) +{ + struct cert_auth_info *cai; + struct cert_auth_info *cai_next; + + if (list != NULL) { + DLIST_FOR_EACH_SAFE(cai, cai_next, list) { + DLIST_REMOVE(list, cai); + free_cai(cai); + } + } +} + +static void overwrite_and_free_authtoks(struct pam_items *pi) +{ + if (pi->pam_authtok != NULL) { + sss_erase_mem_securely((void *)pi->pam_authtok, pi->pam_authtok_size); + free((void *)pi->pam_authtok); + pi->pam_authtok = NULL; + } + + if (pi->pam_newauthtok != NULL) { + sss_erase_mem_securely((void *)pi->pam_newauthtok, pi->pam_newauthtok_size); + free((void *)pi->pam_newauthtok); + pi->pam_newauthtok = NULL; + } + + if (pi->first_factor != NULL) { + sss_erase_mem_securely((void *)pi->first_factor, strlen(pi->first_factor)); + free((void *)pi->first_factor); + pi->first_factor = NULL; + } + + pi->pamstack_authtok = NULL; + pi->pamstack_oldauthtok = NULL; +} + +static void overwrite_and_free_pam_items(struct pam_items *pi) +{ + overwrite_and_free_authtoks(pi); + + free(pi->domain_name); + pi->domain_name = NULL; + + free(pi->otp_vendor); + pi->otp_vendor = NULL; + + free(pi->otp_token_id); + pi->otp_token_id = NULL; + + free(pi->otp_challenge); + pi->otp_challenge = NULL; + + free(pi->passkey_key); + pi->passkey_key = NULL; + + free(pi->passkey_prompt_pin); + pi->passkey_prompt_pin = NULL; + + free(pi->passkey_devinfo); + pi->passkey_devinfo = NULL; + + 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) { + if (s1 == NULL && s2 == NULL) return 0; + if (s1 == NULL && s2 != NULL) return -1; + if (s1 != NULL && s2 == NULL) return 1; + return strcmp(s1, s2); +} + +enum { + SSS_PAM_CONV_DONE = 0, + SSS_PAM_CONV_STD, + SSS_PAM_CONV_REENTER, +}; + +static int do_pam_conversation(pam_handle_t *pamh, const int msg_style, + const char *msg, + const char *reenter_msg, + char **_answer) +{ + int ret; + int state = SSS_PAM_CONV_STD; + const struct pam_conv *conv; + const struct pam_message *mesg[1]; + struct pam_message *pam_msg; + struct pam_response *resp=NULL; + char *answer = NULL; + + if ((msg_style == PAM_TEXT_INFO || msg_style == PAM_ERROR_MSG) && + msg == NULL) return PAM_SYSTEM_ERR; + + if ((msg_style == PAM_PROMPT_ECHO_OFF || + msg_style == PAM_PROMPT_ECHO_ON) && + (msg == NULL || _answer == NULL)) return PAM_SYSTEM_ERR; + + if (msg_style == PAM_TEXT_INFO || msg_style == PAM_ERROR_MSG) { + logger(pamh, LOG_INFO, "User %s message: %s", + msg_style == PAM_TEXT_INFO ? "info" : "error", + msg); + } + + ret=pam_get_item(pamh, PAM_CONV, (const void **) &conv); + if (ret != PAM_SUCCESS) return ret; + if (conv == NULL || conv->conv == NULL) { + logger(pamh, LOG_ERR, "No conversation function"); + return PAM_SYSTEM_ERR; + } + + do { + pam_msg = malloc(sizeof(struct pam_message)); + if (pam_msg == NULL) { + D(("Malloc failed.")); + ret = PAM_SYSTEM_ERR; + goto failed; + } + + pam_msg->msg_style = msg_style; + if (state == SSS_PAM_CONV_REENTER) { + pam_msg->msg = reenter_msg; + } else { + pam_msg->msg = msg; + } + + mesg[0] = (const struct pam_message *) pam_msg; + + ret=conv->conv(1, mesg, &resp, + conv->appdata_ptr); + free(pam_msg); + if (ret != PAM_SUCCESS) { + D(("Conversation failure: %s.", pam_strerror(pamh,ret))); + goto failed; + } + + if (msg_style == PAM_PROMPT_ECHO_OFF || + msg_style == PAM_PROMPT_ECHO_ON) { + if (resp == NULL) { + D(("response expected, but resp==NULL")); + ret = PAM_SYSTEM_ERR; + goto failed; + } + + if (state == SSS_PAM_CONV_REENTER) { + if (null_strcmp(answer, resp[0].resp) != 0) { + logger(pamh, LOG_NOTICE, "Passwords do not match."); + sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); + free(resp[0].resp); + if (answer != NULL) { + sss_erase_mem_securely((void *) answer, strlen(answer)); + free(answer); + answer = NULL; + } + ret = do_pam_conversation(pamh, PAM_ERROR_MSG, + _("Passwords do not match"), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + ret = PAM_SYSTEM_ERR; + goto failed; + } + ret = PAM_CRED_ERR; + goto failed; + } + sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); + free(resp[0].resp); + } else { + if (resp[0].resp == NULL) { + D(("Empty password")); + answer = NULL; + } else { + answer = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); + sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); + free(resp[0].resp); + if(answer == NULL) { + D(("strndup failed")); + ret = PAM_BUF_ERR; + goto failed; + } + } + } + free(resp); + resp = NULL; + } + + if (reenter_msg != NULL && state == SSS_PAM_CONV_STD) { + state = SSS_PAM_CONV_REENTER; + } else { + state = SSS_PAM_CONV_DONE; + } + } while (state != SSS_PAM_CONV_DONE); + + if (_answer) *_answer = answer; + return PAM_SUCCESS; + +failed: + free(answer); + return ret; + +} + +static errno_t display_pw_reset_message(pam_handle_t *pamh, + const char *domain_name, + const char *suffix) +{ + int ret; + struct stat stat_buf; + char *msg_buf = NULL; + int fd = -1; + size_t size; + size_t total_len; + char *filename = NULL; + + if (strchr(suffix, '/') != NULL || strchr(domain_name, '/') != NULL) { + D(("Suffix [%s] or domain name [%s] contain illegal character.", suffix, + domain_name)); + return EINVAL; + } + + size = sizeof(PW_RESET_MSG_FILENAME_TEMPLATE) + strlen(domain_name) + + strlen(suffix); + filename = malloc(size); + if (filename == NULL) { + D(("malloc failed.")); + ret = ENOMEM; + goto done; + } + ret = snprintf(filename, size, PW_RESET_MSG_FILENAME_TEMPLATE, domain_name, + suffix); + if (ret < 0 || ret >= size) { + D(("snprintf failed.")); + ret = EFAULT; + goto done; + } + + fd = open(filename, O_RDONLY); + if (fd == -1) { + ret = errno; + D(("open failed [%d][%s].\n", ret, strerror(ret))); + goto done; + } + + ret = fstat(fd, &stat_buf); + if (ret == -1) { + ret = errno; + D(("fstat failed [%d][%s].", ret, strerror(ret))); + goto done; + } + + if (!S_ISREG(stat_buf.st_mode)) { + logger(pamh, LOG_ERR, + "Password reset message file is not a regular file."); + ret = EINVAL; + goto done; + } + + if (stat_buf.st_uid != 0 || stat_buf.st_gid != 0 || + (stat_buf.st_mode & ~S_IFMT) != 0644) { + logger(pamh, LOG_ERR,"Permission error, " + "file [%s] must be owned by root with permissions 0644.", + filename); + ret = EPERM; + goto done; + } + + if (stat_buf.st_size > PW_RESET_MSG_MAX_SIZE) { + logger(pamh, LOG_ERR, "Password reset message file is too large."); + ret = EFBIG; + goto done; + } + + msg_buf = malloc(stat_buf.st_size + 1); + if (msg_buf == NULL) { + D(("malloc failed.")); + ret = ENOMEM; + goto done; + } + + errno = 0; + total_len = sss_atomic_read_s(fd, msg_buf, stat_buf.st_size); + if (total_len == -1) { + ret = errno; + D(("read failed [%d][%s].", ret, strerror(ret))); + goto done; + } + + ret = close(fd); + fd = -1; + if (ret == -1) { + ret = errno; + D(("close failed [%d][%s].", ret, strerror(ret))); + } + + if (total_len != stat_buf.st_size) { + D(("read fewer bytes [%d] than expected [%d].", total_len, + stat_buf.st_size)); + ret = EIO; + goto done; + } + + msg_buf[stat_buf.st_size] = '\0'; + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, msg_buf, NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + } + +done: + if (fd != -1) { + close(fd); + } + free(msg_buf); + free(filename); + + return ret; +} + +static errno_t select_pw_reset_message(pam_handle_t *pamh, struct pam_items *pi) +{ + int ret; + char *locale; + const char *domain_name; + + domain_name = pi->domain_name; + if (domain_name == NULL || *domain_name == '\0') { + D(("Domain name is unknown.")); + return EINVAL; + } + + locale = setlocale(LC_MESSAGES, NULL); + + ret = -1; + if (locale != NULL) { + ret = display_pw_reset_message(pamh, domain_name, locale); + } + + if (ret != 0) { + ret = display_pw_reset_message(pamh, domain_name, "txt"); + } + + if (ret != 0) { + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, + _("Password reset by root is not supported."), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + } + } + + return ret; +} + +static int user_info_offline_auth(pam_handle_t *pamh, size_t buflen, + uint8_t *buf) +{ + int ret; + int64_t expire_date; + struct tm tm; + char expire_str[128]; + char user_msg[256]; + + expire_str[0] = '\0'; + + if (buflen != sizeof(uint32_t) + sizeof(int64_t)) { + D(("User info response data has the wrong size")); + return PAM_BUF_ERR; + } + + memcpy(&expire_date, buf + sizeof(uint32_t), sizeof(int64_t)); + + if (expire_date > 0) { + if (localtime_r((time_t *) &expire_date, &tm) != NULL) { + ret = strftime(expire_str, sizeof(expire_str), "%c", &tm); + if (ret == 0) { + D(("strftime failed.")); + expire_str[0] = '\0'; + } + } else { + D(("localtime_r failed")); + } + } + + ret = snprintf(user_msg, sizeof(user_msg), "%s%s%s.", + _("Authenticated with cached credentials"), + expire_str[0] ? _(", your cached password will expire at: ") : "", + expire_str[0] ? expire_str : ""); + if (ret < 0 || ret >= sizeof(user_msg)) { + D(("snprintf failed.")); + return PAM_SYSTEM_ERR; + } + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_grace_login(pam_handle_t *pamh, + size_t buflen, + uint8_t *buf) +{ + int ret; + uint32_t grace; + char user_msg[256]; + + if (buflen != 2* sizeof(uint32_t)) { + D(("User info response data has the wrong size")); + return PAM_BUF_ERR; + } + memcpy(&grace, buf + sizeof(uint32_t), sizeof(uint32_t)); + ret = snprintf(user_msg, sizeof(user_msg), + _("Your password has expired. " + "You have %1$d grace login(s) remaining."), + grace); + if (ret < 0 || ret >= sizeof(user_msg)) { + D(("snprintf failed.")); + return PAM_SYSTEM_ERR; + } + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); + + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +#define MINSEC 60 +#define HOURSEC (60*MINSEC) +#define DAYSEC (24*HOURSEC) +static int user_info_expire_warn(pam_handle_t *pamh, + size_t buflen, + uint8_t *buf) +{ + int ret; + uint32_t expire; + char user_msg[256]; + const char* unit; + + if (buflen != 2* sizeof(uint32_t)) { + D(("User info response data has the wrong size")); + return PAM_BUF_ERR; + } + memcpy(&expire, buf + sizeof(uint32_t), sizeof(uint32_t)); + /* expire == 0 indicates the password expired */ + if (expire != 0) { + if (expire >= DAYSEC) { + expire /= DAYSEC; + unit = _n("day", "days", expire); + } else if (expire >= HOURSEC) { + expire /= HOURSEC; + unit = _n("hour", "hours", expire); + } else if (expire >= MINSEC) { + expire /= MINSEC; + unit = _n("minute", "minutes", expire); + } else { + unit = _n("second", "seconds", expire); + } + + ret = snprintf(user_msg, sizeof(user_msg), + _("Your password will expire in %1$d %2$s."), expire, unit); + } else { + ret = snprintf(user_msg, sizeof(user_msg), + _("Your password has expired.")); + } + + if (ret < 0 || ret >= sizeof(user_msg)) { + D(("snprintf failed.")); + return PAM_SYSTEM_ERR; + } + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); + + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_offline_auth_delayed(pam_handle_t *pamh, size_t buflen, + uint8_t *buf) +{ + int ret; + int64_t delayed_until; + struct tm tm; + char delay_str[128]; + char user_msg[256]; + + delay_str[0] = '\0'; + + if (buflen != sizeof(uint32_t) + sizeof(int64_t)) { + D(("User info response data has the wrong size")); + return PAM_BUF_ERR; + } + + memcpy(&delayed_until, buf + sizeof(uint32_t), sizeof(int64_t)); + + if (delayed_until <= 0) { + D(("User info response data has an invalid value")); + return PAM_BUF_ERR; + } + + if (localtime_r((time_t *) &delayed_until, &tm) != NULL) { + ret = strftime(delay_str, sizeof(delay_str), "%c", &tm); + if (ret == 0) { + D(("strftime failed.")); + delay_str[0] = '\0'; + } + } else { + D(("localtime_r failed")); + } + + ret = snprintf(user_msg, sizeof(user_msg), "%s%s.", + _("Authentication is denied until: "), + delay_str); + if (ret < 0 || ret >= sizeof(user_msg)) { + D(("snprintf failed.")); + return PAM_SYSTEM_ERR; + } + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_offline_chpass(pam_handle_t *pamh) +{ + int ret; + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, + _("System is offline, password change not possible"), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_otp_chpass(pam_handle_t *pamh) +{ + int ret; + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, + _("After changing the OTP password, you need to " + "log out and back in order to acquire a ticket"), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_pin_locked(pam_handle_t *pamh) +{ + int ret; + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, _("PIN locked"), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_no_krb_tgt(pam_handle_t *pamh) +{ + int ret; + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, + _("No Kerberos TGT granted as " + "the server does not support this method. " + "Your single-sign on(SSO) experience will " + "be affected."), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_account_expired(pam_handle_t *pamh, size_t buflen, + uint8_t *buf) +{ + int ret; + uint32_t msg_len; + char *user_msg; + size_t bufsize = 0; + + /* resp_type and length of message are expected to be in buf */ + if (buflen < 2* sizeof(uint32_t)) { + D(("User info response data is too short")); + return PAM_BUF_ERR; + } + + /* msg_len = legth of message */ + memcpy(&msg_len, buf + sizeof(uint32_t), sizeof(uint32_t)); + + if (buflen != 2* sizeof(uint32_t) + msg_len) { + D(("User info response data has the wrong size")); + return PAM_BUF_ERR; + } + + bufsize = strlen(EXP_ACC_MSG) + 1; + + if (msg_len > 0) { + bufsize += strlen(SRV_MSG) + msg_len; + } + + user_msg = (char *)malloc(sizeof(char) * bufsize); + if (!user_msg) { + D(("Out of memory.")); + return PAM_SYSTEM_ERR; + } + + ret = snprintf(user_msg, bufsize, "%s%s%.*s", + EXP_ACC_MSG, + msg_len > 0 ? SRV_MSG : "", + (int)msg_len, + msg_len > 0 ? (char *)(buf + 2 * sizeof(uint32_t)) : "" ); + if (ret < 0 || ret > bufsize) { + D(("snprintf failed.")); + + free(user_msg); + return PAM_SYSTEM_ERR; + } + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); + free(user_msg); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int user_info_chpass_error(pam_handle_t *pamh, size_t buflen, + uint8_t *buf) +{ + int ret; + uint32_t msg_len; + char *user_msg; + size_t bufsize = 0; + + if (buflen < 2* sizeof(uint32_t)) { + D(("User info response data is too short")); + return PAM_BUF_ERR; + } + + memcpy(&msg_len, buf + sizeof(uint32_t), sizeof(uint32_t)); + + if (buflen != 2* sizeof(uint32_t) + msg_len) { + D(("User info response data has the wrong size")); + return PAM_BUF_ERR; + } + + bufsize = strlen(_("Password change failed. ")) + 1; + + if (msg_len > 0) { + bufsize += strlen(_("Server message: ")) + msg_len; + } + + user_msg = (char *)malloc(sizeof(char) * bufsize); + if (!user_msg) { + D(("Out of memory.")); + return PAM_SYSTEM_ERR; + } + + ret = snprintf(user_msg, bufsize, "%s%s%.*s", + _("Password change failed. "), + msg_len > 0 ? _("Server message: ") : "", + (int)msg_len, + msg_len > 0 ? (char *)(buf + 2 * sizeof(uint32_t)) : "" ); + if (ret < 0 || ret > bufsize) { + D(("snprintf failed.")); + + free(user_msg); + return PAM_SYSTEM_ERR; + } + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL); + free(user_msg); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; +} + +static int eval_user_info_response(pam_handle_t *pamh, size_t buflen, + uint8_t *buf) +{ + int ret; + uint32_t type; + + if (buflen < sizeof(uint32_t)) { + D(("User info response data is too short")); + return PAM_BUF_ERR; + } + + memcpy(&type, buf, sizeof(uint32_t)); + + switch(type) { + case SSS_PAM_USER_INFO_OFFLINE_AUTH: + ret = user_info_offline_auth(pamh, buflen, buf); + break; + case SSS_PAM_USER_INFO_GRACE_LOGIN: + ret = user_info_grace_login(pamh, buflen, buf); + break; + case SSS_PAM_USER_INFO_EXPIRE_WARN: + ret = user_info_expire_warn(pamh, buflen, buf); + break; + case SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED: + ret = user_info_offline_auth_delayed(pamh, buflen, buf); + break; + case SSS_PAM_USER_INFO_OFFLINE_CHPASS: + ret = user_info_offline_chpass(pamh); + break; + case SSS_PAM_USER_INFO_OTP_CHPASS: + ret = user_info_otp_chpass(pamh); + break; + case SSS_PAM_USER_INFO_CHPASS_ERROR: + ret = user_info_chpass_error(pamh, buflen, buf); + break; + case SSS_PAM_USER_INFO_PIN_LOCKED: + ret = user_info_pin_locked(pamh); + break; + case SSS_PAM_USER_INFO_ACCOUNT_EXPIRED: + ret = user_info_account_expired(pamh, buflen, buf); + break; + case SSS_PAM_USER_INFO_NO_KRB_TGT: + ret = user_info_no_krb_tgt(pamh); + break; + default: + D(("Unknown user info type [%d]", type)); + ret = PAM_SYSTEM_ERR; + } + + return ret; +} + +static int parse_cert_info(struct pam_items *pi, uint8_t *buf, size_t len, + size_t *p, const char **cert_user, + const char **pam_cert_user) +{ + struct cert_auth_info *cai = NULL; + size_t offset; + int ret; + + if (buf[*p + (len - 1)] != '\0') { + D(("cert info does not end with \\0.")); + return EINVAL; + } + + cai = calloc(1, sizeof(struct cert_auth_info)); + if (cai == NULL) { + return ENOMEM; + } + + cai->cert_user = strdup((char *) &buf[*p]); + if (cai->cert_user == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + if (cert_user != NULL) { + *cert_user = cai->cert_user; + } + + offset = strlen(cai->cert_user) + 1; + if (offset >= len) { + D(("Cert message size mismatch")); + ret = EINVAL; + goto done; + } + + cai->token_name = strdup((char *) &buf[*p + offset]); + if (cai->token_name == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + + offset += strlen(cai->token_name) + 1; + if (offset >= len) { + D(("Cert message size mismatch")); + ret = EINVAL; + goto done; + } + + cai->module_name = strdup((char *) &buf[*p + offset]); + if (cai->module_name == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + + offset += strlen(cai->module_name) + 1; + if (offset >= len) { + D(("Cert message size mismatch")); + ret = EINVAL; + goto done; + } + + cai->key_id = strdup((char *) &buf[*p + offset]); + if (cai->key_id == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + + offset += strlen(cai->key_id) + 1; + if (offset >= len) { + D(("Cert message size mismatch")); + ret = EINVAL; + goto done; + } + + cai->label = strdup((char *) &buf[*p + offset]); + if (cai->label == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + + offset += strlen(cai->label) + 1; + if (offset >= len) { + D(("Cert message size mismatch")); + ret = EINVAL; + goto done; + } + + cai->prompt_str = strdup((char *) &buf[*p + offset]); + if (cai->prompt_str == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + + offset += strlen(cai->prompt_str) + 1; + if (offset >= len) { + D(("Cert message size mismatch")); + ret = EINVAL; + goto done; + } + + cai->pam_cert_user = strdup((char *) &buf[*p + offset]); + if (cai->pam_cert_user == NULL) { + D(("strdup failed")); + ret = ENOMEM; + goto done; + } + if (pam_cert_user != NULL) { + *pam_cert_user = cai->pam_cert_user; + } + + D(("cert user: [%s] token name: [%s] module: [%s] key id: [%s] " + "prompt: [%s] pam cert user: [%s]", + cai->cert_user, cai->token_name, cai->module_name, + cai->key_id, cai->prompt_str, cai->pam_cert_user)); + + DLIST_ADD(pi->cert_list, cai); + ret = 0; + +done: + if (ret != 0) { + free_cai(cai); + } + + return ret; +} + +static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf, + struct pam_items *pi) +{ + int ret; + size_t p=0; + char *env_item; + int32_t c; + int32_t type; + int32_t len; + int32_t pam_status; + size_t offset; + const char *cert_user; + const char *pam_cert_user; + + if (buflen < (2*sizeof(int32_t))) { + D(("response buffer is too small")); + return PAM_BUF_ERR; + } + + memcpy(&pam_status, buf+p, sizeof(int32_t)); + p += sizeof(int32_t); + + + memcpy(&c, buf+p, sizeof(int32_t)); + p += sizeof(int32_t); + + while(c>0) { + if (buflen < (p+2*sizeof(int32_t))) { + D(("response buffer is too small")); + return PAM_BUF_ERR; + } + + memcpy(&type, buf+p, sizeof(int32_t)); + p += sizeof(int32_t); + + memcpy(&len, buf+p, sizeof(int32_t)); + p += sizeof(int32_t); + + if (buflen < (p + len)) { + D(("response buffer is too small")); + return PAM_BUF_ERR; + } + + switch(type) { + case SSS_PAM_SYSTEM_INFO: + if (buf[p + (len -1)] != '\0') { + D(("system info does not end with \\0.")); + break; + } + logger(pamh, LOG_INFO, "system info: [%s]", &buf[p]); + break; + case SSS_PAM_DOMAIN_NAME: + if (buf[p + (len -1)] != '\0') { + D(("domain name does not end with \\0.")); + break; + } + D(("domain name: [%s]", &buf[p])); + free(pi->domain_name); + pi->domain_name = strdup((char *) &buf[p]); + if (pi->domain_name == NULL) { + D(("strdup failed")); + } + break; + case SSS_ENV_ITEM: + case SSS_PAM_ENV_ITEM: + case SSS_ALL_ENV_ITEM: + if (buf[p + (len -1)] != '\0') { + D(("env item does not end with \\0.")); + break; + } + + D(("env item: [%s]", &buf[p])); + if (type == SSS_PAM_ENV_ITEM || type == SSS_ALL_ENV_ITEM) { + ret = pam_putenv(pamh, (char *)&buf[p]); + if (ret != PAM_SUCCESS) { + D(("pam_putenv failed.")); + break; + } + } + + if (type == SSS_ENV_ITEM || type == SSS_ALL_ENV_ITEM) { + env_item = strdup((char *)&buf[p]); + if (env_item == NULL) { + D(("strdup failed")); + break; + } + ret = putenv(env_item); + if (ret == -1) { + D(("putenv failed.")); + break; + } + } + break; + case SSS_PAM_USER_INFO: + ret = eval_user_info_response(pamh, len, &buf[p]); + if (ret != PAM_SUCCESS) { + D(("eval_user_info_response failed")); + } + break; + case SSS_PAM_TEXT_MSG: + if (buf[p + (len -1)] != '\0') { + D(("system info does not end with \\0.")); + break; + } + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, (char *) &buf[p], + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + } + break; + case SSS_OTP: + D(("OTP was used, removing authtokens.")); + overwrite_and_free_authtoks(pi); + ret = pam_set_item(pamh, PAM_AUTHTOK, NULL); + if (ret != PAM_SUCCESS) { + D(("Failed to remove PAM_AUTHTOK after using otp [%s]", + pam_strerror(pamh,ret))); + } + break; + case SSS_PAM_OTP_INFO: + if (buf[p + (len - 1)] != '\0') { + D(("otp info does not end with \\0.")); + break; + } + + free(pi->otp_vendor); + pi->otp_vendor = strdup((char *) &buf[p]); + if (pi->otp_vendor == NULL) { + D(("strdup failed")); + break; + } + + offset = strlen(pi->otp_vendor) + 1; + if (offset >= len) { + D(("OTP message size mismatch")); + free(pi->otp_vendor); + pi->otp_vendor = NULL; + break; + } + free(pi->otp_token_id); + pi->otp_token_id = strdup((char *) &buf[p + offset]); + if (pi->otp_token_id == NULL) { + D(("strdup failed")); + break; + } + + offset += strlen(pi->otp_token_id) + 1; + if (offset >= len) { + D(("OTP message size mismatch")); + free(pi->otp_token_id); + pi->otp_token_id = NULL; + break; + } + free(pi->otp_challenge); + pi->otp_challenge = strdup((char *) &buf[p + offset]); + if (pi->otp_challenge == NULL) { + D(("strdup failed")); + break; + } + + break; + case SSS_PAM_CERT_INFO: + case SSS_PAM_CERT_INFO_WITH_HINT: + if (buf[p + (len - 1)] != '\0') { + D(("cert info does not end with \\0.")); + break; + } + + if (type == SSS_PAM_CERT_INFO_WITH_HINT) { + pi->user_name_hint = true; + } else { + pi->user_name_hint = false; + } + + ret = parse_cert_info(pi, buf, len, &p, &cert_user, + &pam_cert_user); + if (ret != 0) { + D(("Failed to parse cert info")); + break; + } + + if ((pi->pam_user == NULL || *(pi->pam_user) == '\0') + && *cert_user != '\0' && *pam_cert_user != '\0') { + ret = pam_set_item(pamh, PAM_USER, pam_cert_user); + if (ret != PAM_SUCCESS) { + D(("Failed to set PAM_USER during " + "Smartcard authentication [%s]", + pam_strerror(pamh, ret))); + break; + } + + pi->pam_user = cert_user; + pi->pam_user_size = strlen(pi->pam_user) + 1; + } + break; + case SSS_PASSWORD_PROMPTING: + 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; + case SSS_CHILD_KEEP_ALIVE: + memcpy(&pi->child_pid, &buf[p], len); + break; + case SSS_PAM_OAUTH2_INFO: + if (buf[p + (len - 1)] != '\0') { + D(("oauth2 info does not end with \\0.")); + break; + } + + free(pi->oauth2_url); + pi->oauth2_url = strdup((char *) &buf[p]); + if (pi->oauth2_url == NULL) { + D(("strdup failed")); + break; + } + + offset = strlen(pi->oauth2_url) + 1; + if (offset >= len) { + D(("OAuth2 message size mismatch")); + free(pi->oauth2_url); + pi->oauth2_url = NULL; + break; + } + + free(pi->oauth2_url_complete); + pi->oauth2_url_complete = strdup((char *) &buf[p + offset]); + if (pi->oauth2_url_complete == NULL) { + D(("strdup failed")); + break; + } + + offset = offset + strlen(pi->oauth2_url_complete) + 1; + if (offset >= len) { + D(("OAuth2 message size mismatch")); + free(pi->oauth2_url_complete); + pi->oauth2_url_complete = NULL; + break; + } + + /* This field is optional. */ + if (pi->oauth2_url_complete[0] == '\0') { + free(pi->oauth2_url_complete); + pi->oauth2_url_complete = NULL; + } + + free(pi->oauth2_pin); + pi->oauth2_pin = strdup((char *) &buf[p + offset]); + if (pi->oauth2_pin == NULL) { + D(("strdup failed")); + break; + } + + break; + case SSS_PAM_PASSKEY_KRB_INFO: + free(pi->passkey_prompt_pin); + pi->passkey_prompt_pin = strdup((char *) &buf[p]); + if (pi->passkey_prompt_pin == NULL) { + D(("strdup failed")); + break; + } + + offset = strlen(pi->passkey_prompt_pin) + 1; + if (offset >= len) { + D(("Passkey message size mismatch")); + free(pi->passkey_prompt_pin); + pi->passkey_prompt_pin = NULL; + break; + } + + free(pi->passkey_key); + pi->passkey_key = strdup((char *) &buf[p + offset]); + if (pi->passkey_key == NULL) { + D(("strdup failed")); + break; + } + /* devinfo shall be retrieved according key handle(s) + */ + free (pi->passkey_devinfo); + pi->passkey_devinfo = NULL; + break; + + case SSS_PAM_PASSKEY_INFO: + if (buf[p + (len - 1)] != '\0') { + D(("passkey info does not end with \\0.")); + break; + } + + free(pi->passkey_prompt_pin); + pi->passkey_prompt_pin = strdup((char *) &buf[p]); + if (pi->passkey_prompt_pin == NULL) { + D(("strdup failed")); + break; + } + /* devinfo shall be retrieved according key handle(s) + */ + free (pi->passkey_devinfo); + pi->passkey_devinfo = NULL; + break; + + case SSS_PAM_PASSKEY_DEVINFO: + if (buf[p + (len - 1)] != '\0') { + D(("passkey devinfo does not end with \\0.")); + break; + } + + free(pi->passkey_devinfo); + pi->passkey_devinfo = strdup((char *) &buf[p]); + if (pi->passkey_devinfo == NULL) { + D(("strdup failed")); + break; + } + break; + default: + D(("Unknown response type [%d]", type)); + } + p += len; + + --c; + } + + return PAM_SUCCESS; +} + +bool is_string_empty_or_whitespace(const char *str) +{ + int i; + + if (str == NULL) { + return true; + } + + for (i = 0; str[i] != '\0'; i++) { + if (!isspace(str[i])) { + return false; + } + } + + return true; +} + +static int get_pam_items(pam_handle_t *pamh, uint32_t flags, + struct pam_items *pi) +{ + int ret; + + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; + pi->pam_authtok = NULL; + pi->pam_authtok_size = 0; + pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_EMPTY; + pi->pam_newauthtok = NULL; + pi->pam_newauthtok_size = 0; + pi->first_factor = NULL; + + ret = pam_get_item(pamh, PAM_SERVICE, (const void **) &(pi->pam_service)); + if (ret != PAM_SUCCESS) return ret; + if (pi->pam_service == NULL) pi->pam_service=""; + pi->pam_service_size=strlen(pi->pam_service)+1; + + ret = pam_get_item(pamh, PAM_USER, (const void **) &(pi->pam_user)); + if (ret == PAM_PERM_DENIED && (flags & PAM_CLI_FLAGS_ALLOW_MISSING_NAME)) { + pi->pam_user = ""; + ret = PAM_SUCCESS; + } + if (ret != PAM_SUCCESS) return ret; + if (flags & PAM_CLI_FLAGS_ALLOW_MISSING_NAME) { + if (is_string_empty_or_whitespace(pi->pam_user)) { + pi->pam_user = ""; + } + } + if (pi->pam_user == NULL) { + D(("No user found, aborting.")); + return PAM_BAD_ITEM; + } + if (strcmp(pi->pam_user, "root") == 0) { + D(("pam_sss will not handle root.")); + return PAM_USER_UNKNOWN; + } + pi->pam_user_size=strlen(pi->pam_user)+1; + + + ret = pam_get_item(pamh, PAM_TTY, (const void **) &(pi->pam_tty)); + if (ret != PAM_SUCCESS) return ret; + if (pi->pam_tty == NULL) pi->pam_tty=""; + pi->pam_tty_size=strlen(pi->pam_tty)+1; + + ret = pam_get_item(pamh, PAM_RUSER, (const void **) &(pi->pam_ruser)); + if (ret != PAM_SUCCESS) return ret; + if (pi->pam_ruser == NULL) pi->pam_ruser=""; + pi->pam_ruser_size=strlen(pi->pam_ruser)+1; + + ret = pam_get_item(pamh, PAM_RHOST, (const void **) &(pi->pam_rhost)); + if (ret != PAM_SUCCESS) return ret; + if (pi->pam_rhost == NULL) pi->pam_rhost=""; + pi->pam_rhost_size=strlen(pi->pam_rhost)+1; + + ret = pam_get_item(pamh, PAM_AUTHTOK, + (const void **) &(pi->pamstack_authtok)); + if (ret != PAM_SUCCESS) return ret; + if (pi->pamstack_authtok == NULL) pi->pamstack_authtok=""; + + ret = pam_get_item(pamh, PAM_OLDAUTHTOK, + (const void **) &(pi->pamstack_oldauthtok)); + if (ret != PAM_SUCCESS) return ret; + if (pi->pamstack_oldauthtok == NULL) pi->pamstack_oldauthtok=""; + + pi->cli_pid = getpid(); + + pi->login_name = pam_modutil_getlogin(pamh); + if (pi->login_name == NULL) pi->login_name=""; + + pi->domain_name = NULL; + + if (pi->requested_domains == NULL) pi->requested_domains = ""; + pi->requested_domains_size = strlen(pi->requested_domains) + 1; + + pi->otp_vendor = NULL; + pi->otp_token_id = NULL; + pi->otp_challenge = NULL; + pi->password_prompting = false; + + pi->cert_list = NULL; + pi->selected_cert = NULL; + + pi->pc = NULL; + + pi->flags = flags; + + return PAM_SUCCESS; +} + +static void print_pam_items(struct pam_items *pi) +{ + if (pi == NULL) return; + + D(("Service: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_service))); + D(("User: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_user))); + D(("Tty: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_tty))); + D(("Ruser: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_ruser))); + D(("Rhost: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_rhost))); + D(("Pamstack_Authtok: %s", + CHECK_AND_RETURN_PI_STRING(pi->pamstack_authtok))); + D(("Pamstack_Oldauthtok: %s", + CHECK_AND_RETURN_PI_STRING(pi->pamstack_oldauthtok))); + D(("Authtok: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_authtok))); + D(("Newauthtok: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_newauthtok))); + D(("Cli_PID: %d", pi->cli_pid)); + D(("Child_PID: %d", pi->child_pid)); + D(("Requested domains: %s", pi->requested_domains)); + D(("Flags: %d", pi->flags)); +} + +static int send_and_receive(pam_handle_t *pamh, struct pam_items *pi, + enum sss_cli_command task, bool quiet_mode) +{ + int ret; + int sret; + int errnop; + struct sss_cli_req_data rd; + uint8_t *buf = NULL; + uint8_t *repbuf = NULL; + size_t replen; + int pam_status = PAM_SYSTEM_ERR; + + print_pam_items(pi); + + ret = pack_message_v3(pi, &rd.len, &buf); + if (ret != 0) { + D(("pack_message failed.")); + pam_status = PAM_SYSTEM_ERR; + goto done; + } + rd.data = buf; + + errnop = 0; + ret = sss_pam_make_request(task, &rd, &repbuf, &replen, &errnop); + + sret = pam_set_data(pamh, FD_DESTRUCTOR, NULL, close_fd); + if (sret != PAM_SUCCESS) { + D(("pam_set_data failed, client might leaks fds")); + } + + if (ret != PAM_SUCCESS) { + /* If there is no PAM responder socket during the access control step + * we assume this is on purpose, i.e. PAM responder is not configured. + * PAM_USER_UNKNOWN is returned to the PAM stack to avoid unexpected + * denials. */ + if (errnop == ESSS_NO_SOCKET && task == SSS_PAM_ACCT_MGMT) { + pam_status = PAM_USER_UNKNOWN; + } else { + if (errnop != 0 && errnop != ESSS_NO_SOCKET) { + logger(pamh, LOG_ERR, "Request to sssd failed. %s", + ssscli_err2string(errnop)); + } + + pam_status = PAM_AUTHINFO_UNAVAIL; + } + goto done; + } + +/* FIXME: add an end signature */ + if (replen < (2*sizeof(int32_t))) { + D(("response not in expected format.")); + pam_status = PAM_SYSTEM_ERR; + goto done; + } + + SAFEALIGN_COPY_UINT32(&pam_status, repbuf, NULL); + ret = eval_response(pamh, replen, repbuf, pi); + if (ret != PAM_SUCCESS) { + D(("eval_response failed.")); + pam_status = ret; + goto done; + } + + switch (task) { + case SSS_PAM_AUTHENTICATE: + logger(pamh, (pam_status == PAM_SUCCESS ? LOG_INFO : LOG_NOTICE), + "authentication %s; logname=%s uid=%lu euid=%d tty=%s " + "ruser=%s rhost=%s user=%s", + pam_status == PAM_SUCCESS ? "success" : "failure", + pi->login_name, getuid(), (unsigned long) geteuid(), + pi->pam_tty, pi->pam_ruser, pi->pam_rhost, pi->pam_user); + if (pam_status != PAM_SUCCESS) { + /* don't log if quiet_mode is on and pam_status is + * User not known to the underlying authentication module + */ + if (!quiet_mode || pam_status != 10) { + logger(pamh, LOG_NOTICE, "received for user %s: %d (%s)", + pi->pam_user, pam_status, + pam_strerror(pamh,pam_status)); + } + } + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + if (pam_status != PAM_SUCCESS) { + /* don't log if quiet_mode is on and pam_status is + * User not known to the underlying authentication module + */ + if (!quiet_mode || pam_status != 10) { + logger(pamh, LOG_NOTICE, + "Authentication failed for user %s: %d (%s)", + pi->pam_user, pam_status, + pam_strerror(pamh,pam_status)); + } + } + break; + case SSS_PAM_CHAUTHTOK: + if (pam_status != PAM_SUCCESS) { + logger(pamh, LOG_NOTICE, + "Password change failed for user %s: %d (%s)", + pi->pam_user, pam_status, + pam_strerror(pamh,pam_status)); + } + break; + case SSS_PAM_ACCT_MGMT: + if (pam_status != PAM_SUCCESS) { + /* don't log if quiet_mode is on and pam_status is + * User not known to the underlying authentication module + */ + if (!quiet_mode || pam_status != 10) { + logger(pamh, LOG_NOTICE, + "Access denied for user %s: %d (%s)", + pi->pam_user, pam_status, + pam_strerror(pamh,pam_status)); + } + } + break; + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_SETCRED: + case SSS_PAM_CLOSE_SESSION: + case SSS_PAM_PREAUTH: + break; + default: + D(("Illegal task [%#x]", task)); + pam_status = PAM_SYSTEM_ERR; + } + +done: + if (buf != NULL ) { + sss_erase_mem_securely((void *)buf, rd.len); + free(buf); + } + free(repbuf); + + return pam_status; +} + +static int prompt_password(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); + sss_erase_mem_securely((void *)answer, strlen(answer)); + free(answer); + answer=NULL; + if (pi->pam_authtok == NULL) { + return PAM_BUF_ERR; + } + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD; + pi->pam_authtok_size=strlen(pi->pam_authtok); + } + + return PAM_SUCCESS; +} + +static int prompt_2fa(pam_handle_t *pamh, struct pam_items *pi, + bool second_factor_optional, + const char *prompt_fa1, const char *prompt_fa2) +{ + int ret; + const struct pam_conv *conv; + const struct pam_message *mesg[2] = { NULL, NULL }; + struct pam_message m[2] = { {0}, {0} }; + struct pam_response *resp = NULL; + size_t needed_size; + + ret = pam_get_item(pamh, PAM_CONV, (const void **) &conv); + if (ret != PAM_SUCCESS) { + return ret; + } + if (conv == NULL || conv->conv == NULL) { + logger(pamh, LOG_ERR, "No conversation function"); + return PAM_SYSTEM_ERR; + } + + m[0].msg_style = PAM_PROMPT_ECHO_OFF; + m[0].msg = prompt_fa1; + m[1].msg_style = PAM_PROMPT_ECHO_OFF; + m[1].msg = prompt_fa2; + + mesg[0] = (const struct pam_message *) m; + /* The following assignment might look a bit odd but is recommended in the + * pam_conv man page to make sure that the second argument of the PAM + * conversation function can be interpreted in two different ways. + * Basically it is important that both the actual struct pam_message and + * the pointers to the struct pam_message are arrays. Since the assignment + * makes clear that mesg[] and (*mesg)[] are arrays it should be kept this + * way and not be replaced by other equivalent assignments. */ + mesg[1] = & (( *mesg )[1]); + + ret = conv->conv(2, mesg, &resp, conv->appdata_ptr); + if (ret != PAM_SUCCESS) { + D(("Conversation failure: %s.", pam_strerror(pamh, ret))); + return ret; + } + + if (resp == NULL) { + D(("response expected, but resp==NULL")); + return PAM_SYSTEM_ERR; + } + + if (resp[0].resp == NULL || *(resp[0].resp) == '\0') { + D(("Missing factor.")); + ret = PAM_CRED_INSUFFICIENT; + goto done; + } + + if (resp[1].resp == NULL || *(resp[1].resp) == '\0') { + /* Missing second factor, assume first factor contains combined 2FA + * credentials if the second factor is not optional. If it is optional + * then it is assumed that the first factor contain the password. */ + pi->pam_authtok = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); + if (pi->pam_authtok == NULL) { + D(("strndup failed.")); + ret = PAM_BUF_ERR; + goto done; + } + pi->pam_authtok_size = strlen(pi->pam_authtok) + 1; + pi->pam_authtok_type = second_factor_optional + ? SSS_AUTHTOK_TYPE_PASSWORD + : SSS_AUTHTOK_TYPE_2FA_SINGLE; + } else if (pi->pam_service != NULL && strcmp(pi->pam_service, "sshd") == 0 + && strcmp(resp[0].resp, resp[1].resp) == 0) { + /* Special handling for SSH with password authentication (ssh's + * 'PasswordAuthentication' option. In this mode the ssh client + * directly prompts the user for a password and the prompts we are + * sending are ignored. Since we send two prompts ssh * will create two + * response as well with the same content. We assume that the combined + * 2FA credentials are used even if the second factor is optional + * because there is no indication about the intention of the user. As a + * result we prefer the more secure variant. */ + + pi->pam_authtok = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); + if (pi->pam_authtok == NULL) { + D(("strndup failed.")); + ret = PAM_BUF_ERR; + goto done; + } + pi->pam_authtok_size = strlen(pi->pam_authtok) + 1; + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_2FA_SINGLE; + } else { + + ret = sss_auth_pack_2fa_blob(resp[0].resp, 0, resp[1].resp, 0, NULL, 0, + &needed_size); + if (ret != EAGAIN) { + D(("sss_auth_pack_2fa_blob failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + pi->pam_authtok = malloc(needed_size); + if (pi->pam_authtok == NULL) { + D(("malloc failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + ret = sss_auth_pack_2fa_blob(resp[0].resp, 0, resp[1].resp, 0, + (uint8_t *) pi->pam_authtok, needed_size, + &needed_size); + if (ret != EOK) { + D(("sss_auth_pack_2fa_blob failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + pi->pam_authtok_size = needed_size; + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_2FA; + pi->first_factor = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); + if (pi->first_factor == NULL) { + D(("strndup failed.")); + ret = PAM_BUF_ERR; + goto done; + } + } + + ret = PAM_SUCCESS; + +done: + if (resp != NULL) { + if (resp[0].resp != NULL) { + sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); + free(resp[0].resp); + } + if (resp[1].resp != NULL) { + sss_erase_mem_securely((void *)resp[1].resp, strlen(resp[1].resp)); + free(resp[1].resp); + } + + free(resp); + resp = NULL; + } + + 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); + sss_erase_mem_securely((void *)answer, strlen(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; +} + +static int prompt_oauth2(pam_handle_t *pamh, struct pam_items *pi) +{ + char *answer = NULL; + char *msg; + int ret; + + if (pi->oauth2_url_complete != NULL) { + ret = asprintf(&msg, _("Authenticate at %1$s and press ENTER."), + pi->oauth2_url_complete); + } else { + ret = asprintf(&msg, _("Authenticate with PIN %1$s at %2$s and press " + "ENTER."), pi->oauth2_pin, pi->oauth2_url); + } + if (ret == -1) { + return PAM_SYSTEM_ERR; + } + + ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, msg, NULL, &answer); + free(msg); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return ret; + } + + /* We don't care about answer here. We just need to notify that the + * authentication has finished. */ + free(answer); + + pi->pam_authtok = strdup(pi->oauth2_pin); + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_OAUTH2; + pi->pam_authtok_size=strlen(pi->oauth2_pin); + + return PAM_SUCCESS; +} + +static int prompt_passkey(pam_handle_t *pamh, struct pam_items *pi) +{ + int ret; + const struct pam_conv *conv; + const struct pam_message *mesg[4] = { NULL, NULL, NULL, NULL }; + struct pam_message m[4] = { {0}, {0}, {0}, {0} }; + struct pam_response *resp = NULL; + bool kerberos_preauth; + bool prompt_pin = false; + int pin_idx = 0; + int msg_idx = 0; + size_t needed_size; + + ret = pam_get_item(pamh, PAM_CONV, (const void **) &conv); + if (ret != PAM_SUCCESS) { + return ret; + } + if (conv == NULL || conv->conv == NULL) { + logger(pamh, LOG_ERR, "No conversation function"); + return PAM_SYSTEM_ERR; + } + + /* check device capabilities + */ + if ((strcasecmp(pi->passkey_devinfo, "dopin") != 0) && + (strcasecmp(pi->passkey_devinfo, "pinonly") != 0) && + (strcasecmp(pi->passkey_devinfo, "pinuv") != 0)) { + /* invalid device or no connected device + * fallback to passord + */ + return EIO; + } + kerberos_preauth = pi->passkey_key != NULL ? true : false; + if (!kerberos_preauth) { + m[msg_idx].msg_style = PAM_TEXT_INFO; + m[msg_idx].msg = PASSKEY_LOCAL_AUTH_MSG; + msg_idx++; + } + + if ( strcasecmp(pi->passkey_prompt_pin, "true") == 0) { + if ((strcasecmp(pi->passkey_devinfo, "dopin") == 0) || + (strcasecmp(pi->passkey_devinfo, "pinonly") == 0)) { + prompt_pin = true; + + m[msg_idx].msg_style = PAM_PROMPT_ECHO_OFF; + m[msg_idx].msg = PASSKEY_DEFAULT_PIN_MSG; + pin_idx = msg_idx; + msg_idx++; + } else { + /* let try UV according device verification capabilities */ + prompt_pin = false; + + /* Prompt to remind the user to perform verification (.eg fingerprint) */ + m[msg_idx].msg_style = PAM_TEXT_INFO; + m[msg_idx].msg = _("Perform User Verification on your device"); + msg_idx++; + } + } + + mesg[0] = (const struct pam_message *) m; + /* The following assignment might look a bit odd but is recommended in the + * pam_conv man page to make sure that the second argument of the PAM + * conversation function can be interpreted in two different ways. + * Basically it is important that both the actual struct pam_message and + * the pointers to the struct pam_message are arrays. Since the assignment + * makes clear that mesg[] and (*mesg)[] are arrays it should be kept this + * way and not be replaced by other equivalent assignments. */ + for (int i = 1; i < msg_idx; i++) { + mesg[i] = & (( *mesg )[i]); + } + + ret = conv->conv(msg_idx, mesg, &resp, conv->appdata_ptr); + if (ret != PAM_SUCCESS) { + D(("Conversation failure: %s.", pam_strerror(pamh, ret))); + return ret; + } + + if (kerberos_preauth) { + if (!prompt_pin) { + resp[pin_idx].resp = NULL; + /* fix passkey_child_credentials.c when NO PIN */ + resp[pin_idx].resp = strdup ("NULL"); + /* end fix */ +} + + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY_KRB; + sss_auth_passkey_calc_size(pi->passkey_prompt_pin, + pi->passkey_key, + resp[pin_idx].resp, + &needed_size); + + pi->pam_authtok = malloc(needed_size); + if (pi->pam_authtok == NULL) { + D(("malloc failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + sss_auth_pack_passkey_blob((uint8_t *)pi->pam_authtok, + pi->passkey_prompt_pin, pi->passkey_key, + resp[pin_idx].resp); + + } else { + if (!prompt_pin) { + /* user verification = false, SSS_AUTHTOK_TYPE_PASSKEY will be reset to + * SSS_AUTHTOK_TYPE_NULL in PAM responder + */ + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY; + pi->pam_authtok = NULL; + pi->pam_authtok_size = 0; + /* fix passkey_child_credentials.c when NO PIN */ + pi->pam_authtok = strdup("null"); + pi->pam_authtok_size = strlen(pi->pam_authtok); + /* end fix */ + ret = PAM_SUCCESS; + goto done; + } else { + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY; + pi->pam_authtok = strdup(resp[pin_idx].resp); + needed_size = strlen(pi->pam_authtok); + } + } + + pi->pam_authtok_size = needed_size; + + /* Fallback to password auth if no PIN was entered */ + if (prompt_pin) { + if (resp[pin_idx].resp == NULL || resp[pin_idx].resp[0] == '\0') { + ret = EIO; + goto done; + } + } + + ret = PAM_SUCCESS; + +done: + if (resp != NULL) { + if (resp[pin_idx].resp != NULL) { + sss_erase_mem_securely((void *)resp[pin_idx].resp, + strlen(resp[pin_idx].resp)); + free(resp[pin_idx].resp); + } + + free(resp); + resp = NULL; + } + + return ret; +} + +static int passkey_get_current_device_info(pam_handle_t *pamh, struct pam_items *pi, + bool quiet_mode) +{ + int ret; + // const char* prompt = _("Connect your passkey device, then press ENTER."); + const char* prompt = "Connect your passkey device, then press ENTER."; + char* answer; + + int ori_flags = pi->flags; + + pi->flags |= PAM_CLI_FLAGS_REQUIRE_PASSKEY_CACHED_DEVINFO; + + ret = send_and_receive(pamh, pi, SSS_PAM_PASSKEY_PREAUTH, quiet_mode); + if (ret != PAM_SUCCESS) { + D(("send_and_receive returned [%d] (ignored) during passkey_preauth", ret)); + ret = PAM_SUCCESS; + } + pi->flags = ori_flags; + + if (pi->passkey_devinfo == NULL) { + ret = PAM_SYSTEM_ERR; + goto done; + } + + if (strcasecmp(pi->passkey_devinfo, "dopin") == 0) { + /* this is an authentication retry (after a wrong UV or PIN) + * which assumes the device is connected + * the user will be prompted for a PIN + */ + goto done; + } + if (strcasecmp(pi->passkey_devinfo, "nodev") == 0) { + /* the cache indicates that no valid device has been + * connected, there we shall fallback to password authentication + */ + goto done; + } + + /* user shall confirm or reconfirm its device is ready */ + ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, prompt, NULL, &answer); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed ignored - we do inot need response but just ENTER")); + ret = PAM_SUCCESS; + } + + if ((strcasecmp(pi->passkey_devinfo, "pinuv") == 0) || + (strcasecmp(pi->passkey_devinfo, "pinonly") == 0)) { + /* OK */ + goto done; + } + /* cache is empty or indicate invalid info + * request fresh info + */ + + if (pi->passkey_key != NULL) { + + size_t needed_size; + + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSKEY_KRB; + sss_auth_passkey_calc_size(pi->passkey_prompt_pin, + pi->passkey_key, + "", /* empty PIN but unsued */ + &needed_size); + + pi->pam_authtok = malloc(needed_size); + if (pi->pam_authtok == NULL) { + D(("malloc failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + sss_auth_pack_passkey_blob((uint8_t *)pi->pam_authtok, + pi->passkey_prompt_pin, + pi->passkey_key, + ""); + pi->pam_authtok_size = needed_size; + } else { + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; + pi->pam_authtok = NULL; + pi->pam_authtok_size = 0; + } + ret = send_and_receive(pamh, pi, SSS_PAM_PASSKEY_PREAUTH, quiet_mode); + if (ret != PAM_SUCCESS) { + D(("send_and_receive returned [%d] (ignored) during passkey_preauth", ret)); + ret = PAM_SUCCESS; + } + + if (pi->passkey_devinfo != NULL && + ((strcasecmp(pi->passkey_devinfo, "pinuv") == 0) || + (strcasecmp(pi->passkey_devinfo, "pinonly") == 0))) { + /* OK */ + goto done; + } + + /* inform user she/he shall fallback to password authntication and wait its ENTER + */ + { + char* _prompt; + int r = asprintf(&_prompt, "%s %s", + pi->passkey_devinfo == NULL ? "SYSTEM ERROR DEVINFO" : pi->passkey_devinfo, + " Fallback to password authentication"); + if (r == -1) { + ret = ENOMEM; + goto done; + } + + ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, _prompt, NULL, &answer); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed ignored - we doi not need response but just ENTER")); + ret = PAM_SUCCESS; + } + free (_prompt); + } + + if (pi->passkey_devinfo == NULL) { + ret = PAM_SYSTEM_ERR; + goto done; + } + ret = PAM_SUCCESS; + done: + return ret; +} + +#define SC_PROMPT_FMT "PIN for %s: " + +#ifndef discard_const +#define discard_const(ptr) ((void *)((uintptr_t)(ptr))) +#endif + +#define CERT_SEL_PROMPT_FMT "%s" +#define SEL_TITLE discard_const("Please select a certificate") + +static int prompt_multi_cert_gdm(pam_handle_t *pamh, struct pam_items *pi) +{ +#ifdef HAVE_GDM_PAM_EXTENSIONS + int ret; + size_t cert_count = 0; + size_t c; + const struct pam_conv *conv; + struct cert_auth_info *cai; + GdmPamExtensionChoiceListRequest *request = NULL; + GdmPamExtensionChoiceListResponse *response = NULL; + struct pam_message prompt_message; + const struct pam_message *prompt_messages[1]; + struct pam_response *reply = NULL; + char *prompt; + + if (!GDM_PAM_EXTENSION_SUPPORTED(GDM_PAM_EXTENSION_CHOICE_LIST)) { + return ENOTSUP; + } + + if (pi->cert_list == NULL) { + return EINVAL; + } + + DLIST_FOR_EACH(cai, pi->cert_list) { + cert_count++; + } + + ret = pam_get_item(pamh, PAM_CONV, (const void **)&conv); + if (ret != PAM_SUCCESS) { + ret = EIO; + return ret; + } + + request = calloc(1, GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_SIZE(cert_count)); + if (request == NULL) { + ret = ENOMEM; + goto done; + } + GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_INIT(request, SEL_TITLE, cert_count); + + c = 0; + DLIST_FOR_EACH(cai, pi->cert_list) { + ret = asprintf(&prompt, CERT_SEL_PROMPT_FMT, cai->prompt_str); + if (ret == -1) { + ret = ENOMEM; + goto done; + } + free(cai->choice_list_id); + ret = asprintf(&cai->choice_list_id, "%zu", c); + if (ret == -1) { + cai->choice_list_id = NULL; + free(prompt); + ret = ENOMEM; + goto done; + } + + request->list.items[c].key = cai->choice_list_id; + request->list.items[c++].text = prompt; + } + + GDM_PAM_EXTENSION_MESSAGE_TO_BINARY_PROMPT_MESSAGE(request, + &prompt_message); + prompt_messages[0] = &prompt_message; + + ret = conv->conv(1, prompt_messages, &reply, conv->appdata_ptr); + if (ret != PAM_SUCCESS) { + ret = EIO; + goto done; + } + + ret = EIO; + response = GDM_PAM_EXTENSION_REPLY_TO_CHOICE_LIST_RESPONSE(reply); + if (response->key == NULL) { + goto done; + } + + DLIST_FOR_EACH(cai, pi->cert_list) { + if (strcmp(response->key, cai->choice_list_id) == 0) { + pam_info(pamh, "Certificate ‘%s’ selected", cai->key_id); + pi->selected_cert = cai; + ret = 0; + break; + } + } + +done: + if (request != NULL) { + for (c = 0; c < cert_count; c++) { + free(discard_const(request->list.items[c++].text)); + } + free(request); + } + free(response); + + return ret; +#else + return ENOTSUP; +#endif +} + +#define TEXT_CERT_SEL_PROMPT_FMT "%s\n[%zu]:\n%s\n" +#define TEXT_SEL_TITLE discard_const("Please select a certificate by typing " \ + "the corresponding number\n") + +static int prompt_multi_cert(pam_handle_t *pamh, struct pam_items *pi) +{ + int ret; + size_t cert_count = 0; + size_t tries = 0; + long int resp = -1; + struct cert_auth_info *cai; + char *prompt; + char *tmp; + char *answer; + char *ep; + + /* First check if gdm extension is supported */ + ret = prompt_multi_cert_gdm(pamh, pi); + if (ret != ENOTSUP) { + return ret; + } + + if (pi->cert_list == NULL) { + return EINVAL; + } + + prompt = strdup(TEXT_SEL_TITLE); + if (prompt == NULL) { + return ENOMEM; + } + + DLIST_FOR_EACH(cai, pi->cert_list) { + cert_count++; + ret = asprintf(&tmp, TEXT_CERT_SEL_PROMPT_FMT, prompt, cert_count, + cai->prompt_str); + free(prompt); + if (ret == -1) { + return ENOMEM; + } + + prompt = tmp; + } + + do { + ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_ON, prompt, NULL, + &answer); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + break; + } + + errno = 0; + resp = strtol(answer, &ep, 10); + if (errno == 0 && *ep == '\0' && resp > 0 && resp <= cert_count) { + /* do not free answer ealier because ep is pointing to it */ + free(answer); + break; + } + free(answer); + resp = -1; + } while (++tries < 5); + free(prompt); + + pi->selected_cert = NULL; + ret = ENOENT; + if (resp > 0 && resp <= cert_count) { + cert_count = 0; + DLIST_FOR_EACH(cai, pi->cert_list) { + cert_count++; + if (resp == cert_count) { + pam_info(pamh, "Certificate ‘%s’ selected", cai->key_id); + pi->selected_cert = cai; + ret = 0; + break; + } + } + } + + return ret; +} + +#define SC_INSERT_PROMPT _("Please (re)insert (different) Smartcard") + +static int prompt_sc_pin(pam_handle_t *pamh, struct pam_items *pi) +{ + int ret; + char *answer = NULL; + char *prompt = NULL; + size_t needed_size; + const struct pam_conv *conv; + const struct pam_message *mesg[2] = { NULL, NULL }; + struct pam_message m[2] = { { 0 }, { 0 } }; + struct pam_response *resp = NULL; + struct cert_auth_info *cai = pi->selected_cert; + + if (cai == NULL && (SERVICE_IS_GDM_SMARTCARD(pi) + || (pi->flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH))) { + ret = asprintf(&prompt, SC_INSERT_PROMPT); + } else if (cai == NULL || cai->token_name == NULL + || *cai->token_name == '\0') { + return PAM_SYSTEM_ERR; + } else { + ret = asprintf(&prompt, SC_PROMPT_FMT, cai->token_name); + } + + if (ret == -1) { + D(("asprintf failed.")); + return PAM_SYSTEM_ERR; + } + + if (cai == NULL) { + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, prompt, NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("Conversation failure: %s, ignored", pam_strerror(pamh, ret))); + } + } + + if (pi->user_name_hint) { + ret = pam_get_item(pamh, PAM_CONV, (const void **)&conv); + if (ret != PAM_SUCCESS) { + free(prompt); + return ret; + } + if (conv == NULL || conv->conv == NULL) { + logger(pamh, LOG_ERR, "No conversation function"); + free(prompt); + return PAM_SYSTEM_ERR; + } + + m[0].msg_style = PAM_PROMPT_ECHO_OFF; + m[0].msg = prompt; + m[1].msg_style = PAM_PROMPT_ECHO_ON; + m[1].msg = "User name hint: "; + + mesg[0] = (const struct pam_message *)m; + /* The following assignment might look a bit odd but is recommended in the + * pam_conv man page to make sure that the second argument of the PAM + * conversation function can be interpreted in two different ways. + * Basically it is important that both the actual struct pam_message and + * the pointers to the struct pam_message are arrays. Since the assignment + * makes clear that mesg[] and (*mesg)[] are arrays it should be kept this + * way and not be replaced by other equivalent assignments. */ + mesg[1] = &((*mesg)[1]); + + ret = conv->conv(2, mesg, &resp, conv->appdata_ptr); + free(prompt); + if (ret != PAM_SUCCESS) { + D(("Conversation failure: %s.", pam_strerror(pamh, ret))); + return ret; + } + + if (resp == NULL) { + D(("response expected, but resp==NULL")); + return PAM_SYSTEM_ERR; + } + + if (resp[0].resp == NULL || *(resp[0].resp) == '\0') { + D(("Missing PIN.")); + ret = PAM_CRED_INSUFFICIENT; + goto done; + } + + answer = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); + sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); + free(resp[0].resp); + resp[0].resp = NULL; + if (answer == NULL) { + D(("strndup failed")); + ret = PAM_BUF_ERR; + goto done; + } + + if (resp[1].resp != NULL && *(resp[1].resp) != '\0') { + ret = pam_set_item(pamh, PAM_USER, resp[1].resp); + free(resp[1].resp); + resp[1].resp = NULL; + if (ret != PAM_SUCCESS) { + D(("Failed to set PAM_USER with user name hint [%s]", + pam_strerror(pamh, ret))); + goto done; + } + + ret = pam_get_item(pamh, PAM_USER, (const void **)&(pi->pam_user)); + if (ret != PAM_SUCCESS) { + D(("Failed to get PAM_USER with user name hint [%s]", + pam_strerror(pamh, ret))); + goto done; + } + + pi->pam_user_size = strlen(pi->pam_user) + 1; + } + } else { + ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, prompt, NULL, + &answer); + free(prompt); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return ret; + } + } + + if (cai == NULL) { + /* it is expected that the user just replaces the Smartcard which + * would trigger gdm to restart the PAM module, so it is not + * expected that this part of the code is reached. */ + ret = PAM_AUTHINFO_UNAVAIL; + goto done; + } + + if (answer == NULL || *answer == '\0') { + D(("Missing PIN.")); + ret = PAM_CRED_INSUFFICIENT; + goto done; + } else { + + ret = sss_auth_pack_sc_blob(answer, 0, cai->token_name, 0, + cai->module_name, 0, + cai->key_id, 0, + cai->label, 0, + NULL, 0, &needed_size); + if (ret != EAGAIN) { + D(("sss_auth_pack_sc_blob failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + pi->pam_authtok = malloc(needed_size); + if (pi->pam_authtok == NULL) { + D(("malloc failed.")); + ret = PAM_BUF_ERR; + goto done; + } + + ret = sss_auth_pack_sc_blob(answer, 0, cai->token_name, 0, + cai->module_name, 0, + cai->key_id, 0, + cai->label, 0, + (uint8_t *) pi->pam_authtok, needed_size, + &needed_size); + if (ret != EOK) { + D(("sss_auth_pack_sc_blob failed.")); + free((void *)pi->pam_authtok); + ret = PAM_BUF_ERR; + goto done; + } + + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_SC_PIN; + pi->pam_authtok_size = needed_size; + } + + ret = PAM_SUCCESS; + +done: + if (answer != NULL) { + sss_erase_mem_securely((void *)answer, strlen(answer)); + free(answer); + answer=NULL; + } + + if (resp != NULL) { + if (resp[0].resp != NULL) { + sss_erase_mem_securely((void *)resp[0].resp, strlen(resp[0].resp)); + free(resp[0].resp); + } + if (resp[1].resp != NULL) { + sss_erase_mem_securely((void *)resp[1].resp, strlen(resp[1].resp)); + free(resp[1].resp); + } + + free(resp); + resp = NULL; + } + + return ret; +} + +static int prompt_new_password(pam_handle_t *pamh, struct pam_items *pi) +{ + int ret; + char *answer = NULL; + + ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, + _("New Password: "), + _("Reenter new Password: "), + &answer); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + return ret; + } + if (answer == NULL) { + pi->pam_newauthtok = NULL; + pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_EMPTY; + pi->pam_newauthtok_size=0; + } else { + pi->pam_newauthtok = strdup(answer); + sss_erase_mem_securely((void *)answer, strlen(answer)); + free(answer); + answer=NULL; + if (pi->pam_newauthtok == NULL) { + return PAM_BUF_ERR; + } + pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_PASSWORD; + pi->pam_newauthtok_size=strlen(pi->pam_newauthtok); + } + + return PAM_SUCCESS; +} + +static void eval_argv(pam_handle_t *pamh, int argc, const char **argv, + uint32_t *flags, int *retries, bool *quiet_mode, + const char **domains) +{ + char *ep; + + *quiet_mode = false; + + for (; argc-- > 0; ++argv) { + if (strcmp(*argv, "forward_pass") == 0) { + *flags |= PAM_CLI_FLAGS_FORWARD_PASS; + } else if (strcmp(*argv, "use_first_pass") == 0) { + *flags |= PAM_CLI_FLAGS_USE_FIRST_PASS; + } else if (strcmp(*argv, "use_authtok") == 0) { + *flags |= PAM_CLI_FLAGS_USE_AUTHTOK; + } else if (strncmp(*argv, OPT_DOMAINS_KEY, strlen(OPT_DOMAINS_KEY)) == 0) { + if (*(*argv+strlen(OPT_DOMAINS_KEY)) == '\0') { + logger(pamh, LOG_ERR, "Missing argument to option domains."); + *domains = ""; + } else { + *domains = *argv+strlen(OPT_DOMAINS_KEY); + } + + } else if (strncmp(*argv, OPT_RETRY_KEY, strlen(OPT_RETRY_KEY)) == 0) { + if (*(*argv+6) == '\0') { + logger(pamh, LOG_ERR, "Missing argument to option retry."); + *retries = 0; + } else { + errno = 0; + *retries = strtol(*argv+6, &ep, 10); + if (errno != 0) { + D(("strtol failed [%d][%s]", errno, strerror(errno))); + *retries = 0; + } + if (*ep != '\0') { + logger(pamh, LOG_ERR, "Argument to option retry contains " + "extra characters."); + *retries = 0; + } + if (*retries < 0) { + logger(pamh, LOG_ERR, "Argument to option retry must not " + "be negative."); + *retries = 0; + } + } + } else if (strcmp(*argv, "quiet") == 0) { + *quiet_mode = true; + } else if (strcmp(*argv, "allow_chauthtok_by_root") == 0) { + *flags |= PAM_CLI_FLAGS_ALLOW_CHAUTHTOK_BY_ROOT; + } else if (strcmp(*argv, "ignore_unknown_user") == 0) { + *flags |= PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER; + } else if (strcmp(*argv, "ignore_authinfo_unavail") == 0) { + *flags |= PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL; + } else if (strcmp(*argv, "use_2fa") == 0) { + *flags |= PAM_CLI_FLAGS_USE_2FA; + } else if (strcmp(*argv, "allow_missing_name") == 0) { + *flags |= PAM_CLI_FLAGS_ALLOW_MISSING_NAME; + } else if (strcmp(*argv, "prompt_always") == 0) { + *flags |= PAM_CLI_FLAGS_PROMPT_ALWAYS; + } else if (strcmp(*argv, "try_cert_auth") == 0) { + *flags |= PAM_CLI_FLAGS_TRY_CERT_AUTH; + } else if (strcmp(*argv, "require_cert_auth") == 0) { + *flags |= PAM_CLI_FLAGS_REQUIRE_CERT_AUTH; + } else { + logger(pamh, LOG_WARNING, "unknown option: %s", *argv); + } + } + + return; +} + +static int prompt_by_config(pam_handle_t *pamh, struct pam_items *pi) +{ + size_t c; + int ret = PAM_SUCCESS; + + if (pi->pc == NULL || *pi->pc == NULL) { + return PAM_SYSTEM_ERR; + } + + 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: + if (pi->password_prompting) { + ret = prompt_2fa(pamh, pi, true, pc_get_2fa_1st_prompt(pi->pc[c]), + pc_get_2fa_2nd_prompt(pi->pc[c])); + } else { + ret = prompt_2fa(pamh, pi, false, 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_PASSKEY: + ret = prompt_passkey(pamh, pi); + /* no more supported. need devinfo. Cannot be done here + pc_get_passkey_inter_prompt(pi->pc[c]), + pc_get_passkey_touch_prompt(pi->pc[c])); + */ + break; + case PC_TYPE_SC_PIN: + ret = prompt_sc_pin(pamh, pi); + /* Todo: add extra string option */ + break; + default: + ret = PAM_SYSTEM_ERR; + } + + /* 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) +{ + int ret; + const char *pin = NULL; + + if ((flags & PAM_CLI_FLAGS_USE_FIRST_PASS) + || ( pi->pamstack_authtok != NULL + && *(pi->pamstack_authtok) != '\0' + && !(flags & PAM_CLI_FLAGS_PROMPT_ALWAYS))) { + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PAM_STACKED; + pi->pam_authtok = strdup(pi->pamstack_authtok); + if (pi->pam_authtok == NULL) { + D(("option use_first_pass set, but no password found")); + return PAM_BUF_ERR; + } + pi->pam_authtok_size = strlen(pi->pam_authtok); + } else { + if (pi->oauth2_url != NULL) { + /* Prompt config is not supported for OAuth2. */ + ret = prompt_oauth2(pamh, pi); + } else if (pi->pc != NULL) { + ret = prompt_by_config(pamh, pi); + } 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 if (SERVICE_IS_GDM_SMARTCARD(pi) + || (pi->flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH)) { + /* Use pin prompt as fallback for gdm-smartcard */ + ret = prompt_sc_pin(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, true, _("First Factor: "), + _("Second Factor (optional): ")); + } else { + ret = prompt_2fa(pamh, pi, false, _("First Factor: "), + _("Second Factor: ")); + } + } else if (pi->passkey_prompt_pin != NULL && pi->passkey_devinfo != NULL) { + ret = prompt_passkey(pamh, pi); + /* Fallback to password auth if no PIN was entered */ + if (ret == EIO) { + ret = prompt_password(pamh, pi, _("Password: ")); + if (pi->pam_authtok_size == 0) { + D(("Empty password failure")); + pi->passkey_prompt_pin = NULL; + return PAM_AUTHTOK_ERR; + } + } + } else { + ret = prompt_password(pamh, pi, _("Password: ")); + } + } + if (ret != PAM_SUCCESS) { + D(("failed to get password from user")); + return ret; + } + + if (flags & PAM_CLI_FLAGS_FORWARD_PASS) { + if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_PASSWORD + || pi->pam_authtok_type == SSS_AUTHTOK_TYPE_PAM_STACKED) { + ret = pam_set_item(pamh, PAM_AUTHTOK, pi->pam_authtok); + } else if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_SC_PIN) { + pin = sss_auth_get_pin_from_sc_blob((uint8_t *) pi->pam_authtok, + pi->pam_authtok_size); + if (pin != NULL) { + ret = pam_set_item(pamh, PAM_AUTHTOK, pin); + } else { + ret = PAM_SYSTEM_ERR; + } + } else if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_2FA + && pi->first_factor != NULL) { + ret = pam_set_item(pamh, PAM_AUTHTOK, pi->first_factor); + } else { + ret = PAM_SYSTEM_ERR; + } + if (ret != PAM_SUCCESS) { + D(("Failed to set PAM_AUTHTOK [%s], " + "authtok may not be available for other modules", + pam_strerror(pamh,ret))); + } + } + } + + return PAM_SUCCESS; +} + +static int check_authtok_data(pam_handle_t *pamh, struct pam_items *pi) +{ + int pam_status; + int *authtok_type; + size_t *authtok_size; + char *authtok_data; + + pam_status = pam_get_data(pamh, PAM_SSS_AUTHOK_TYPE, + (const void **) &authtok_type); + if (pam_status != PAM_SUCCESS) { + D(("pam_get_data failed.")); + return EIO; + } + + pam_status = pam_get_data(pamh, PAM_SSS_AUTHOK_SIZE, + (const void **) &authtok_size); + if (pam_status != PAM_SUCCESS) { + D(("pam_get_data failed.")); + return EIO; + } + + pam_status = pam_get_data(pamh, PAM_SSS_AUTHOK_DATA, + (const void **) &authtok_data); + if (pam_status != PAM_SUCCESS) { + D(("pam_get_data failed.")); + return EIO; + } + + pi->pam_authtok = malloc(*authtok_size); + if (pi->pam_authtok == NULL) { + D(("malloc failed.")); + return ENOMEM; + } + memcpy(pi->pam_authtok, authtok_data, *authtok_size); + + pi->pam_authtok_type = *authtok_type; + pi->pam_authtok_size = *authtok_size; + + return 0; +} + +static int keep_authtok_data(pam_handle_t *pamh, struct pam_items *pi) +{ + int pam_status; + int *authtok_type; + size_t *authtok_size; + char *authtok_data; + + authtok_type = malloc(sizeof(int)); + if (authtok_type == NULL) { + D(("malloc failed.")); + return ENOMEM; + } + *authtok_type = pi->pam_authtok_type; + + pam_status = pam_set_data(pamh, PAM_SSS_AUTHOK_TYPE, authtok_type, + free_exp_data); + if (pam_status != PAM_SUCCESS) { + free(authtok_type); + D(("pam_set_data failed.")); + return EIO; + } + + authtok_size = malloc(sizeof(size_t)); + if (authtok_size == NULL) { + D(("malloc failed.")); + return ENOMEM; + } + *authtok_size = pi->pam_authtok_size; + + pam_status = pam_set_data(pamh, PAM_SSS_AUTHOK_SIZE, authtok_size, + free_exp_data); + if (pam_status != PAM_SUCCESS) { + free(authtok_size); + D(("pam_set_data failed.")); + return EIO; + } + + authtok_data = malloc(pi->pam_authtok_size); + if (authtok_data == NULL) { + D(("malloc failed.")); + return ENOMEM; + } + memcpy(authtok_data, pi->pam_authtok, pi->pam_authtok_size); + + pam_status = pam_set_data(pamh, PAM_SSS_AUTHOK_DATA, authtok_data, + free_exp_data); + if (pam_status != PAM_SUCCESS) { + free(authtok_data); + D(("pam_set_data failed.")); + return EIO; + } + + return 0; +} + +static int get_authtok_for_password_change(pam_handle_t *pamh, + struct pam_items *pi, + uint32_t flags, + int pam_flags) +{ + int ret; + const int *exp_data = NULL; + ret = pam_get_data(pamh, PWEXP_FLAG, (const void **) &exp_data); + if (ret != PAM_SUCCESS) { + exp_data = NULL; + } + + if (pam_flags & PAM_PRELIM_CHECK) { + if (!(flags & PAM_CLI_FLAGS_ALLOW_CHAUTHTOK_BY_ROOT) && getuid() == 0 && !exp_data ) + return PAM_SUCCESS; + + 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, true, + _("First Factor (Current Password): "), + _("Second Factor (optional): ")); + } else { + ret = prompt_2fa(pamh, pi, false, + _("First Factor (Current Password): "), + _("Second Factor: ")); + } + } else if ((flags & PAM_CLI_FLAGS_USE_FIRST_PASS) + && check_authtok_data(pamh, pi) != 0) { + if (pi->pamstack_oldauthtok == NULL) { + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; + pi->pam_authtok = NULL; + pi->pam_authtok_size = 0; + } else { + pi->pam_authtok = strdup(pi->pamstack_oldauthtok); + if (pi->pam_authtok == NULL) { + D(("strdup failed")); + return PAM_BUF_ERR; + } + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD; + pi->pam_authtok_size = strlen(pi->pam_authtok); + } + ret = PAM_SUCCESS; + } else { + ret = prompt_password(pamh, pi, _("Current Password: ")); + } + if (ret != PAM_SUCCESS) { + D(("failed to get credentials from user")); + return ret; + } + + ret = pam_set_item(pamh, PAM_OLDAUTHTOK, pi->pam_authtok); + if (ret != PAM_SUCCESS) { + D(("Failed to set PAM_OLDAUTHTOK [%s], " + "oldauthtok may not be available", + pam_strerror(pamh,ret))); + return ret; + } + + if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_2FA) { + ret = keep_authtok_data(pamh, pi); + if (ret != 0) { + D(("Failed to store authtok data to pam handle. Password " + "change might fail.")); + } + } + + return PAM_SUCCESS; + } + + if (check_authtok_data(pamh, pi) != 0) { + if (pi->pamstack_oldauthtok == NULL) { + if (getuid() != 0) { + D(("no password found for chauthtok")); + return PAM_BUF_ERR; + } else { + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY; + pi->pam_authtok = NULL; + pi->pam_authtok_size = 0; + } + } else { + pi->pam_authtok = strdup(pi->pamstack_oldauthtok); + if (pi->pam_authtok == NULL) { + D(("strdup failed")); + return PAM_BUF_ERR; + } + pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD; + pi->pam_authtok_size = strlen(pi->pam_authtok); + } + } + + if (flags & PAM_CLI_FLAGS_USE_AUTHTOK) { + pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_PASSWORD; + pi->pam_newauthtok = strdup(pi->pamstack_authtok); + if (pi->pam_newauthtok == NULL) { + D(("option use_authtok set, but no new password found")); + return PAM_BUF_ERR; + } + pi->pam_newauthtok_size = strlen(pi->pam_newauthtok); + } else { + ret = prompt_new_password(pamh, pi); + if (ret != PAM_SUCCESS) { + D(("failed to get new password from user")); + return ret; + } + + if (flags & PAM_CLI_FLAGS_FORWARD_PASS) { + ret = pam_set_item(pamh, PAM_AUTHTOK, pi->pam_newauthtok); + if (ret != PAM_SUCCESS) { + D(("Failed to set PAM_AUTHTOK [%s], " + "oldauthtok may not be available", + pam_strerror(pamh,ret))); + } + } + } + + return PAM_SUCCESS; +} + +#define SC_ENTER_LABEL_FMT "Please insert smart card labeled\n %s" +#define SC_ENTER_FMT "Please insert smart card" + +static int check_login_token_name(pam_handle_t *pamh, struct pam_items *pi, + int retries, bool quiet_mode) +{ + int ret; + int pam_status; + char *login_token_name; + char *prompt = NULL; + uint32_t orig_flags = pi->flags; + + login_token_name = getenv("PKCS11_LOGIN_TOKEN_NAME"); + if (login_token_name == NULL + && !(pi->flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH)) { + return PAM_SUCCESS; + } + + if (login_token_name == NULL) { + ret = asprintf(&prompt, SC_ENTER_FMT); + } else { + ret = asprintf(&prompt, SC_ENTER_LABEL_FMT, login_token_name); + } + if (ret == -1) { + return ENOMEM; + } + + pi->flags |= PAM_CLI_FLAGS_REQUIRE_CERT_AUTH; + + /* TODO: check multiple cert case */ + while (pi->cert_list == NULL || pi->cert_list->token_name == NULL + || (login_token_name != NULL + && strcmp(login_token_name, + pi->cert_list->token_name) != 0)) { + + free_cert_list(pi->cert_list); + pi->cert_list = NULL; + if (retries < 0) { + ret = PAM_AUTHINFO_UNAVAIL; + goto done; + } + retries--; + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, prompt, NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + goto done; + } + + pam_status = send_and_receive(pamh, pi, SSS_PAM_PREAUTH, quiet_mode); + if (pam_status != PAM_SUCCESS) { + D(("send_and_receive returned [%d] during pre-auth", pam_status)); + /* + * Since we are waiting for the right Smartcard to be inserted errors + * can be ignored here. + */ + } + } + + ret = PAM_SUCCESS; + +done: + + pi->flags = orig_flags; + free(prompt); + + return ret; +} + +static int pam_sss(enum sss_cli_command task, pam_handle_t *pamh, + int pam_flags, int argc, const char **argv) +{ + int ret; + int pam_status; + struct pam_items pi = { 0 }; + uint32_t flags = 0; + const int *exp_data; + int *pw_exp_data; + bool retry = false; + bool quiet_mode = false; + int retries = 0; + const char *domains = NULL; + + bindtextdomain(PACKAGE, LOCALEDIR); + + D(("Hello pam_sssd: %#x", task)); + + eval_argv(pamh, argc, argv, &flags, &retries, &quiet_mode, &domains); + + /* Fail all authentication on misconfigured domains= parameter. The admin + * probably wanted to restrict authentication, so it's safer to fail */ + if (domains && strcmp(domains, "") == 0) { + return PAM_SYSTEM_ERR; + } + + pi.requested_domains = domains; + + ret = get_pam_items(pamh, flags, &pi); + if (ret != PAM_SUCCESS) { + D(("get items returned error: %s", pam_strerror(pamh,ret))); + if ((flags & PAM_CLI_FLAGS_TRY_CERT_AUTH) + || (flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) ) { + return PAM_AUTHINFO_UNAVAIL; + } + if (flags & PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER && ret == PAM_USER_UNKNOWN) { + ret = PAM_IGNORE; + } + if (flags & PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL + && ret == PAM_AUTHINFO_UNAVAIL) { + ret = PAM_IGNORE; + } + return ret; + } + + do { + retry = false; + + switch(task) { + case SSS_PAM_AUTHENTICATE: + /* + * Only do preauth if + * - PAM_CLI_FLAGS_USE_FIRST_PASS is not set + * - no password is on the stack or PAM_CLI_FLAGS_PROMPT_ALWAYS is set + * - preauth indicator file exists. + */ + if ( !(flags & PAM_CLI_FLAGS_USE_FIRST_PASS) + && (pi.pam_authtok == NULL + || (flags & PAM_CLI_FLAGS_PROMPT_ALWAYS)) + && access(PAM_PREAUTH_INDICATOR, F_OK) == 0) { + + if (flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) { + /* Do not use PAM_CLI_FLAGS_REQUIRE_CERT_AUTH in the first + * SSS_PAM_PREAUTH run. In case a card is already inserted + * we do not have to prompt to insert a card. */ + pi.flags &= ~PAM_CLI_FLAGS_REQUIRE_CERT_AUTH; + pi.flags |= PAM_CLI_FLAGS_TRY_CERT_AUTH; + } + + pam_status = send_and_receive(pamh, &pi, SSS_PAM_PREAUTH, + quiet_mode); + + pi.flags = flags; + if (pam_status != PAM_SUCCESS) { + D(("send_and_receive returned [%d] during pre-auth", + pam_status)); + /* + * Since we are only interested in the result message + * and will always use password authentication + * as a fallback (except for gdm-smartcard), + * errors can be ignored here. + */ + } + } + + if (flags & PAM_CLI_FLAGS_TRY_CERT_AUTH + && pi.cert_list == NULL) { + D(("No certificates for authentication available.")); + overwrite_and_free_pam_items(&pi); + return PAM_AUTHINFO_UNAVAIL; + } + + if (SERVICE_IS_GDM_SMARTCARD(&pi) + || (flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH)) { + ret = check_login_token_name(pamh, &pi, retries, + quiet_mode); + if (ret != PAM_SUCCESS) { + D(("check_login_token_name failed.\n")); + } + } + if (pi.passkey_prompt_pin != NULL && pi.passkey_devinfo == NULL) { + ret = passkey_get_current_device_info(pamh, &pi, quiet_mode); + if (ret != PAM_SUCCESS) { + D(("passkey_get_current_device_info failed.\n")); + overwrite_and_free_pam_items(&pi); + return ret; + } + } + ret = get_authtok_for_authentication(pamh, &pi, flags); + if (ret != PAM_SUCCESS) { + D(("failed to get authentication token: %s", + pam_strerror(pamh, ret))); + overwrite_and_free_pam_items(&pi); + return ret; + } + break; + case SSS_PAM_CHAUTHTOK: + /* + * Even if we only want to change the (long term) password + * there are cases where more than the password is needed to + * get the needed privileges in a backend to change the + * password. + * + * E.g. with mandatory 2-factor authentication we have to ask + * not only for the current password but for the second + * factor, e.g. the one-time token value, as well. + * + * The means the preauth step has to be done here as well but + * only if + * - PAM_PRELIM_CHECK is set + * - PAM_CLI_FLAGS_USE_FIRST_PASS is not set + * - no password is on the stack or PAM_CLI_FLAGS_PROMPT_ALWAYS is set + * - preauth indicator file exists. + */ + if ( (pam_flags & PAM_PRELIM_CHECK) + && !(flags & PAM_CLI_FLAGS_USE_FIRST_PASS) + && (pi.pam_authtok == NULL + || (flags & PAM_CLI_FLAGS_PROMPT_ALWAYS)) + && access(PAM_PREAUTH_INDICATOR, F_OK) == 0) { + pam_status = send_and_receive(pamh, &pi, SSS_PAM_PREAUTH, + quiet_mode); + if (pam_status != PAM_SUCCESS) { + D(("send_and_receive returned [%d] during pre-auth", + pam_status)); + /* + * Since we are only interested in the result message + * and will always use password authentication + * as a fallback, errors can be ignored here. + */ + } + } + + ret = get_authtok_for_password_change(pamh, &pi, flags, pam_flags); + if (ret != PAM_SUCCESS) { + D(("failed to get tokens for password change: %s", + pam_strerror(pamh, ret))); + overwrite_and_free_pam_items(&pi); + return ret; + } + + if (pam_flags & PAM_PRELIM_CHECK) { + if (pi.pam_authtok_type == SSS_AUTHTOK_TYPE_2FA) { + /* We cannot validate the credentials with an OTP + * token value during PAM_PRELIM_CHECK because it + * would be invalid for the actual password change. So + * we are done. */ + + overwrite_and_free_pam_items(&pi); + return PAM_SUCCESS; + } + task = SSS_PAM_CHAUTHTOK_PRELIM; + } + break; + case SSS_PAM_ACCT_MGMT: + case SSS_PAM_SETCRED: + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_CLOSE_SESSION: + break; + default: + D(("Illegal task [%#x]", task)); + overwrite_and_free_pam_items(&pi); + return PAM_SYSTEM_ERR; + } + + pam_status = send_and_receive(pamh, &pi, task, quiet_mode); + + if (flags & PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER + && pam_status == PAM_USER_UNKNOWN) { + pam_status = PAM_IGNORE; + } + if (flags & PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL + && pam_status == PAM_AUTHINFO_UNAVAIL) { + pam_status = PAM_IGNORE; + } + + switch (task) { + case SSS_PAM_AUTHENTICATE: + /* We allow sssd to send the return code PAM_NEW_AUTHTOK_REQD during + * authentication, see sss_cli.h for details */ + if (pam_status == PAM_NEW_AUTHTOK_REQD) { + D(("Authtoken expired, trying to change it")); + + pw_exp_data = malloc(sizeof(int)); + if (pw_exp_data == NULL) { + D(("malloc failed.")); + pam_status = PAM_BUF_ERR; + break; + } + *pw_exp_data = 1; + + pam_status = pam_set_data(pamh, PWEXP_FLAG, pw_exp_data, + free_exp_data); + if (pam_status != PAM_SUCCESS) { + D(("pam_set_data failed.")); + } + } + break; + case SSS_PAM_ACCT_MGMT: + if (pam_status == PAM_SUCCESS && + pam_get_data(pamh, PWEXP_FLAG, (const void **) &exp_data) == + PAM_SUCCESS) { + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, + _("Password expired. Change your password now."), + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + } + pam_status = PAM_NEW_AUTHTOK_REQD; + } + break; + case SSS_PAM_CHAUTHTOK: + if (pam_status != PAM_SUCCESS && pam_status != PAM_USER_UNKNOWN) { + ret = pam_set_item(pamh, PAM_AUTHTOK, NULL); + if (ret != PAM_SUCCESS) { + D(("Failed to unset PAM_AUTHTOK [%s]", + pam_strerror(pamh,ret))); + } + ret = pam_set_item(pamh, PAM_OLDAUTHTOK, NULL); + if (ret != PAM_SUCCESS) { + D(("Failed to unset PAM_OLDAUTHTOK [%s]", + pam_strerror(pamh,ret))); + } + } + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + if (pam_status == PAM_PERM_DENIED && pi.pam_authtok_size == 0 && + getuid() == 0 && + pam_get_data(pamh, PWEXP_FLAG, (const void **) &exp_data) != + PAM_SUCCESS) { + + ret = select_pw_reset_message(pamh, &pi); + if (ret != 0) { + D(("select_pw_reset_message failed.\n")); + } + } + default: + /* nothing to do */ + break; + } + + overwrite_and_free_pam_items(&pi); + + D(("retries [%d].", retries)); + + if (pam_status != PAM_SUCCESS && + (task == SSS_PAM_AUTHENTICATE || task == SSS_PAM_CHAUTHTOK_PRELIM) && + retries > 0) { + retry = true; + retries--; + + flags &= ~PAM_CLI_FLAGS_USE_FIRST_PASS; + ret = pam_set_item(pamh, PAM_AUTHTOK, NULL); + if (ret != PAM_SUCCESS) { + D(("Failed to unset PAM_AUTHTOK [%s]", + pam_strerror(pamh,ret))); + } + ret = pam_set_item(pamh, PAM_OLDAUTHTOK, NULL); + if (ret != PAM_SUCCESS) { + D(("Failed to unset PAM_OLDAUTHTOK [%s]", + pam_strerror(pamh,ret))); + } + } + } while(retry); + + return pam_status; +} + +PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, + const char **argv ) +{ + return pam_sss(SSS_PAM_AUTHENTICATE, pamh, flags, argc, argv); +} + + +PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, + const char **argv ) +{ + return pam_sss(SSS_PAM_SETCRED, pamh, flags, argc, argv); +} + +PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, + const char **argv ) +{ + return pam_sss(SSS_PAM_ACCT_MGMT, pamh, flags, argc, argv); +} + +PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, + const char **argv ) +{ + return pam_sss(SSS_PAM_CHAUTHTOK, pamh, flags, argc, argv); +} + +PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, + const char **argv ) +{ + return pam_sss(SSS_PAM_OPEN_SESSION, pamh, flags, argc, argv); +} + +PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, + const char **argv ) +{ + return pam_sss(SSS_PAM_CLOSE_SESSION, pamh, flags, argc, argv); +} + + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_sssd_modstruct ={ + "pam_sssd", + pam_sm_authenticate, + pam_sm_setcred, + pam_sm_acct_mgmt, + pam_sm_open_session, + pam_sm_close_session, + pam_sm_chauthtok +}; + +#endif diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h index 817a61bb266..db8395d40c2 100644 --- a/src/sss_client/sss_cli.h +++ b/src/sss_client/sss_cli.h @@ -1,824 +1,824 @@ -/* - SSSD - - Client Interface for NSS and PAM. - - Authors: - Simo Sorce - - Copyright (C) Red Hat, Inc 2007 - - 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 . -*/ - -#ifndef _SSSCLI_H -#define _SSSCLI_H - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "shared/safealign.h" - -#ifndef HAVE_ERRNO_T -#define HAVE_ERRNO_T -typedef int errno_t; -#else -#include -#endif - -#ifndef EOK -#define EOK 0 -#endif - -#ifndef NETDB_INTERNAL -#define NETDB_INTERNAL (-1) -#endif - -#define SSS_NSS_PROTOCOL_VERSION 1 -#define SSS_PAM_PROTOCOL_VERSION 3 -#define SSS_SUDO_PROTOCOL_VERSION 1 -#define SSS_AUTOFS_PROTOCOL_VERSION 1 -#define SSS_SSH_PROTOCOL_VERSION 0 -#define SSS_PAC_PROTOCOL_VERSION 1 - -#ifdef LOGIN_NAME_MAX -#define SSS_NAME_MAX LOGIN_NAME_MAX -#else -#define SSS_NAME_MAX 256 -#endif - -/** - * @defgroup sss_cli_command SSS client commands - * @{ - */ - -/** The allowed commands an SSS client can send to the SSSD */ - -enum sss_cli_command { -/* null */ - SSS_CLI_NULL = 0x0000, - -/* version */ - SSS_GET_VERSION = 0x0001, - -/* passwd */ - - SSS_NSS_GETPWNAM = 0x0011, - SSS_NSS_GETPWUID = 0x0012, - SSS_NSS_SETPWENT = 0x0013, - SSS_NSS_GETPWENT = 0x0014, - SSS_NSS_ENDPWENT = 0x0015, - - SSS_NSS_GETPWNAM_EX = 0x0019, - SSS_NSS_GETPWUID_EX = 0x001A, - -/* group */ - - SSS_NSS_GETGRNAM = 0x0021, - SSS_NSS_GETGRGID = 0x0022, - SSS_NSS_SETGRENT = 0x0023, - SSS_NSS_GETGRENT = 0x0024, - SSS_NSS_ENDGRENT = 0x0025, - SSS_NSS_INITGR = 0x0026, - - SSS_NSS_GETGRNAM_EX = 0x0029, - SSS_NSS_GETGRGID_EX = 0x002A, - SSS_NSS_INITGR_EX = 0x002E, - -#if 0 -/* aliases */ - - SSS_NSS_GETALIASBYNAME = 0x0031, - SSS_NSS_GETALIASBYPORT = 0x0032, - SSS_NSS_SETALIASENT = 0x0033, - SSS_NSS_GETALIASENT = 0x0034, - SSS_NSS_ENDALIASENT = 0x0035, - -/* ethers */ - - SSS_NSS_GETHOSTTON = 0x0041, - SSS_NSS_GETNTOHOST = 0x0042, - SSS_NSS_SETETHERENT = 0x0043, - SSS_NSS_GETETHERENT = 0x0044, - SSS_NSS_ENDETHERENT = 0x0045, -#endif - -/* hosts */ - - SSS_NSS_GETHOSTBYNAME = 0x0051, - SSS_NSS_GETHOSTBYNAME2 = 0x0052, - SSS_NSS_GETHOSTBYADDR = 0x0053, - SSS_NSS_SETHOSTENT = 0x0054, - SSS_NSS_GETHOSTENT = 0x0055, - SSS_NSS_ENDHOSTENT = 0x0056, - -/* netgroup */ - - SSS_NSS_SETNETGRENT = 0x0061, - SSS_NSS_GETNETGRENT = 0x0062, - SSS_NSS_ENDNETGRENT = 0x0063, - -/* networks */ - - SSS_NSS_GETNETBYNAME = 0x0071, - SSS_NSS_GETNETBYADDR = 0x0072, - SSS_NSS_SETNETENT = 0x0073, - SSS_NSS_GETNETENT = 0x0074, - SSS_NSS_ENDNETENT = 0x0075, - -#if 0 -/* protocols */ - - SSS_NSS_GETPROTOBYNAME = 0x0081, - SSS_NSS_GETPROTOBYNUM = 0x0082, - SSS_NSS_SETPROTOENT = 0x0083, - SSS_NSS_GETPROTOENT = 0x0084, - SSS_NSS_ENDPROTOENT = 0x0085, - -/* rpc */ - - SSS_NSS_GETRPCBYNAME = 0x0091, - SSS_NSS_GETRPCBYNUM = 0x0092, - SSS_NSS_SETRPCENT = 0x0093, - SSS_NSS_GETRPCENT = 0x0094, - SSS_NSS_ENDRPCENT = 0x0095, -#endif - -/* services */ - - SSS_NSS_GETSERVBYNAME = 0x00A1, - SSS_NSS_GETSERVBYPORT = 0x00A2, - SSS_NSS_SETSERVENT = 0x00A3, - SSS_NSS_GETSERVENT = 0x00A4, - SSS_NSS_ENDSERVENT = 0x00A5, - -#if 0 -/* shadow */ - - SSS_NSS_GETSPNAM = 0x00B1, - SSS_NSS_GETSPUID = 0x00B2, - SSS_NSS_SETSPENT = 0x00B3, - SSS_NSS_GETSPENT = 0x00B4, - SSS_NSS_ENDSPENT = 0x00B5, -#endif - -/* SUDO */ - SSS_SUDO_GET_SUDORULES = 0x00C1, - SSS_SUDO_GET_DEFAULTS = 0x00C2, - -/* autofs */ - SSS_AUTOFS_SETAUTOMNTENT = 0x00D1, - SSS_AUTOFS_GETAUTOMNTENT = 0x00D2, - SSS_AUTOFS_GETAUTOMNTBYNAME = 0x00D3, - SSS_AUTOFS_ENDAUTOMNTENT = 0x00D4, - -/* SSH */ - SSS_SSH_GET_USER_PUBKEYS = 0x00E1, - SSS_SSH_GET_HOST_PUBKEYS = 0x00E2, - -/* PAM related calls */ - SSS_PAM_AUTHENTICATE = 0x00F1, /**< see pam_sm_authenticate(3) for - * details. - * - * Additionally we allow sssd to send - * the return code PAM_NEW_AUTHTOK_REQD - * during authentication if the - * authentication was successful but - * the authentication token is expired. - * To meet the standards of libpam we - * return PAM_SUCCESS for - * authentication and set a flag so - * that the account management module - * can return PAM_NEW_AUTHTOK_REQD if - * sssd return success for account - * management. We do this to reduce the - * communication with external servers, - * because there are cases, e.g. - * Kerberos authentication, where the - * information that the password is - * expired is already available during - * authentication. */ - SSS_PAM_SETCRED = 0x00F2, /**< see pam_sm_setcred(3) for - * details */ - SSS_PAM_ACCT_MGMT = 0x00F3, /**< see pam_sm_acct_mgmt(3) for - * details */ - SSS_PAM_OPEN_SESSION = 0x00F4, /**< see pam_sm_open_session(3) for - * details */ - SSS_PAM_CLOSE_SESSION = 0x00F5, /**< see pam_sm_close_session(3) for - *details */ - SSS_PAM_CHAUTHTOK = 0x00F6, /**< second run of the password change - * operation where the PAM_UPDATE_AUTHTOK - * flag is set and the real change may - * happen, see pam_sm_chauthtok(3) for - * details */ - SSS_PAM_CHAUTHTOK_PRELIM = 0x00F7, /**< first run of the password change - * operation where the PAM_PRELIM_CHECK - * flag is set, see pam_sm_chauthtok(3) - * for details */ - SSS_CMD_RENEW = 0x00F8, /**< Renew a credential with a limited - * lifetime, e.g. a Kerberos Ticket - * Granting Ticket (TGT) */ - SSS_PAM_PREAUTH = 0x00F9, /**< Request which can be run before - * an authentication request to find - * out which authentication methods - * are available for the given user. */ - SSS_GSSAPI_INIT = 0x00FA, /**< Initialize GSSAPI authentication. */ - SSS_GSSAPI_SEC_CTX = 0x00FB, /**< Establish GSSAPI security ctx. */ - SSS_PAM_PASSKEY_PREAUTH = 0x00FC, /**< could be called to request device - * information of device supporting - * passkey credential. - * typically, the command returns - * SSS_PAM_PASSKEY_DEVINFO */ -/* PAC responder calls */ - SSS_PAC_ADD_PAC_USER = 0x0101, - -/* ID-SID mapping calls */ -SSS_NSS_GETSIDBYNAME = 0x0111, /**< Takes a zero terminated fully qualified - name and returns the zero terminated - string representation of the SID of the - object with the given name. */ -SSS_NSS_GETSIDBYID = 0x0112, /**< Takes an unsigned 32bit integer (POSIX ID) - and returns the zero terminated string - representation of the SID of the object - with the given ID. */ -SSS_NSS_GETNAMEBYSID = 0x0113, /**< Takes the zero terminated string - representation of a SID and returns the - zero terminated fully qualified name of - the related object. */ -SSS_NSS_GETIDBYSID = 0x0114, /**< Takes the zero terminated string - representation of a SID and returns and - returns the POSIX ID of the related object - as unsigned 32bit integer value and - another unsigned 32bit integer value - indicating the type (unknown, user, group, - both) of the object. */ -SSS_NSS_GETORIGBYNAME = 0x0115, /**< Takes a zero terminated fully qualified - name and returns a list of zero - terminated strings with key-value pairs - where the first string is the key and - second the value. Hence the list should - have an even number of strings, if not - the whole list is invalid. */ -SSS_NSS_GETNAMEBYCERT = 0x0116, /**< Takes the zero terminated string - of the base64 encoded DER representation - of a X509 certificate and returns the zero - terminated fully qualified name of the - related object. */ -SSS_NSS_GETLISTBYCERT = 0x0117, /**< Takes the zero terminated string - of the base64 encoded DER representation - of a X509 certificate and returns a list - of zero terminated fully qualified names - of the related objects. */ -SSS_NSS_GETSIDBYUID = 0x0118, /**< Takes an unsigned 32bit integer (POSIX UID) - and return the zero terminated string - representation of the SID of the object - with the given UID. */ -SSS_NSS_GETSIDBYGID = 0x0119, /**< Takes an unsigned 32bit integer (POSIX GID) - and return the zero terminated string - representation of the SID of the object - with the given UID. */ -SSS_NSS_GETORIGBYUSERNAME = 0x011A, /**< Takes a zero terminated fully qualified - user name and returns a list of zero - terminated strings with key-value pairs - where the first string is the key and - second the value. Hence the list should - have an even number of strings, if not - the whole list is invalid. */ -SSS_NSS_GETORIGBYGROUPNAME = 0x011B, /**< Takes a zero terminated fully qualified - group name and returns a list of zero - terminated strings with key-value pairs - where the first string is the key and - second the value. Hence the list should - have an even number of strings, if not - the whole list is invalid. */ -SSS_NSS_GETSIDBYUSERNAME = 0x011C, /**< Takes a zero terminated fully qualified - name and returns the zero terminated - string representation of the SID of the - user with the given name. */ -SSS_NSS_GETSIDBYGROUPNAME = 0x011D, /**< Takes a zero terminated fully qualified - name and returns the zero terminated - string representation of the SID of the - group with the given name. */ - - -/* subid */ - SSS_NSS_GET_SUBID_RANGES = 0x0130, /**< Requests both subuid and subgid ranges - defined for a user. */ -}; - -/** - * @} - */ /* end of group sss_cli_command */ - - -/** - * @defgroup sss_pam SSSD and PAM - * - * SSSD offers authentication and authorization via PAM - * - * The SSSD provides a PAM client modules pam_sss which can be called from the - * PAM stack of the operation system. pam_sss will collect all the data about - * the user from the PAM stack and sends them via a socket to the PAM - * responder of the SSSD. The PAM responder selects the appropriate backend - * and forwards the data via D-BUS to the backend. The backend preforms the - * requested operation and sends the result expressed by a PAM return value - * and optional additional information back to the PAM responder. Finally the - * PAM responder forwards the response back to the client. - * - * @{ - */ - -/** - * @} - */ /* end of group sss_pam */ - -/** - * @defgroup sss_authtok_type Authentication Tokens - * @ingroup sss_pam - * - * To indicate to the components of the SSSD how to handle the authentication - * token the client sends the type of the authentication token to the SSSD. - * - * @{ - */ - -/** The different types of authentication tokens */ - -enum sss_authtok_type { - SSS_AUTHTOK_TYPE_EMPTY = 0x0000, /**< No authentication token - * available */ - SSS_AUTHTOK_TYPE_PASSWORD = 0x0001, /**< Authentication token is a - * password, it may or may no contain - * a trailing \\0 */ - SSS_AUTHTOK_TYPE_CCFILE = 0x0002, /**< Authentication token is a path to - * a Kerberos credential cache file, - * it may or may no contain - * a trailing \\0 */ - SSS_AUTHTOK_TYPE_2FA = 0x0003, /**< Authentication token has two - * factors, they may or may no contain - * a trailing \\0 */ - SSS_AUTHTOK_TYPE_SC_PIN = 0x0004, /**< Authentication token is a Smart - * Card PIN, it may or may no contain - * a trailing \\0 */ - SSS_AUTHTOK_TYPE_SC_KEYPAD = 0x0005, /**< Authentication token indicates - * 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 */ - SSS_AUTHTOK_TYPE_OAUTH2 = 0x0007, /**< Authentication token is a - * oauth2 token for presented - * challenge that is acquired from - * Kerberos. It may or may no - * contain a trailing \\0 */ - SSS_AUTHTOK_TYPE_PASSKEY = 0x0008, /**< Authentication token is a Passkey - * PIN, it may or may not contain - * a trailing \\0 */ - SSS_AUTHTOK_TYPE_PASSKEY_KRB = 0x0009, /**< Authentication token contains - * Passkey data used for Kerberos - * pre-authentication */ - SSS_AUTHTOK_TYPE_PASSKEY_REPLY = 0x0010, /**< Authentication token contains - * Passkey reply data presented as - * a kerberos challenge answer */ - SSS_AUTHTOK_TYPE_PAM_STACKED = 0x0011, /**< Authentication token contains - * either 2FA_SINGLE or PASSWORD - * via PAM use_first_pass */ -}; - -/** - * @} - */ /* end of group sss_authtok_type */ - -#define SSS_START_OF_PAM_REQUEST 0x4d415049 -#define SSS_END_OF_PAM_REQUEST 0x4950414d - -#define PAM_PREAUTH_INDICATOR PUBCONF_PATH"/pam_preauth_available" - -enum pam_item_type { - SSS_PAM_ITEM_EMPTY = 0x0000, - SSS_PAM_ITEM_USER, - SSS_PAM_ITEM_SERVICE, - SSS_PAM_ITEM_TTY, - SSS_PAM_ITEM_RUSER, - SSS_PAM_ITEM_RHOST, - SSS_PAM_ITEM_AUTHTOK, - SSS_PAM_ITEM_NEWAUTHTOK, - SSS_PAM_ITEM_CLI_LOCALE, - SSS_PAM_ITEM_CLI_PID, - SSS_PAM_ITEM_CHILD_PID, - SSS_PAM_ITEM_REQUESTED_DOMAINS, - SSS_PAM_ITEM_FLAGS, -}; - -#define PAM_CLI_FLAGS_USE_FIRST_PASS (1 << 0) -#define PAM_CLI_FLAGS_FORWARD_PASS (1 << 1) -#define PAM_CLI_FLAGS_USE_AUTHTOK (1 << 2) -#define PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER (1 << 3) -#define PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL (1 << 4) -#define PAM_CLI_FLAGS_USE_2FA (1 << 5) -#define PAM_CLI_FLAGS_ALLOW_MISSING_NAME (1 << 6) -#define PAM_CLI_FLAGS_PROMPT_ALWAYS (1 << 7) -#define PAM_CLI_FLAGS_TRY_CERT_AUTH (1 << 8) -#define PAM_CLI_FLAGS_REQUIRE_CERT_AUTH (1 << 9) -#define PAM_CLI_FLAGS_ALLOW_CHAUTHTOK_BY_ROOT (1 << 10) -#define PAM_CLI_FLAGS_REQUIRE_PASSKEY_CACHED_DEVINFO (1 << 11) - -#define SSS_NSS_MAX_ENTRIES 256 -#define SSS_NSS_HEADER_SIZE (sizeof(uint32_t) * 4) -struct sss_cli_req_data { - size_t len; - const void *data; -}; - -/* this is in milliseconds, wait up to 300 seconds */ -#define SSS_CLI_SOCKET_TIMEOUT 300000 - -enum sss_status { - SSS_STATUS_TRYAGAIN, - SSS_STATUS_UNAVAIL, - SSS_STATUS_SUCCESS -}; - -/** - * @defgroup sss_pam_cli Responses to the PAM client - * @ingroup sss_pam - * @{ - */ - -/** - * @defgroup response_type Messages from the server - * @ingroup sss_pam_cli - * - * SSSD can send different kind of information back to the client. - * A response from the SSSD can contain 0 or more messages. Each message - * contains a type tag and the size of the message data, both are unsigned - * 32-bit integer values, followed be the message specific data. - * - * If the message is generated by a backend it is send back to the PAM - * responder via a D-BUS message in an array of D-BUS structs. The struct - * consists of a DBUS_TYPE_UINT32 for the tag and a DBUS_TYPE_ARRAY to hold - * the message. - * - * Examples: - * - #SSS_PAM_ENV_ITEM, - uint32_t | uint32_t | uint8_t[4] - ----------|----------|------------ - 0x03 | 0x04 | a=b\\0 - * @{ - */ - -/** Types of different messages */ - -enum response_type { - SSS_PAM_SYSTEM_INFO = 0x01, /**< Message for the system log. - * @param String, zero terminated. */ - SSS_PAM_DOMAIN_NAME, /**< Name of the domain the user belongs too. - * This messages is generated by the PAM responder. - * @param String, zero terminated, with the domain - * name. */ - SSS_PAM_ENV_ITEM, /**< Set and environment variable with pam_putenv(3). - * @param String, zero terminated, of the form - * name=value. See pam_putenv(3) for details. */ - SSS_ENV_ITEM, /**< Set and environment variable with putenv(3). - * @param String, zero terminated, of the form - * name=value. See putenv(3) for details. */ - SSS_ALL_ENV_ITEM, /**< Set and environment variable with putenv(3) and - * pam_putenv(3). - * @param String, zero terminated, of the form - * name=value. See putenv(3) and pam_putenv(3) for - * details. */ - SSS_PAM_USER_INFO, /**< A message which should be displayed to the user. - * @param User info message, see #user_info_type - * for details. */ - SSS_PAM_TEXT_MSG, /**< A plain text message which should be displayed to - * the user. This should only be used in the case where - * it is not possible to use SSS_PAM_USER_INFO. - * @param A zero terminated string. */ - SSS_PAM_OTP_INFO, /**< A message which optionally may contain the name - * of the vendor, the ID of an OTP token and a - * challenge. - * @param Three zero terminated strings, if one of the - * strings is missing the message will contain only - * an empty string (\0) for that component. */ - SSS_PAM_CERT_INFO, /**< A message indicating that Smartcard/certificate - * based authentication is available and contains - * details about the found Smartcard. - * @param user name, zero terminated - * @param token name, zero terminated - * @param PKCS#11 module name, zero terminated - * @param key id, zero terminated */ - SSS_OTP, /**< Indicates that the authtok was a OTP, so don't - * cache it. There is no message. - * @param None. */ - SSS_PASSWORD_PROMPTING, /**< Indicates that password prompting is possible. - * This might be used together with - * SSS_PAM_OTP_INFO to determine the type of - * prompting. There is no message. - * @param None. */ - SSS_CERT_AUTH_PROMPTING, /**< Indicates that on the server side - * Smartcard/certificate based authentication is - * available for the selected account. This might - * be used together with other prompting options - * to determine the type of prompting. - * @param None. */ - 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. */ - SSS_CHILD_KEEP_ALIVE, /**< Indicates that the child process is kept alived - * and further communication must be done with the - * same child. The message is the pid of the child - * process. */ - SSS_PAM_OAUTH2_INFO, /**< A message which contains the oauth2 - * parameters for the user. - * @param Three zero terminated strings: - * - verification_uri - * - verification_uri_complete - * - user_code - */ - SSS_PAM_PASSKEY_INFO, /**< Indicates that passkey authentication is available. - * including a parameter string which dictates whether - * prompting for PIN is needed. - * @param - * - prompt_pin - */ - SSS_PAM_PASSKEY_DEVINFO, /**< Indicates a passkey device information - * including a parameter string - * @param - * - device information string - */ - SSS_PAM_PASSKEY_KRB_INFO, /**< A message containing the passkey parameters - * for the user. The key is the cryptographic challenge - * used as the key to the passkey hash table entry. - * @param - * - user verification (string) - * - key (string) - */ -}; - -/** - * @defgroup user_info_type User info messages - * @ingroup response_type - * - * To achieve a consistent user experience and to facilitate - * internationalization all messages show to the user are generate by the PAM - * client and not by the SSSD server components. To indicate what message the - * client should display to the user SSSD can send a #SSS_PAM_USER_INFO message - * where the data part contains one of the following tags as an unsigned - * 32-bit integer value and optional data. - * - * Examples: - * - #SSS_PAM_USER_INFO_OFFLINE_CHPASS - * uint32_t | uint32_t | uint32_t - * ----------|----------|---------- - * 0x06 | 0x04 | 0x03 - * - * - #SSS_PAM_USER_INFO_CHPASS_ERROR - * uint32_t | uint32_t | uint32_t | uint32_t | uint8_t[3] - * ----------|----------|----------|----------|------------ - * 0x06 | 0x0B | 0x04 | 0x03 | abc - * @{ - */ - -/** Different types of user messages */ - -enum user_info_type { - SSS_PAM_USER_INFO_OFFLINE_AUTH = 0x01, /**< Inform the user that the - * authentication happened offline. - * This message is generated by the - * PAM responder. - * @param Time when the cached - * password will expire in seconds - * since the UNIX Epoch as returned - * by time(2) as int64_t. A value - * of zero indicates that the - * cached password will never - * expire. */ - SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED, /**< Tell the user how low a new - * authentication is delayed. This - * message is generated by the PAM - * responder. - * @param Time when an - * authentication is allowed again - * in seconds since the UNIX Epoch - * as returned by time(2) as - * int64_t. */ - SSS_PAM_USER_INFO_OFFLINE_CHPASS, /**< * Tell the user that it is not - * possible to change the password while - * the system is offline. This message - * is generated by the PAM responder. */ - SSS_PAM_USER_INFO_OTP_CHPASS, /**< Tell the user that he needs to kinit - * or login and logout to get a TGT after - * an OTP password change */ - SSS_PAM_USER_INFO_CHPASS_ERROR, /**< Tell the user that a password change - * failed and optionally give a reason. - * @param Size of the message as unsigned - * 32-bit integer value. A value of 0 - * indicates that no message is following. - * @param String with the specified - * length. */ - - SSS_PAM_USER_INFO_GRACE_LOGIN, /**< Warn the user that the password is - * expired and inform about the remaining - * number of grace logins. - * @param The number of remaining grace - * logins as uint32_t */ - SSS_PAM_USER_INFO_EXPIRE_WARN, /**< Warn the user that the password will - * expire soon. - * @param Number of seconds before the - * user's password will expire. */ - - SSS_PAM_USER_INFO_ACCOUNT_EXPIRED, /**< Tell the user that the account - * has expired and optionally give - * a reason. - * @param Size of the message as - * unsigned 32-bit integer value. A - * value of 0 indicates that no message - * is following. @param String with the - * specified length. */ - - SSS_PAM_USER_INFO_PIN_LOCKED, /**< Tell the user that the PIN is locked */ - SSS_PAM_USER_INFO_NO_KRB_TGT, /**< Tell the user that Kerberos local/offline - auth was performed, therefore no TGT - is granted */ -}; -/** - * @} - */ /* end of group user_info_type */ - -/** - * @} - */ /* end of group response_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_PASSKEY, - 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); -const char *pc_get_passkey_inter_prompt(struct prompt_config *pc); -const char *pc_get_passkey_touch_prompt(struct prompt_config *pc); -errno_t pc_list_add_passkey(struct prompt_config ***pc_list, - const char *inter_prompt, - const char *touch_prompt); -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 -}; - -enum sss_cli_error_codes { - ESSS_SSS_CLI_ERROR_START = 0x1000, - ESSS_BAD_SOCKET, - ESSS_BAD_CRED_MSG, - ESSS_SERVER_NOT_TRUSTED, - ESSS_NO_SOCKET, - ESSS_SOCKET_STAT_ERROR, - - ESS_SSS_CLI_ERROR_MAX -}; - -const char *ssscli_err2string(int err); - -enum sss_status sss_cli_make_request_with_checks(enum sss_cli_command cmd, - struct sss_cli_req_data *rd, - int timeout, - uint8_t **repbuf, size_t *replen, - int *errnop, - const char *socket_name, - bool check_server_creds, - bool allow_custom_errors); - -enum nss_status sss_nss_make_request(enum sss_cli_command cmd, - struct sss_cli_req_data *rd, - uint8_t **repbuf, size_t *replen, - int *errnop); - -enum nss_status sss_nss_make_request_timeout(enum sss_cli_command cmd, - struct sss_cli_req_data *rd, - int timeout, - uint8_t **repbuf, size_t *replen, - int *errnop); - -int sss_pam_make_request(enum sss_cli_command cmd, - struct sss_cli_req_data *rd, - uint8_t **repbuf, size_t *replen, - int *errnop); - -void sss_cli_close_socket(void); - -/* Checks access to the PAC responder and opens the socket, if available. - * Required for processes like krb5_child that need to open the socket - * before dropping privs. - */ -int sss_pac_check_and_open(void); - -int sss_pac_make_request(enum sss_cli_command cmd, - struct sss_cli_req_data *rd, - uint8_t **repbuf, size_t *replen, - int *errnop); - -int sss_pac_make_request_with_lock(enum sss_cli_command cmd, - struct sss_cli_req_data *rd, - uint8_t **repbuf, size_t *replen, - int *errnop); - -#if 0 - -/* GETSPNAM Request: - * - * 0-X: string with name - * - * Replies: - * - * 0-3: 32bit unsigned number of results - * 4-7: 32bit unsigned (reserved/padding) - * For each result: - * 0-7: 64bit unsigned with Date of last change - * 8-15: 64bit unsigned with Min #days between changes - * 16-23: 64bit unsigned with Max #days between changes - * 24-31: 64bit unsigned with #days before pwd expires - * 32-39: 64bit unsigned with #days after pwd expires until account is disabled - * 40-47: 64bit unsigned with expiration date in days since 1970-01-01 - * 48-55: 64bit unsigned (flags/reserved) - * 56-X: sequence of 2, 0 terminated, strings (name, pwd) 64bit padded - */ -#endif - -/* Return strlen(str) or maxlen, whichever is shorter - * Returns EINVAL if str is NULL, EFBIG if str is longer than maxlen - * _len will return the result - */ -errno_t sss_strnlen(const char *str, size_t maxlen, size_t *len); - -void sss_nss_lock(void); -void sss_nss_unlock(void); -void sss_pam_lock(void); -void sss_pam_unlock(void); -void sss_nss_mc_lock(void); -void sss_nss_mc_unlock(void); -void sss_pac_lock(void); -void sss_pac_unlock(void); - -errno_t sss_readrep_copy_string(const char *in, - size_t *offset, - size_t *slen, - size_t *dlen, - char **out, - size_t *size); - -enum pam_gssapi_cmd { - PAM_GSSAPI_GET_NAME, - PAM_GSSAPI_INIT, - PAM_GSSAPI_SENTINEL -}; - -#endif /* _SSSCLI_H */ +/* + SSSD + + Client Interface for NSS and PAM. + + Authors: + Simo Sorce + + Copyright (C) Red Hat, Inc 2007 + + 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 . +*/ + +#ifndef _SSSCLI_H +#define _SSSCLI_H + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "shared/safealign.h" + +#ifndef HAVE_ERRNO_T +#define HAVE_ERRNO_T +typedef int errno_t; +#else +#include +#endif + +#ifndef EOK +#define EOK 0 +#endif + +#ifndef NETDB_INTERNAL +#define NETDB_INTERNAL (-1) +#endif + +#define SSS_NSS_PROTOCOL_VERSION 1 +#define SSS_PAM_PROTOCOL_VERSION 3 +#define SSS_SUDO_PROTOCOL_VERSION 1 +#define SSS_AUTOFS_PROTOCOL_VERSION 1 +#define SSS_SSH_PROTOCOL_VERSION 0 +#define SSS_PAC_PROTOCOL_VERSION 1 + +#ifdef LOGIN_NAME_MAX +#define SSS_NAME_MAX LOGIN_NAME_MAX +#else +#define SSS_NAME_MAX 256 +#endif + +/** + * @defgroup sss_cli_command SSS client commands + * @{ + */ + +/** The allowed commands an SSS client can send to the SSSD */ + +enum sss_cli_command { +/* null */ + SSS_CLI_NULL = 0x0000, + +/* version */ + SSS_GET_VERSION = 0x0001, + +/* passwd */ + + SSS_NSS_GETPWNAM = 0x0011, + SSS_NSS_GETPWUID = 0x0012, + SSS_NSS_SETPWENT = 0x0013, + SSS_NSS_GETPWENT = 0x0014, + SSS_NSS_ENDPWENT = 0x0015, + + SSS_NSS_GETPWNAM_EX = 0x0019, + SSS_NSS_GETPWUID_EX = 0x001A, + +/* group */ + + SSS_NSS_GETGRNAM = 0x0021, + SSS_NSS_GETGRGID = 0x0022, + SSS_NSS_SETGRENT = 0x0023, + SSS_NSS_GETGRENT = 0x0024, + SSS_NSS_ENDGRENT = 0x0025, + SSS_NSS_INITGR = 0x0026, + + SSS_NSS_GETGRNAM_EX = 0x0029, + SSS_NSS_GETGRGID_EX = 0x002A, + SSS_NSS_INITGR_EX = 0x002E, + +#if 0 +/* aliases */ + + SSS_NSS_GETALIASBYNAME = 0x0031, + SSS_NSS_GETALIASBYPORT = 0x0032, + SSS_NSS_SETALIASENT = 0x0033, + SSS_NSS_GETALIASENT = 0x0034, + SSS_NSS_ENDALIASENT = 0x0035, + +/* ethers */ + + SSS_NSS_GETHOSTTON = 0x0041, + SSS_NSS_GETNTOHOST = 0x0042, + SSS_NSS_SETETHERENT = 0x0043, + SSS_NSS_GETETHERENT = 0x0044, + SSS_NSS_ENDETHERENT = 0x0045, +#endif + +/* hosts */ + + SSS_NSS_GETHOSTBYNAME = 0x0051, + SSS_NSS_GETHOSTBYNAME2 = 0x0052, + SSS_NSS_GETHOSTBYADDR = 0x0053, + SSS_NSS_SETHOSTENT = 0x0054, + SSS_NSS_GETHOSTENT = 0x0055, + SSS_NSS_ENDHOSTENT = 0x0056, + +/* netgroup */ + + SSS_NSS_SETNETGRENT = 0x0061, + SSS_NSS_GETNETGRENT = 0x0062, + SSS_NSS_ENDNETGRENT = 0x0063, + +/* networks */ + + SSS_NSS_GETNETBYNAME = 0x0071, + SSS_NSS_GETNETBYADDR = 0x0072, + SSS_NSS_SETNETENT = 0x0073, + SSS_NSS_GETNETENT = 0x0074, + SSS_NSS_ENDNETENT = 0x0075, + +#if 0 +/* protocols */ + + SSS_NSS_GETPROTOBYNAME = 0x0081, + SSS_NSS_GETPROTOBYNUM = 0x0082, + SSS_NSS_SETPROTOENT = 0x0083, + SSS_NSS_GETPROTOENT = 0x0084, + SSS_NSS_ENDPROTOENT = 0x0085, + +/* rpc */ + + SSS_NSS_GETRPCBYNAME = 0x0091, + SSS_NSS_GETRPCBYNUM = 0x0092, + SSS_NSS_SETRPCENT = 0x0093, + SSS_NSS_GETRPCENT = 0x0094, + SSS_NSS_ENDRPCENT = 0x0095, +#endif + +/* services */ + + SSS_NSS_GETSERVBYNAME = 0x00A1, + SSS_NSS_GETSERVBYPORT = 0x00A2, + SSS_NSS_SETSERVENT = 0x00A3, + SSS_NSS_GETSERVENT = 0x00A4, + SSS_NSS_ENDSERVENT = 0x00A5, + +#if 0 +/* shadow */ + + SSS_NSS_GETSPNAM = 0x00B1, + SSS_NSS_GETSPUID = 0x00B2, + SSS_NSS_SETSPENT = 0x00B3, + SSS_NSS_GETSPENT = 0x00B4, + SSS_NSS_ENDSPENT = 0x00B5, +#endif + +/* SUDO */ + SSS_SUDO_GET_SUDORULES = 0x00C1, + SSS_SUDO_GET_DEFAULTS = 0x00C2, + +/* autofs */ + SSS_AUTOFS_SETAUTOMNTENT = 0x00D1, + SSS_AUTOFS_GETAUTOMNTENT = 0x00D2, + SSS_AUTOFS_GETAUTOMNTBYNAME = 0x00D3, + SSS_AUTOFS_ENDAUTOMNTENT = 0x00D4, + +/* SSH */ + SSS_SSH_GET_USER_PUBKEYS = 0x00E1, + SSS_SSH_GET_HOST_PUBKEYS = 0x00E2, + +/* PAM related calls */ + SSS_PAM_AUTHENTICATE = 0x00F1, /**< see pam_sm_authenticate(3) for + * details. + * + * Additionally we allow sssd to send + * the return code PAM_NEW_AUTHTOK_REQD + * during authentication if the + * authentication was successful but + * the authentication token is expired. + * To meet the standards of libpam we + * return PAM_SUCCESS for + * authentication and set a flag so + * that the account management module + * can return PAM_NEW_AUTHTOK_REQD if + * sssd return success for account + * management. We do this to reduce the + * communication with external servers, + * because there are cases, e.g. + * Kerberos authentication, where the + * information that the password is + * expired is already available during + * authentication. */ + SSS_PAM_SETCRED = 0x00F2, /**< see pam_sm_setcred(3) for + * details */ + SSS_PAM_ACCT_MGMT = 0x00F3, /**< see pam_sm_acct_mgmt(3) for + * details */ + SSS_PAM_OPEN_SESSION = 0x00F4, /**< see pam_sm_open_session(3) for + * details */ + SSS_PAM_CLOSE_SESSION = 0x00F5, /**< see pam_sm_close_session(3) for + *details */ + SSS_PAM_CHAUTHTOK = 0x00F6, /**< second run of the password change + * operation where the PAM_UPDATE_AUTHTOK + * flag is set and the real change may + * happen, see pam_sm_chauthtok(3) for + * details */ + SSS_PAM_CHAUTHTOK_PRELIM = 0x00F7, /**< first run of the password change + * operation where the PAM_PRELIM_CHECK + * flag is set, see pam_sm_chauthtok(3) + * for details */ + SSS_CMD_RENEW = 0x00F8, /**< Renew a credential with a limited + * lifetime, e.g. a Kerberos Ticket + * Granting Ticket (TGT) */ + SSS_PAM_PREAUTH = 0x00F9, /**< Request which can be run before + * an authentication request to find + * out which authentication methods + * are available for the given user. */ + SSS_GSSAPI_INIT = 0x00FA, /**< Initialize GSSAPI authentication. */ + SSS_GSSAPI_SEC_CTX = 0x00FB, /**< Establish GSSAPI security ctx. */ + SSS_PAM_PASSKEY_PREAUTH = 0x00FC, /**< could be called to request device + * information of device supporting + * passkey credential. + * typically, the command returns + * SSS_PAM_PASSKEY_DEVINFO */ +/* PAC responder calls */ + SSS_PAC_ADD_PAC_USER = 0x0101, + +/* ID-SID mapping calls */ +SSS_NSS_GETSIDBYNAME = 0x0111, /**< Takes a zero terminated fully qualified + name and returns the zero terminated + string representation of the SID of the + object with the given name. */ +SSS_NSS_GETSIDBYID = 0x0112, /**< Takes an unsigned 32bit integer (POSIX ID) + and returns the zero terminated string + representation of the SID of the object + with the given ID. */ +SSS_NSS_GETNAMEBYSID = 0x0113, /**< Takes the zero terminated string + representation of a SID and returns the + zero terminated fully qualified name of + the related object. */ +SSS_NSS_GETIDBYSID = 0x0114, /**< Takes the zero terminated string + representation of a SID and returns and + returns the POSIX ID of the related object + as unsigned 32bit integer value and + another unsigned 32bit integer value + indicating the type (unknown, user, group, + both) of the object. */ +SSS_NSS_GETORIGBYNAME = 0x0115, /**< Takes a zero terminated fully qualified + name and returns a list of zero + terminated strings with key-value pairs + where the first string is the key and + second the value. Hence the list should + have an even number of strings, if not + the whole list is invalid. */ +SSS_NSS_GETNAMEBYCERT = 0x0116, /**< Takes the zero terminated string + of the base64 encoded DER representation + of a X509 certificate and returns the zero + terminated fully qualified name of the + related object. */ +SSS_NSS_GETLISTBYCERT = 0x0117, /**< Takes the zero terminated string + of the base64 encoded DER representation + of a X509 certificate and returns a list + of zero terminated fully qualified names + of the related objects. */ +SSS_NSS_GETSIDBYUID = 0x0118, /**< Takes an unsigned 32bit integer (POSIX UID) + and return the zero terminated string + representation of the SID of the object + with the given UID. */ +SSS_NSS_GETSIDBYGID = 0x0119, /**< Takes an unsigned 32bit integer (POSIX GID) + and return the zero terminated string + representation of the SID of the object + with the given UID. */ +SSS_NSS_GETORIGBYUSERNAME = 0x011A, /**< Takes a zero terminated fully qualified + user name and returns a list of zero + terminated strings with key-value pairs + where the first string is the key and + second the value. Hence the list should + have an even number of strings, if not + the whole list is invalid. */ +SSS_NSS_GETORIGBYGROUPNAME = 0x011B, /**< Takes a zero terminated fully qualified + group name and returns a list of zero + terminated strings with key-value pairs + where the first string is the key and + second the value. Hence the list should + have an even number of strings, if not + the whole list is invalid. */ +SSS_NSS_GETSIDBYUSERNAME = 0x011C, /**< Takes a zero terminated fully qualified + name and returns the zero terminated + string representation of the SID of the + user with the given name. */ +SSS_NSS_GETSIDBYGROUPNAME = 0x011D, /**< Takes a zero terminated fully qualified + name and returns the zero terminated + string representation of the SID of the + group with the given name. */ + + +/* subid */ + SSS_NSS_GET_SUBID_RANGES = 0x0130, /**< Requests both subuid and subgid ranges + defined for a user. */ +}; + +/** + * @} + */ /* end of group sss_cli_command */ + + +/** + * @defgroup sss_pam SSSD and PAM + * + * SSSD offers authentication and authorization via PAM + * + * The SSSD provides a PAM client modules pam_sss which can be called from the + * PAM stack of the operation system. pam_sss will collect all the data about + * the user from the PAM stack and sends them via a socket to the PAM + * responder of the SSSD. The PAM responder selects the appropriate backend + * and forwards the data via D-BUS to the backend. The backend preforms the + * requested operation and sends the result expressed by a PAM return value + * and optional additional information back to the PAM responder. Finally the + * PAM responder forwards the response back to the client. + * + * @{ + */ + +/** + * @} + */ /* end of group sss_pam */ + +/** + * @defgroup sss_authtok_type Authentication Tokens + * @ingroup sss_pam + * + * To indicate to the components of the SSSD how to handle the authentication + * token the client sends the type of the authentication token to the SSSD. + * + * @{ + */ + +/** The different types of authentication tokens */ + +enum sss_authtok_type { + SSS_AUTHTOK_TYPE_EMPTY = 0x0000, /**< No authentication token + * available */ + SSS_AUTHTOK_TYPE_PASSWORD = 0x0001, /**< Authentication token is a + * password, it may or may no contain + * a trailing \\0 */ + SSS_AUTHTOK_TYPE_CCFILE = 0x0002, /**< Authentication token is a path to + * a Kerberos credential cache file, + * it may or may no contain + * a trailing \\0 */ + SSS_AUTHTOK_TYPE_2FA = 0x0003, /**< Authentication token has two + * factors, they may or may no contain + * a trailing \\0 */ + SSS_AUTHTOK_TYPE_SC_PIN = 0x0004, /**< Authentication token is a Smart + * Card PIN, it may or may no contain + * a trailing \\0 */ + SSS_AUTHTOK_TYPE_SC_KEYPAD = 0x0005, /**< Authentication token indicates + * 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 */ + SSS_AUTHTOK_TYPE_OAUTH2 = 0x0007, /**< Authentication token is a + * oauth2 token for presented + * challenge that is acquired from + * Kerberos. It may or may no + * contain a trailing \\0 */ + SSS_AUTHTOK_TYPE_PASSKEY = 0x0008, /**< Authentication token is a Passkey + * PIN, it may or may not contain + * a trailing \\0 */ + SSS_AUTHTOK_TYPE_PASSKEY_KRB = 0x0009, /**< Authentication token contains + * Passkey data used for Kerberos + * pre-authentication */ + SSS_AUTHTOK_TYPE_PASSKEY_REPLY = 0x0010, /**< Authentication token contains + * Passkey reply data presented as + * a kerberos challenge answer */ + SSS_AUTHTOK_TYPE_PAM_STACKED = 0x0011, /**< Authentication token contains + * either 2FA_SINGLE or PASSWORD + * via PAM use_first_pass */ +}; + +/** + * @} + */ /* end of group sss_authtok_type */ + +#define SSS_START_OF_PAM_REQUEST 0x4d415049 +#define SSS_END_OF_PAM_REQUEST 0x4950414d + +#define PAM_PREAUTH_INDICATOR PUBCONF_PATH"/pam_preauth_available" + +enum pam_item_type { + SSS_PAM_ITEM_EMPTY = 0x0000, + SSS_PAM_ITEM_USER, + SSS_PAM_ITEM_SERVICE, + SSS_PAM_ITEM_TTY, + SSS_PAM_ITEM_RUSER, + SSS_PAM_ITEM_RHOST, + SSS_PAM_ITEM_AUTHTOK, + SSS_PAM_ITEM_NEWAUTHTOK, + SSS_PAM_ITEM_CLI_LOCALE, + SSS_PAM_ITEM_CLI_PID, + SSS_PAM_ITEM_CHILD_PID, + SSS_PAM_ITEM_REQUESTED_DOMAINS, + SSS_PAM_ITEM_FLAGS, +}; + +#define PAM_CLI_FLAGS_USE_FIRST_PASS (1 << 0) +#define PAM_CLI_FLAGS_FORWARD_PASS (1 << 1) +#define PAM_CLI_FLAGS_USE_AUTHTOK (1 << 2) +#define PAM_CLI_FLAGS_IGNORE_UNKNOWN_USER (1 << 3) +#define PAM_CLI_FLAGS_IGNORE_AUTHINFO_UNAVAIL (1 << 4) +#define PAM_CLI_FLAGS_USE_2FA (1 << 5) +#define PAM_CLI_FLAGS_ALLOW_MISSING_NAME (1 << 6) +#define PAM_CLI_FLAGS_PROMPT_ALWAYS (1 << 7) +#define PAM_CLI_FLAGS_TRY_CERT_AUTH (1 << 8) +#define PAM_CLI_FLAGS_REQUIRE_CERT_AUTH (1 << 9) +#define PAM_CLI_FLAGS_ALLOW_CHAUTHTOK_BY_ROOT (1 << 10) +#define PAM_CLI_FLAGS_REQUIRE_PASSKEY_CACHED_DEVINFO (1 << 11) + +#define SSS_NSS_MAX_ENTRIES 256 +#define SSS_NSS_HEADER_SIZE (sizeof(uint32_t) * 4) +struct sss_cli_req_data { + size_t len; + const void *data; +}; + +/* this is in milliseconds, wait up to 300 seconds */ +#define SSS_CLI_SOCKET_TIMEOUT 300000 + +enum sss_status { + SSS_STATUS_TRYAGAIN, + SSS_STATUS_UNAVAIL, + SSS_STATUS_SUCCESS +}; + +/** + * @defgroup sss_pam_cli Responses to the PAM client + * @ingroup sss_pam + * @{ + */ + +/** + * @defgroup response_type Messages from the server + * @ingroup sss_pam_cli + * + * SSSD can send different kind of information back to the client. + * A response from the SSSD can contain 0 or more messages. Each message + * contains a type tag and the size of the message data, both are unsigned + * 32-bit integer values, followed be the message specific data. + * + * If the message is generated by a backend it is send back to the PAM + * responder via a D-BUS message in an array of D-BUS structs. The struct + * consists of a DBUS_TYPE_UINT32 for the tag and a DBUS_TYPE_ARRAY to hold + * the message. + * + * Examples: + * - #SSS_PAM_ENV_ITEM, + uint32_t | uint32_t | uint8_t[4] + ----------|----------|------------ + 0x03 | 0x04 | a=b\\0 + * @{ + */ + +/** Types of different messages */ + +enum response_type { + SSS_PAM_SYSTEM_INFO = 0x01, /**< Message for the system log. + * @param String, zero terminated. */ + SSS_PAM_DOMAIN_NAME, /**< Name of the domain the user belongs too. + * This messages is generated by the PAM responder. + * @param String, zero terminated, with the domain + * name. */ + SSS_PAM_ENV_ITEM, /**< Set and environment variable with pam_putenv(3). + * @param String, zero terminated, of the form + * name=value. See pam_putenv(3) for details. */ + SSS_ENV_ITEM, /**< Set and environment variable with putenv(3). + * @param String, zero terminated, of the form + * name=value. See putenv(3) for details. */ + SSS_ALL_ENV_ITEM, /**< Set and environment variable with putenv(3) and + * pam_putenv(3). + * @param String, zero terminated, of the form + * name=value. See putenv(3) and pam_putenv(3) for + * details. */ + SSS_PAM_USER_INFO, /**< A message which should be displayed to the user. + * @param User info message, see #user_info_type + * for details. */ + SSS_PAM_TEXT_MSG, /**< A plain text message which should be displayed to + * the user. This should only be used in the case where + * it is not possible to use SSS_PAM_USER_INFO. + * @param A zero terminated string. */ + SSS_PAM_OTP_INFO, /**< A message which optionally may contain the name + * of the vendor, the ID of an OTP token and a + * challenge. + * @param Three zero terminated strings, if one of the + * strings is missing the message will contain only + * an empty string (\0) for that component. */ + SSS_PAM_CERT_INFO, /**< A message indicating that Smartcard/certificate + * based authentication is available and contains + * details about the found Smartcard. + * @param user name, zero terminated + * @param token name, zero terminated + * @param PKCS#11 module name, zero terminated + * @param key id, zero terminated */ + SSS_OTP, /**< Indicates that the authtok was a OTP, so don't + * cache it. There is no message. + * @param None. */ + SSS_PASSWORD_PROMPTING, /**< Indicates that password prompting is possible. + * This might be used together with + * SSS_PAM_OTP_INFO to determine the type of + * prompting. There is no message. + * @param None. */ + SSS_CERT_AUTH_PROMPTING, /**< Indicates that on the server side + * Smartcard/certificate based authentication is + * available for the selected account. This might + * be used together with other prompting options + * to determine the type of prompting. + * @param None. */ + 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. */ + SSS_CHILD_KEEP_ALIVE, /**< Indicates that the child process is kept alived + * and further communication must be done with the + * same child. The message is the pid of the child + * process. */ + SSS_PAM_OAUTH2_INFO, /**< A message which contains the oauth2 + * parameters for the user. + * @param Three zero terminated strings: + * - verification_uri + * - verification_uri_complete + * - user_code + */ + SSS_PAM_PASSKEY_INFO, /**< Indicates that passkey authentication is available. + * including a parameter string which dictates whether + * prompting for PIN is needed. + * @param + * - prompt_pin + */ + SSS_PAM_PASSKEY_DEVINFO, /**< Indicates a passkey device information + * including a parameter string + * @param + * - device information string + */ + SSS_PAM_PASSKEY_KRB_INFO, /**< A message containing the passkey parameters + * for the user. The key is the cryptographic challenge + * used as the key to the passkey hash table entry. + * @param + * - user verification (string) + * - key (string) + */ +}; + +/** + * @defgroup user_info_type User info messages + * @ingroup response_type + * + * To achieve a consistent user experience and to facilitate + * internationalization all messages show to the user are generate by the PAM + * client and not by the SSSD server components. To indicate what message the + * client should display to the user SSSD can send a #SSS_PAM_USER_INFO message + * where the data part contains one of the following tags as an unsigned + * 32-bit integer value and optional data. + * + * Examples: + * - #SSS_PAM_USER_INFO_OFFLINE_CHPASS + * uint32_t | uint32_t | uint32_t + * ----------|----------|---------- + * 0x06 | 0x04 | 0x03 + * + * - #SSS_PAM_USER_INFO_CHPASS_ERROR + * uint32_t | uint32_t | uint32_t | uint32_t | uint8_t[3] + * ----------|----------|----------|----------|------------ + * 0x06 | 0x0B | 0x04 | 0x03 | abc + * @{ + */ + +/** Different types of user messages */ + +enum user_info_type { + SSS_PAM_USER_INFO_OFFLINE_AUTH = 0x01, /**< Inform the user that the + * authentication happened offline. + * This message is generated by the + * PAM responder. + * @param Time when the cached + * password will expire in seconds + * since the UNIX Epoch as returned + * by time(2) as int64_t. A value + * of zero indicates that the + * cached password will never + * expire. */ + SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED, /**< Tell the user how low a new + * authentication is delayed. This + * message is generated by the PAM + * responder. + * @param Time when an + * authentication is allowed again + * in seconds since the UNIX Epoch + * as returned by time(2) as + * int64_t. */ + SSS_PAM_USER_INFO_OFFLINE_CHPASS, /**< * Tell the user that it is not + * possible to change the password while + * the system is offline. This message + * is generated by the PAM responder. */ + SSS_PAM_USER_INFO_OTP_CHPASS, /**< Tell the user that he needs to kinit + * or login and logout to get a TGT after + * an OTP password change */ + SSS_PAM_USER_INFO_CHPASS_ERROR, /**< Tell the user that a password change + * failed and optionally give a reason. + * @param Size of the message as unsigned + * 32-bit integer value. A value of 0 + * indicates that no message is following. + * @param String with the specified + * length. */ + + SSS_PAM_USER_INFO_GRACE_LOGIN, /**< Warn the user that the password is + * expired and inform about the remaining + * number of grace logins. + * @param The number of remaining grace + * logins as uint32_t */ + SSS_PAM_USER_INFO_EXPIRE_WARN, /**< Warn the user that the password will + * expire soon. + * @param Number of seconds before the + * user's password will expire. */ + + SSS_PAM_USER_INFO_ACCOUNT_EXPIRED, /**< Tell the user that the account + * has expired and optionally give + * a reason. + * @param Size of the message as + * unsigned 32-bit integer value. A + * value of 0 indicates that no message + * is following. @param String with the + * specified length. */ + + SSS_PAM_USER_INFO_PIN_LOCKED, /**< Tell the user that the PIN is locked */ + SSS_PAM_USER_INFO_NO_KRB_TGT, /**< Tell the user that Kerberos local/offline + auth was performed, therefore no TGT + is granted */ +}; +/** + * @} + */ /* end of group user_info_type */ + +/** + * @} + */ /* end of group response_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_PASSKEY, + 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); +const char *pc_get_passkey_inter_prompt(struct prompt_config *pc); +const char *pc_get_passkey_touch_prompt(struct prompt_config *pc); +errno_t pc_list_add_passkey(struct prompt_config ***pc_list, + const char *inter_prompt, + const char *touch_prompt); +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 +}; + +enum sss_cli_error_codes { + ESSS_SSS_CLI_ERROR_START = 0x1000, + ESSS_BAD_SOCKET, + ESSS_BAD_CRED_MSG, + ESSS_SERVER_NOT_TRUSTED, + ESSS_NO_SOCKET, + ESSS_SOCKET_STAT_ERROR, + + ESS_SSS_CLI_ERROR_MAX +}; + +const char *ssscli_err2string(int err); + +enum sss_status sss_cli_make_request_with_checks(enum sss_cli_command cmd, + struct sss_cli_req_data *rd, + int timeout, + uint8_t **repbuf, size_t *replen, + int *errnop, + const char *socket_name, + bool check_server_creds, + bool allow_custom_errors); + +enum nss_status sss_nss_make_request(enum sss_cli_command cmd, + struct sss_cli_req_data *rd, + uint8_t **repbuf, size_t *replen, + int *errnop); + +enum nss_status sss_nss_make_request_timeout(enum sss_cli_command cmd, + struct sss_cli_req_data *rd, + int timeout, + uint8_t **repbuf, size_t *replen, + int *errnop); + +int sss_pam_make_request(enum sss_cli_command cmd, + struct sss_cli_req_data *rd, + uint8_t **repbuf, size_t *replen, + int *errnop); + +void sss_cli_close_socket(void); + +/* Checks access to the PAC responder and opens the socket, if available. + * Required for processes like krb5_child that need to open the socket + * before dropping privs. + */ +int sss_pac_check_and_open(void); + +int sss_pac_make_request(enum sss_cli_command cmd, + struct sss_cli_req_data *rd, + uint8_t **repbuf, size_t *replen, + int *errnop); + +int sss_pac_make_request_with_lock(enum sss_cli_command cmd, + struct sss_cli_req_data *rd, + uint8_t **repbuf, size_t *replen, + int *errnop); + +#if 0 + +/* GETSPNAM Request: + * + * 0-X: string with name + * + * Replies: + * + * 0-3: 32bit unsigned number of results + * 4-7: 32bit unsigned (reserved/padding) + * For each result: + * 0-7: 64bit unsigned with Date of last change + * 8-15: 64bit unsigned with Min #days between changes + * 16-23: 64bit unsigned with Max #days between changes + * 24-31: 64bit unsigned with #days before pwd expires + * 32-39: 64bit unsigned with #days after pwd expires until account is disabled + * 40-47: 64bit unsigned with expiration date in days since 1970-01-01 + * 48-55: 64bit unsigned (flags/reserved) + * 56-X: sequence of 2, 0 terminated, strings (name, pwd) 64bit padded + */ +#endif + +/* Return strlen(str) or maxlen, whichever is shorter + * Returns EINVAL if str is NULL, EFBIG if str is longer than maxlen + * _len will return the result + */ +errno_t sss_strnlen(const char *str, size_t maxlen, size_t *len); + +void sss_nss_lock(void); +void sss_nss_unlock(void); +void sss_pam_lock(void); +void sss_pam_unlock(void); +void sss_nss_mc_lock(void); +void sss_nss_mc_unlock(void); +void sss_pac_lock(void); +void sss_pac_unlock(void); + +errno_t sss_readrep_copy_string(const char *in, + size_t *offset, + size_t *slen, + size_t *dlen, + char **out, + size_t *size); + +enum pam_gssapi_cmd { + PAM_GSSAPI_GET_NAME, + PAM_GSSAPI_INIT, + PAM_GSSAPI_SENTINEL +}; + +#endif /* _SSSCLI_H */ From 916f6e99e7df8854a9358b8b148c23021b698284 Mon Sep 17 00:00:00 2001 From: Philippe Smadja Date: Mon, 16 Jun 2025 19:15:36 +0200 Subject: [PATCH 4/8] make check ok --- src/tests/system/data/test_ipa/certificate | 2 +- src/tests/system/data/test_ipa/public_keys | 2 +- src/tests/system/data/test_passkey/readme.md | 2 +- .../test_passkey/test_passkey__check_tgt/umockdev.script.ipa | 2 +- .../test_passkey__ipa_server_offline/umockdev.script.ipa | 2 +- .../test_passkey__lookup_user_from_cache/umockdev.script.ad | 2 +- .../test_passkey__lookup_user_from_cache/umockdev.script.ipa | 2 +- .../test_passkey__lookup_user_from_cache/umockdev.script.ldap | 2 +- .../test_passkey__lookup_user_from_cache/umockdev.script.samba | 2 +- .../test_passkey__prompt_options/umockdev.script.ipa | 2 +- .../test_passkey/test_passkey__register_ipa/umockdev.script | 2 +- .../test_passkey/test_passkey__register_sssctl/umockdev.script | 2 +- .../test_passkey__su_fallback_to_password/umockdev.script.ipa | 2 +- .../test_passkey__su_fips_fido_key/umockdev.script.ad | 2 +- .../test_passkey__su_fips_fido_key/umockdev.script.ipa | 2 +- .../test_passkey__su_fips_fido_key/umockdev.script.ldap | 2 +- .../test_passkey__su_fips_fido_key/umockdev.script.samba | 2 +- .../test_passkey__su_no_pin_set/umockdev.script.ipa | 2 +- .../data/test_passkey/test_passkey__su_user/umockdev.script.ad | 2 +- .../data/test_passkey/test_passkey__su_user/umockdev.script.ipa | 2 +- .../test_passkey/test_passkey__su_user/umockdev.script.ldap | 2 +- .../test_passkey/test_passkey__su_user/umockdev.script.samba | 2 +- .../umockdev.script.ad.user1 | 2 +- .../umockdev.script.ad.user2 | 2 +- .../umockdev.script.ad.user3 | 2 +- .../umockdev.script.ipa.user1 | 2 +- .../umockdev.script.ipa.user2 | 2 +- .../umockdev.script.ipa.user3 | 2 +- .../umockdev.script.ldap.user1 | 2 +- .../umockdev.script.ldap.user2 | 2 +- .../umockdev.script.ldap.user3 | 2 +- .../umockdev.script.samba.user1 | 2 +- .../umockdev.script.samba.user2 | 2 +- .../umockdev.script.samba.user3 | 2 +- .../umockdev.script.ad | 2 +- .../umockdev.script.ldap | 2 +- .../umockdev.script.samba | 2 +- .../test_passkey__su_user_when_offline/umockdev.script.ad | 2 +- .../test_passkey__su_user_when_offline/umockdev.script.ipa | 2 +- .../test_passkey__su_user_when_offline/umockdev.script.ldap | 2 +- .../test_passkey__su_user_when_offline/umockdev.script.samba | 2 +- .../passkey-mapping.ipa | 2 +- .../passkey-mapping.ldap | 2 +- .../umockdev.script.ad | 2 +- .../umockdev.script.ipa | 2 +- .../umockdev.script.ldap | 2 +- .../umockdev.script.samba | 2 +- .../test_passkey__su_user_with_failed_pin/umockdev.script.ad | 2 +- .../test_passkey__su_user_with_failed_pin/umockdev.script.ipa | 2 +- .../test_passkey__su_user_with_failed_pin/umockdev.script.ldap | 2 +- .../test_passkey__su_user_with_failed_pin/umockdev.script.samba | 2 +- .../umockdev.script.ad | 2 +- .../umockdev.script.ipa | 2 +- .../umockdev.script.ldap | 2 +- .../umockdev.script.samba | 2 +- .../test_passkey__su_user_with_multiple_keys/umockdev.script.ad | 2 +- .../umockdev.script.ipa | 2 +- .../umockdev.script.ldap | 2 +- .../umockdev.script.samba | 2 +- .../test_passkey__su_with_12_mappings/umockdev.script.ipa | 2 +- 60 files changed, 60 insertions(+), 60 deletions(-) diff --git a/src/tests/system/data/test_ipa/certificate b/src/tests/system/data/test_ipa/certificate index 068327aba16..0d2892902f1 100644 --- a/src/tests/system/data/test_ipa/certificate +++ b/src/tests/system/data/test_ipa/certificate @@ -1 +1 @@ -MIIC0TCCAbmgAwIBAgIUO2P/fhnIwU5C6XzvBEAsTo9EuGowDQYJKoZIhvcNAQELBQAwETEPMA0GA1UEAwwGdXNlci00MB4XDTI1MDIxOTEwMDU0MloXDTI2MDIxOTEwMDU0MlowETEPMA0GA1UEAwwGdXNlci00MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqTTyBCieYZ+mrQ1C/0cQOqginRELEOzUOMkhirHGr0mQpivNM1VTz4g+ZyJmofKgh25+VC1KzQiFceM8ZFcG58/I8+dwrGhhys4GAT5UHdD0XRQJ4Z0uxlbEXL48K7QnzDxSPPpxmCOu4kTLju+OinfwFQNSvV6Q+zydSHUI4UQ5cSfHj8ZxU6jfIQRg82+eBig4liynQsf1XGX6wwGPZ50Dasx5KVtgo2+ssUVFb62i7lmQdLxmuRzSaEfbauaaq/ZRXQvePGpMVg5PgrWlCWFz2hoGg+AEbqDhDMAfo0yE14dj+Nrz3huWvz6zNOuosau/BUQ/ZhtEk6Ath43WtwIDAQABoyEwHzAdBgNVHQ4EFgQUYyljRt30Bhs5URza+ZUKRm16s1gwDQYJKoZIhvcNAQELBQADggEBAF+utdu1lpSR9RzfgZM1J6Zy2bBSe0XCnnYLb3UNwAcAi33ZtLURmD8ypA8jzU1MrJvuuFT6heWRVIWEtUjl6ofXmHigyTTUXnp+l4GtOYH45nwQ/8HrfDpr0DvpYKemUOC2FOmBZB23OBLK2rrdBhJ7op2s8QD2aDgfnkIy2/woPwyIWucl69Lm7K6IAR+22op5OQI/XVEy+WcFZY99qxwCBp8S9ID4M8wqL72VOYEG/JKhO4whXvcaSuSwQgGqXdQGHEtCIzvK4K3s7qm2iOmw5KAv4cegVzRuDBGW7HzJm3nu4sOwOxAYNQkDYlolgeTc2jhQrZshBGVC/pIhtVs= \ No newline at end of file +MIIC0TCCAbmgAwIBAgIUO2P/fhnIwU5C6XzvBEAsTo9EuGowDQYJKoZIhvcNAQELBQAwETEPMA0GA1UEAwwGdXNlci00MB4XDTI1MDIxOTEwMDU0MloXDTI2MDIxOTEwMDU0MlowETEPMA0GA1UEAwwGdXNlci00MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqTTyBCieYZ+mrQ1C/0cQOqginRELEOzUOMkhirHGr0mQpivNM1VTz4g+ZyJmofKgh25+VC1KzQiFceM8ZFcG58/I8+dwrGhhys4GAT5UHdD0XRQJ4Z0uxlbEXL48K7QnzDxSPPpxmCOu4kTLju+OinfwFQNSvV6Q+zydSHUI4UQ5cSfHj8ZxU6jfIQRg82+eBig4liynQsf1XGX6wwGPZ50Dasx5KVtgo2+ssUVFb62i7lmQdLxmuRzSaEfbauaaq/ZRXQvePGpMVg5PgrWlCWFz2hoGg+AEbqDhDMAfo0yE14dj+Nrz3huWvz6zNOuosau/BUQ/ZhtEk6Ath43WtwIDAQABoyEwHzAdBgNVHQ4EFgQUYyljRt30Bhs5URza+ZUKRm16s1gwDQYJKoZIhvcNAQELBQADggEBAF+utdu1lpSR9RzfgZM1J6Zy2bBSe0XCnnYLb3UNwAcAi33ZtLURmD8ypA8jzU1MrJvuuFT6heWRVIWEtUjl6ofXmHigyTTUXnp+l4GtOYH45nwQ/8HrfDpr0DvpYKemUOC2FOmBZB23OBLK2rrdBhJ7op2s8QD2aDgfnkIy2/woPwyIWucl69Lm7K6IAR+22op5OQI/XVEy+WcFZY99qxwCBp8S9ID4M8wqL72VOYEG/JKhO4whXvcaSuSwQgGqXdQGHEtCIzvK4K3s7qm2iOmw5KAv4cegVzRuDBGW7HzJm3nu4sOwOxAYNQkDYlolgeTc2jhQrZshBGVC/pIhtVs= diff --git a/src/tests/system/data/test_ipa/public_keys b/src/tests/system/data/test_ipa/public_keys index 9a7b6d742e6..2ac32fd9985 100644 --- a/src/tests/system/data/test_ipa/public_keys +++ b/src/tests/system/data/test_ipa/public_keys @@ -1,3 +1,3 @@ ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCI56aGtsWIbjz8XhODRT8NAio+TIHMXdiKoG6SdCtVlCR6xNP6gXgmChVWJ9DXlOF3WztOBf9om5SsPGX73/to= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPeSgRv4Vyq6ehrcA8hc6LFd9cUR1H3vdtH+WMJXvf1h -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4O+2dXWi4iTJR3vlsqZE9707K0sKTeEnWHyMoWUY0N/p6TZgxoLWfUW8XPbj4Yt4BaI6M48/jWZjHjXPJjLfgon3BT5LvyZHrlnN34APcZ7+r73mMt4pIPI7WnzqJluRGgcdEQuJhaSUbTGBUoHwCmp5JAREqWMappkwwSo9QQEzeAxT58dLTEyENTxB1DCP7sJShZd9p+37+5XJ1m1fkpnpDb/JMnTX9jSApk6r2DOZgcpX2aNFMbxSQ5zASfhmzJCfx52c97GD85Zx+LMXBJ4KzNKAbvTsruFj3k5jh7pYvqZseUuReBDCjSGQ4Q4MDBm+XAPbCNfQcgLPo678kkIFK8bZfKLDeJH4g64EO1xHGxBhmcYiXkc0gyqpECKekiUb3ltkyUA/R+Q8Jo+Zr70IHEM2fFvyiIqyz2Nx3PH/FTLDSBguQuEtzBHcMfb9/sO8lbofJneAfsAk3C3TYxqJ176xYqk4C24pB8UrOAflni+P0RqS1Zwte2u3ZD0E= \ No newline at end of file +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4O+2dXWi4iTJR3vlsqZE9707K0sKTeEnWHyMoWUY0N/p6TZgxoLWfUW8XPbj4Yt4BaI6M48/jWZjHjXPJjLfgon3BT5LvyZHrlnN34APcZ7+r73mMt4pIPI7WnzqJluRGgcdEQuJhaSUbTGBUoHwCmp5JAREqWMappkwwSo9QQEzeAxT58dLTEyENTxB1DCP7sJShZd9p+37+5XJ1m1fkpnpDb/JMnTX9jSApk6r2DOZgcpX2aNFMbxSQ5zASfhmzJCfx52c97GD85Zx+LMXBJ4KzNKAbvTsruFj3k5jh7pYvqZseUuReBDCjSGQ4Q4MDBm+XAPbCNfQcgLPo678kkIFK8bZfKLDeJH4g64EO1xHGxBhmcYiXkc0gyqpECKekiUb3ltkyUA/R+Q8Jo+Zr70IHEM2fFvyiIqyz2Nx3PH/FTLDSBguQuEtzBHcMfb9/sO8lbofJneAfsAk3C3TYxqJ176xYqk4C24pB8UrOAflni+P0RqS1Zwte2u3ZD0E= diff --git a/src/tests/system/data/test_passkey/readme.md b/src/tests/system/data/test_passkey/readme.md index 001fa3d3c85..a64f4222015 100644 --- a/src/tests/system/data/test_passkey/readme.md +++ b/src/tests/system/data/test_passkey/readme.md @@ -65,4 +65,4 @@ LD_PRELOAD=/opt/random.so umockdev-run --device /tmp/umockdev.device --script ${ Insert your passkey device, then press ENTER. Enter PIN: user1 -``` \ No newline at end of file +``` diff --git a/src/tests/system/data/test_passkey/test_passkey__check_tgt/umockdev.script.ipa b/src/tests/system/data/test_passkey/test_passkey__check_tgt/umockdev.script.ipa index 831ad5d490c..fa04b4963f0 100644 --- a/src/tests/system/data/test_passkey/test_passkey__check_tgt/umockdev.script.ipa +++ b/src/tests/system/data/test_passkey/test_passkey__check_tgt/umockdev.script.ipa @@ -22,4 +22,4 @@ r 289 ^C r 291 ^C^Dֻ^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 289 ^C^Dֻ^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 291 ^C^Dֻ^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 289 ^C^Dֻ^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^D֐^@^@^AbidX@Q@ rj뀈,EAX+a<^H>*H^K^NKHa@^]=R^IY%^C^D^@^Xو*x^_HEQgqdtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/o^C^D^AC}.[^E^@^@^@^V^CXH0F^B!^@pC^^p3G^K|NjyZe˒e6d^B!^@^L^C^D^BV^Al^J4y1"ApZm|w^L^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 289 ^C^Dֻ^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^D֐^@^@^AbidX@Q@ rj뀈,EAX+a<^H>*H^K^NKHa@^]=R^IY%^C^D^@^Xو*x^_HEQgqdtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/o^C^D^AC}.[^E^@^@^@^V^CXH0F^B!^@pC^^p3G^K|NjyZe˒e6d^B!^@^L^C^D^BV^Al^J4y1"ApZm|w^L^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__ipa_server_offline/umockdev.script.ipa b/src/tests/system/data/test_passkey/test_passkey__ipa_server_offline/umockdev.script.ipa index 0aa3a56e5ba..4bfd4d09ef1 100644 --- a/src/tests/system/data/test_passkey/test_passkey__ipa_server_offline/umockdev.script.ipa +++ b/src/tests/system/data/test_passkey/test_passkey__ipa_server_offline/umockdev.script.ipa @@ -19,4 +19,4 @@ r 288 r 290 rl^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 rl^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 rl^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 222 rl^@^@^AbidX@Ƥ0SWE>X[w^U)n{=mu}M^_!rl^@LtWf^HY3c$…dtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/orl^AC}.[^E^@^@^@^O^CXF0D^B ^A"^IJ,R^DR^R^Cr-1"Ӧ!I^B nKLsrl^B^_b5\A^U}¸)^I^Aw=^D^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 222 rl^@^@^AbidX@Ƥ0SWE>X[w^U)n{=mu}M^_!rl^@LtWf^HY3c$…dtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/orl^AC}.[^E^@^@^@^O^CXF0D^B ^A"^IJ,R^DR^R^Cr-1"Ӧ!I^B nKLsrl^B^_b5\A^U}¸)^I^Aw=^D^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__lookup_user_from_cache/umockdev.script.ad b/src/tests/system/data/test_passkey/test_passkey__lookup_user_from_cache/umockdev.script.ad index e9864e7f2da..b76bae3ed35 100644 --- a/src/tests/system/data/test_passkey/test_passkey__lookup_user_from_cache/umockdev.script.ad +++ b/src/tests/system/data/test_passkey/test_passkey__lookup_user_from_cache/umockdev.script.ad @@ -16,4 +16,4 @@ r 288 r 288 ^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 ^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 ^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 52 ^@^@^AbidX@Z;*?^O[u!^Z0=bUG^TqFp^O]^HחI7%^@Lb.Fm4m^Mcdtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YD^AA,K!^E^@^@^@^X^CXF0D^B ^N^A^Zb;u.^S^_v}v{^K'Y{^B 4^H^BɩXû^DAe3A'bU_^M`[Ay^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 52 ^@^@^AbidX@Z;*?^O[u!^Z0=bUG^TqFp^O]^HחI7%^@Lb.Fm4m^Mcdtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YD^AA,K!^E^@^@^@^X^CXF0D^B ^N^A^Zb;u.^S^_v}v{^K'Y{^B 4^H^BɩXû^DAe3A'bU_^M`[Ay^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__lookup_user_from_cache/umockdev.script.ipa b/src/tests/system/data/test_passkey/test_passkey__lookup_user_from_cache/umockdev.script.ipa index 1bc651a4465..3be6a85a87c 100644 --- a/src/tests/system/data/test_passkey/test_passkey__lookup_user_from_cache/umockdev.script.ipa +++ b/src/tests/system/data/test_passkey/test_passkey__lookup_user_from_cache/umockdev.script.ipa @@ -19,4 +19,4 @@ r 288 r 290 p^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 p^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 p^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 174 p^@^@^AbidX@5FLEEo"=F^@^] l\^KP"~֦E^Gc^\;3p^@ۏ$^]^Aj~j^JL&dtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/op^AC}.[^E^@^@^@^\^CXG0E^B Eo]Wk%^H$-^W^BR^`x^P׀^B!^@ёqp^Bu9\^ZKR&>/͎[`t,^^eg^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 174 p^@^@^AbidX@5FLEEo"=F^@^] l\^KP"~֦E^Gc^\;3p^@ۏ$^]^Aj~j^JL&dtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/op^AC}.[^E^@^@^@^\^CXG0E^B Eo]Wk%^H$-^W^BR^`x^P׀^B!^@ёqp^Bu9\^ZKR&>/͎[`t,^^eg^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__lookup_user_from_cache/umockdev.script.ldap b/src/tests/system/data/test_passkey/test_passkey__lookup_user_from_cache/umockdev.script.ldap index db026662951..96f6c76fc47 100644 --- a/src/tests/system/data/test_passkey/test_passkey__lookup_user_from_cache/umockdev.script.ldap +++ b/src/tests/system/data/test_passkey/test_passkey__lookup_user_from_cache/umockdev.script.ldap @@ -18,4 +18,4 @@ r 288 ^S r 288 ^S=^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 ^S=^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 ^S=^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 28 ^S=^@^@^AbidX@^A^TMgm^L-^CS^MW4y_ j\W-VcXG^L^B^W^Oޚt^S=^@4R5vv7M^Fdtypejpublic-key^BX%ЁL}e/Z^UO^[+^K,^S=^A]l^U^J^H^E^@^@^@^G^CXF0D^B j8^M\:H^O@%qt(^\/Ǻ~$$!>;^B u^S=^B{9AbF6^Xs5^K*ywv^L^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 28 ^S=^@^@^AbidX@^A^TMgm^L-^CS^MW4y_ j\W-VcXG^L^B^W^Oޚt^S=^@4R5vv7M^Fdtypejpublic-key^BX%ЁL}e/Z^UO^[+^K,^S=^A]l^U^J^H^E^@^@^@^G^CXF0D^B j8^M\:H^O@%qt(^\/Ǻ~$$!>;^B u^S=^B{9AbF6^Xs5^K*ywv^L^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__lookup_user_from_cache/umockdev.script.samba b/src/tests/system/data/test_passkey/test_passkey__lookup_user_from_cache/umockdev.script.samba index 4c0b6e23e92..45f9af8373e 100644 --- a/src/tests/system/data/test_passkey/test_passkey__lookup_user_from_cache/umockdev.script.samba +++ b/src/tests/system/data/test_passkey/test_passkey__lookup_user_from_cache/umockdev.script.samba @@ -16,4 +16,4 @@ r 246 n r 289 n5Ż^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 287 n5Ż^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 289 n5Ż^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 75 n5Ő^@^@^AbidX@ŀn&X9T$HΚ'm^S^Ys^F$m7^Iksn5^@۬JL^Xqdtypejpublic-key^BX% ^S@^_n1n;Qm\<ִ>n5^A`^_W^E^@^@^@F^CXH0F^B!^@/2^_1,,0^ABd^FKZ@m^B!^@^S&n5^BO|FoзN$<ˣ!W\^V^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 75 n5Ő^@^@^AbidX@ŀn&X9T$HΚ'm^S^Ys^F$m7^Iksn5^@۬JL^Xqdtypejpublic-key^BX% ^S@^_n1n;Qm\<ִ>n5^A`^_W^E^@^@^@F^CXH0F^B!^@/2^_1,,0^ABd^FKZ@m^B!^@^S&n5^BO|FoзN$<ˣ!W\^V^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__prompt_options/umockdev.script.ipa b/src/tests/system/data/test_passkey/test_passkey__prompt_options/umockdev.script.ipa index 0e88c0f935e..289296f4597 100644 --- a/src/tests/system/data/test_passkey/test_passkey__prompt_options/umockdev.script.ipa +++ b/src/tests/system/data/test_passkey/test_passkey__prompt_options/umockdev.script.ipa @@ -27,4 +27,4 @@ r 288 r 290 ˏ-^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 ˏ-^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 ˏ-^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 198 ˏ-^@^@^AbidX@n]^KRg8^C$wJˡƲ婟Cx^RV^B ^A>nnUU0Q\]՟^CGcg8^Dx5cY^B0^B0^A^C^B^A^B^B^I^@9*7_80^M^F^I*H^M^A^A^K^E^@0.1,0*^F^CU^D^C^S#g8^EYubico U2F Root CA Serial 4572006310 ^W^M140801000000Z^X^O20500g8^F904000000Z0n1^K0^I^F^CU^D^F^S^BSE1^R0^P^F^CU^D^J^L^IYubico AB1"0 ^F^CU^D^K^L^YAutg8^Ghenticator Attestation1'0%^F^CU^D^C^L^^Yubico U2F EE Serial 92551g8^H41600Y0^S^F^G*H=^B^A^F^H*H=^C^A^G^CB^@^DS0^Nȣ\^G2V^FL$]^MSX^Kg8^IsG^O^W^Uyyhp\^W^C&oۆ^U#£00^S^F^J+^F^A^D^A^J^M^A^D^E^D^Cg8^J^E^D^C0"^F^I+^F^A^D^A^J^B^D^U1.3.6.1.4.1.41482.1.70^S^F^K+^F^A^D^A^\^B^A^A^D^D^C^B^Dg8^K00!^F^K+^F^A^D^A^\^A^A^D^D^R^D^P/W^SG^VZ *0^L^F^CU^]^S^A^A^D^B0^@0^M^F^I*Hg8^L^M^A^A^K^E^@^C^A^A^@^Ai1d;I^O!/X,H^\_^X"t9e>3J]^KbP^N^D<ԓg8^MpI^Hw^J^P=^VN^Rr`C6"̜j^G-x'^_^A^^jE| {V^Rg|%51g8^NKөuԼd~^R,޵/u^T^@\b^_*G^Hۨy%M^V:E*DN^T^YvX6^N^M^`g8^OE^V^N)"L^HP`nw<\S~rOoL*2r?Q^H.k[]ȊLDNEg8^P;31V^P~#6!^K((g!B^Kv^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 291 g8^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@g8^D^H^@^Afpacked^BXĜ^@|Dږ^Qx??d^Q/N4/oC}.[E^@^@^@^B/W^SGg8^@^VZ *^@@^O^U$ӂb(ݎ^S^P,^Os^JtC"6_;^Uځ^\^@ [g8^A^Q^YZ 8^^y^D^N^A^B^C& ^A!X ʼZzJH_^Lý^_\Z^O^F^H^\I,{b"g8^BX jV$/ybgw|i^Z;fRy^[(ZU^Ccalg&csigXF0D^B >]^KRg8^C$wJˡƲ婟Cx^RV^B ^A>nnUU0Q\]՟^CGcg8^Dx5cY^B0^B0^A^C^B^A^B^B^I^@9*7_80^M^F^I*H^M^A^A^K^E^@0.1,0*^F^CU^D^C^S#g8^EYubico U2F Root CA Serial 4572006310 ^W^M140801000000Z^X^O20500g8^F904000000Z0n1^K0^I^F^CU^D^F^S^BSE1^R0^P^F^CU^D^J^L^IYubico AB1"0 ^F^CU^D^K^L^YAutg8^Ghenticator Attestation1'0%^F^CU^D^C^L^^Yubico U2F EE Serial 92551g8^H41600Y0^S^F^G*H=^B^A^F^H*H=^C^A^G^CB^@^DS0^Nȣ\^G2V^FL$]^MSX^Kg8^IsG^O^W^Uyyhp\^W^C&oۆ^U#£00^S^F^J+^F^A^D^A^J^M^A^D^E^D^Cg8^J^E^D^C0"^F^I+^F^A^D^A^J^B^D^U1.3.6.1.4.1.41482.1.70^S^F^K+^F^A^D^A^\^B^A^A^D^D^C^B^Dg8^K00!^F^K+^F^A^D^A^\^A^A^D^D^R^D^P/W^SG^VZ *0^L^F^CU^]^S^A^A^D^B0^@0^M^F^I*Hg8^L^M^A^A^K^E^@^C^A^A^@^Ai1d;I^O!/X,H^\_^X"t9e>3J]^KbP^N^D<ԓg8^MpI^Hw^J^P=^VN^Rr`C6"̜j^G-x'^_^A^^jE| {V^Rg|%51g8^NKөuԼd~^R,޵/u^T^@\b^_*G^Hۨy%M^V:E*DN^T^YvX6^N^M^`g8^OE^V^N)"L^HP`nw<\S~rOoL*2r?Q^H.k[]ȊLDNEg8^P;31V^P~#6!^K((g!B^Kv^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__register_sssctl/umockdev.script b/src/tests/system/data/test_passkey/test_passkey__register_sssctl/umockdev.script index 9e04a725cc5..9c600c1c71a 100644 --- a/src/tests/system/data/test_passkey/test_passkey__register_sssctl/umockdev.script +++ b/src/tests/system/data/test_passkey/test_passkey__register_sssctl/umockdev.script @@ -16,4 +16,4 @@ r 288 2qu r 290 2qu^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 2qu^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 2qu^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 170 2qu^D^H^@^Afpacked^BXD^^^W^D@+0w7-яD^I^E^H7E^@^@^@^A/W^SG2qu^@^VZ *^@@?qD^V(E/[R^SJ%kK69#4^W^ABA9^IE虿2qu^Aw^TwjY^Z%~F#^A^B^C& ^A!X ~$Gv71!^`P^W-^A^@^]_[^Afo^X:x"2qu^BX #?5FFl^N"kEV^Z$^S֣.jc-^B^Ccalg&csigXF0D^B ^Y1Q]2qu^CC^I>^PyIlr}^A\^L.?^B X9S^N鵭8t^\"2:[c2qu^Dx5cY^B0^B0^A^C^B^A^B^B^I^@9*7_80^M^F^I*H^M^A^A^K^E^@0.1,0*^F^CU^D^C^S#2qu^EYubico U2F Root CA Serial 4572006310 ^W^M140801000000Z^X^O205002qu^F904000000Z0n1^K0^I^F^CU^D^F^S^BSE1^R0^P^F^CU^D^J^L^IYubico AB1"0 ^F^CU^D^K^L^YAut2qu^Ghenticator Attestation1'0%^F^CU^D^C^L^^Yubico U2F EE Serial 925512qu^H41600Y0^S^F^G*H=^B^A^F^H*H=^C^A^G^CB^@^DS0^Nȣ\^G2V^FL$]^MSX^K2qu^IsG^O^W^Uyyhp\^W^C&oۆ^U#£00^S^F^J+^F^A^D^A^J^M^A^D^E^D^C2qu^J^E^D^C0"^F^I+^F^A^D^A^J^B^D^U1.3.6.1.4.1.41482.1.70^S^F^K+^F^A^D^A^\^B^A^A^D^D^C^B^D2qu^K00!^F^K+^F^A^D^A^\^A^A^D^D^R^D^P/W^SG^VZ *0^L^F^CU^]^S^A^A^D^B0^@0^M^F^I*H2qu^L^M^A^A^K^E^@^C^A^A^@^Ai1d;I^O!/X,H^\_^X"t9e>3J]^KbP^N^D<ԓ2qu^MpI^Hw^J^P=^VN^Rr`C6"̜j^G-x'^_^A^^jE| {V^Rg|%512qu^NKөuԼd~^R,޵/u^T^@\b^_*G^Hۨy%M^V:E*DN^T^YvX6^N^M^`2qu^OE^V^N)"L^HP`nw<\S~rOoL*2r?Q^H.k[]ȊLDNE2qu^P;31V^P~#6!^K((g!B^Kv^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 170 2qu^D^H^@^Afpacked^BXD^^^W^D@+0w7-яD^I^E^H7E^@^@^@^A/W^SG2qu^@^VZ *^@@?qD^V(E/[R^SJ%kK69#4^W^ABA9^IE虿2qu^Aw^TwjY^Z%~F#^A^B^C& ^A!X ~$Gv71!^`P^W-^A^@^]_[^Afo^X:x"2qu^BX #?5FFl^N"kEV^Z$^S֣.jc-^B^Ccalg&csigXF0D^B ^Y1Q]2qu^CC^I>^PyIlr}^A\^L.?^B X9S^N鵭8t^\"2:[c2qu^Dx5cY^B0^B0^A^C^B^A^B^B^I^@9*7_80^M^F^I*H^M^A^A^K^E^@0.1,0*^F^CU^D^C^S#2qu^EYubico U2F Root CA Serial 4572006310 ^W^M140801000000Z^X^O205002qu^F904000000Z0n1^K0^I^F^CU^D^F^S^BSE1^R0^P^F^CU^D^J^L^IYubico AB1"0 ^F^CU^D^K^L^YAut2qu^Ghenticator Attestation1'0%^F^CU^D^C^L^^Yubico U2F EE Serial 925512qu^H41600Y0^S^F^G*H=^B^A^F^H*H=^C^A^G^CB^@^DS0^Nȣ\^G2V^FL$]^MSX^K2qu^IsG^O^W^Uyyhp\^W^C&oۆ^U#£00^S^F^J+^F^A^D^A^J^M^A^D^E^D^C2qu^J^E^D^C0"^F^I+^F^A^D^A^J^B^D^U1.3.6.1.4.1.41482.1.70^S^F^K+^F^A^D^A^\^B^A^A^D^D^C^B^D2qu^K00!^F^K+^F^A^D^A^\^A^A^D^D^R^D^P/W^SG^VZ *0^L^F^CU^]^S^A^A^D^B0^@0^M^F^I*H2qu^L^M^A^A^K^E^@^C^A^A^@^Ai1d;I^O!/X,H^\_^X"t9e>3J]^KbP^N^D<ԓ2qu^MpI^Hw^J^P=^VN^Rr`C6"̜j^G-x'^_^A^^jE| {V^Rg|%512qu^NKөuԼd~^R,޵/u^T^@\b^_*G^Hۨy%M^V:E*DN^T^YvX6^N^M^`2qu^OE^V^N)"L^HP`nw<\S~rOoL*2r?Q^H.k[]ȊLDNE2qu^P;31V^P~#6!^K((g!B^Kv^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_fallback_to_password/umockdev.script.ipa b/src/tests/system/data/test_passkey/test_passkey__su_fallback_to_password/umockdev.script.ipa index c8e82d860e1..cc4b65ec4a3 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_fallback_to_password/umockdev.script.ipa +++ b/src/tests/system/data/test_passkey/test_passkey__su_fallback_to_password/umockdev.script.ipa @@ -26,4 +26,4 @@ r 288 rE r 290 rE^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 rE^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 rE^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 126 rE^@^@^AbidX@I(^NQ7ȇ^@^E7a=ES`?F^X^@6^VŞ4eurE^@^OTH6:c^^dtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/orE^AC}.[^E^@^@^@^J^CXH0F^B!^@L^ZK^F^T8G1[^JFLF"^R^B!^@frE^Bp^C^JAPvP5/^Eyv^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 126 rE^@^@^AbidX@I(^NQ7ȇ^@^E7a=ES`?F^X^@6^VŞ4eurE^@^OTH6:c^^dtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/orE^AC}.[^E^@^@^@^J^CXH0F^B!^@L^ZK^F^T8G1[^JFLF"^R^B!^@frE^Bp^C^JAPvP5/^Eyv^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_fips_fido_key/umockdev.script.ad b/src/tests/system/data/test_passkey/test_passkey__su_fips_fido_key/umockdev.script.ad index 3c737812ce4..ec7d74277ad 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_fips_fido_key/umockdev.script.ad +++ b/src/tests/system/data/test_passkey/test_passkey__su_fips_fido_key/umockdev.script.ad @@ -20,4 +20,4 @@ r 290 ^K, r 288 ^K,E^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 ^K,E^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 ^K,E^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 76 ^K,E^@^@^AbidX@^_K^Yr:6^@Buj$~^[^R^^{o^@D당w^[iq ^^+^K,E^@3gv-?e5>|dtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YD^K,E^AA,K!^E^@^@^@^D^CXG0E^B f0d^M P^OKOOqF^B^_^DK4^L^B!^@VT^K,E^B^GxGF_*⶞^O^S޲v^@A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 76 ^K,E^@^@^AbidX@^_K^Yr:6^@Buj$~^[^R^^{o^@D당w^[iq ^^+^K,E^@3gv-?e5>|dtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YD^K,E^AA,K!^E^@^@^@^D^CXG0E^B f0d^M P^OKOOqF^B^_^DK4^L^B!^@VT^K,E^B^GxGF_*⶞^O^S޲v^@A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_fips_fido_key/umockdev.script.ipa b/src/tests/system/data/test_passkey/test_passkey__su_fips_fido_key/umockdev.script.ipa index 4381bd7ba88..aba6addee23 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_fips_fido_key/umockdev.script.ipa +++ b/src/tests/system/data/test_passkey/test_passkey__su_fips_fido_key/umockdev.script.ipa @@ -17,4 +17,4 @@ r 290 Y)0Y r 288 Y)0Y^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 Y)0Y^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 289 Y)0Y^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 149 Y)0Y^@^@^AbidX@_kDeӘ^Vr^K^@RuYa^G]7^HqEb^``^O^U/1^T^@oT?Y)0Y^@B^SY2%g~b^K^Fydtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/oY)0Y^AC}.[^E^@^@^@^Q^CXF0D^B ^B^[<^L^Eڦ;)1\^@Ϳ95=)^B fþ%Y)0Y^B^W|^R^Do)K^`^B^HlW\)^E^Rv^U^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 149 Y)0Y^@^@^AbidX@_kDeӘ^Vr^K^@RuYa^G]7^HqEb^``^O^U/1^T^@oT?Y)0Y^@B^SY2%g~b^K^Fydtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/oY)0Y^AC}.[^E^@^@^@^Q^CXF0D^B ^B^[<^L^Eڦ;)1\^@Ϳ95=)^B fþ%Y)0Y^B^W|^R^Do)K^`^B^HlW\)^E^Rv^U^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_fips_fido_key/umockdev.script.ldap b/src/tests/system/data/test_passkey/test_passkey__su_fips_fido_key/umockdev.script.ldap index 9c6e3effd91..70f409da31a 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_fips_fido_key/umockdev.script.ldap +++ b/src/tests/system/data/test_passkey/test_passkey__su_fips_fido_key/umockdev.script.ldap @@ -18,4 +18,4 @@ r 288 r 287 L_^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 L_^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 L_^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 244 L_^@^@^AbidX@cЬm^]pv+hh^HuU^A`^_W^E^@^@^@^L^CXG0E^B >uy{Y^HU^\=1O#^^^S^B!^@^EQU^Bg^JJ{^JYZW^U^Q^Ia$^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 124 U^@^@^AbidX@^Ls^Yb:o['٠E&*ܜΠ^Ox^O^WB*^VU^@t^]i^D^K}^X3f\^]dtypejpublic-key^BX% ^S@^_n1n;Qm\<ִ>U^A`^_W^E^@^@^@^L^CXG0E^B >uy{Y^HU^\=1O#^^^S^B!^@^EQU^Bg^JJ{^JYZW^U^Q^Ia$^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_no_pin_set/umockdev.script.ipa b/src/tests/system/data/test_passkey/test_passkey__su_no_pin_set/umockdev.script.ipa index a76e7072e1b..72b37a104b7 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_no_pin_set/umockdev.script.ipa +++ b/src/tests/system/data/test_passkey/test_passkey__su_no_pin_set/umockdev.script.ipa @@ -15,4 +15,4 @@ r 290 r 288 c^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 291 c^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 c^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 221 c^@^@^AbidX@^^^F%FM^Xq$U^V^RLi^FI#^RU"!5%^J^_c^@`^G8^O9)Kdtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/oc^AC}.[^A^@^@^@^M^CXF0D^B e_r^Yb#V4j-RF㬑^YLܱ}^B Ag^Rc^BhQ^Jc7^^^V[xg0^`^Hӆ^D^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 221 c^@^@^AbidX@^^^F%FM^Xq$U^V^RLi^FI#^RU"!5%^J^_c^@`^G8^O9)Kdtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/oc^AC}.[^A^@^@^@^M^CXF0D^B e_r^Yb#V4j-RF㬑^YLܱ}^B Ag^Rc^BhQ^Jc7^^^V[xg0^`^Hӆ^D^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user/umockdev.script.ad b/src/tests/system/data/test_passkey/test_passkey__su_user/umockdev.script.ad index e9864e7f2da..b76bae3ed35 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user/umockdev.script.ad +++ b/src/tests/system/data/test_passkey/test_passkey__su_user/umockdev.script.ad @@ -16,4 +16,4 @@ r 288 r 288 ^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 ^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 ^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 52 ^@^@^AbidX@Z;*?^O[u!^Z0=bUG^TqFp^O]^HחI7%^@Lb.Fm4m^Mcdtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YD^AA,K!^E^@^@^@^X^CXF0D^B ^N^A^Zb;u.^S^_v}v{^K'Y{^B 4^H^BɩXû^DAe3A'bU_^M`[Ay^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 52 ^@^@^AbidX@Z;*?^O[u!^Z0=bUG^TqFp^O]^HחI7%^@Lb.Fm4m^Mcdtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YD^AA,K!^E^@^@^@^X^CXF0D^B ^N^A^Zb;u.^S^_v}v{^K'Y{^B 4^H^BɩXû^DAe3A'bU_^M`[Ay^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user/umockdev.script.ipa b/src/tests/system/data/test_passkey/test_passkey__su_user/umockdev.script.ipa index 1bc651a4465..3be6a85a87c 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user/umockdev.script.ipa +++ b/src/tests/system/data/test_passkey/test_passkey__su_user/umockdev.script.ipa @@ -19,4 +19,4 @@ r 288 r 290 p^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 p^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 p^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 174 p^@^@^AbidX@5FLEEo"=F^@^] l\^KP"~֦E^Gc^\;3p^@ۏ$^]^Aj~j^JL&dtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/op^AC}.[^E^@^@^@^\^CXG0E^B Eo]Wk%^H$-^W^BR^`x^P׀^B!^@ёqp^Bu9\^ZKR&>/͎[`t,^^eg^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 174 p^@^@^AbidX@5FLEEo"=F^@^] l\^KP"~֦E^Gc^\;3p^@ۏ$^]^Aj~j^JL&dtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/op^AC}.[^E^@^@^@^\^CXG0E^B Eo]Wk%^H$-^W^BR^`x^P׀^B!^@ёqp^Bu9\^ZKR&>/͎[`t,^^eg^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user/umockdev.script.ldap b/src/tests/system/data/test_passkey/test_passkey__su_user/umockdev.script.ldap index db026662951..96f6c76fc47 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user/umockdev.script.ldap +++ b/src/tests/system/data/test_passkey/test_passkey__su_user/umockdev.script.ldap @@ -18,4 +18,4 @@ r 288 ^S r 288 ^S=^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 ^S=^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 ^S=^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 28 ^S=^@^@^AbidX@^A^TMgm^L-^CS^MW4y_ j\W-VcXG^L^B^W^Oޚt^S=^@4R5vv7M^Fdtypejpublic-key^BX%ЁL}e/Z^UO^[+^K,^S=^A]l^U^J^H^E^@^@^@^G^CXF0D^B j8^M\:H^O@%qt(^\/Ǻ~$$!>;^B u^S=^B{9AbF6^Xs5^K*ywv^L^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 28 ^S=^@^@^AbidX@^A^TMgm^L-^CS^MW4y_ j\W-VcXG^L^B^W^Oޚt^S=^@4R5vv7M^Fdtypejpublic-key^BX%ЁL}e/Z^UO^[+^K,^S=^A]l^U^J^H^E^@^@^@^G^CXF0D^B j8^M\:H^O@%qt(^\/Ǻ~$$!>;^B u^S=^B{9AbF6^Xs5^K*ywv^L^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user/umockdev.script.samba b/src/tests/system/data/test_passkey/test_passkey__su_user/umockdev.script.samba index 4c0b6e23e92..45f9af8373e 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user/umockdev.script.samba +++ b/src/tests/system/data/test_passkey/test_passkey__su_user/umockdev.script.samba @@ -16,4 +16,4 @@ r 246 n r 289 n5Ż^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 287 n5Ż^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 289 n5Ż^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 75 n5Ő^@^@^AbidX@ŀn&X9T$HΚ'm^S^Ys^F$m7^Iksn5^@۬JL^Xqdtypejpublic-key^BX% ^S@^_n1n;Qm\<ִ>n5^A`^_W^E^@^@^@F^CXH0F^B!^@/2^_1,,0^ABd^FKZ@m^B!^@^S&n5^BO|FoзN$<ˣ!W\^V^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 75 n5Ő^@^@^AbidX@ŀn&X9T$HΚ'm^S^Ys^F$m7^Iksn5^@۬JL^Xqdtypejpublic-key^BX% ^S@^_n1n;Qm\<ִ>n5^A`^_W^E^@^@^@F^CXH0F^B!^@/2^_1,,0^ABd^FKZ@m^B!^@^S&n5^BO|FoзN$<ˣ!W\^V^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ad.user1 b/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ad.user1 index 22e9c14b10d..5681ab392b5 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ad.user1 +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ad.user1 @@ -18,4 +18,4 @@ r 289 Ny4 r 289 Ny4^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 Ny4^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 Ny4^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 76 Ny4^@^@^AbidX@տ^Kiς^N_^J*uGH?^Kan:>^\OҭD2M&=ZNy4^@BQ6Do^B^Zܔ^@dtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YDNy4^AA,K!^E^@^@^@^K^CXG0E^B!^@^[*G%Ir^B^JQR^Pm^S^V^B^B ^B^JNy4^Bɐ؂ҡ͵劻Ψ|"{<^B氋^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 76 Ny4^@^@^AbidX@տ^Kiς^N_^J*uGH?^Kan:>^\OҭD2M&=ZNy4^@BQ6Do^B^Zܔ^@dtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YDNy4^AA,K!^E^@^@^@^K^CXG0E^B!^@^[*G%Ir^B^JQR^Pm^S^V^B^B ^B^JNy4^Bɐ؂ҡ͵劻Ψ|"{<^B氋^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ad.user2 b/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ad.user2 index e72e148f0f0..8015eda584c 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ad.user2 +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ad.user2 @@ -15,4 +15,4 @@ r 221 r 287 {^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 {^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 {^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 78 {^@^@^AbidX@տ^Kiς^N_^J*uGH?^Kan:>^\OҭD2M&=Z{^@BQ6Do^B^Zܔ^@dtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YD{^AA,K!^E^@^@^@^O^CXG0E^B p^XAh(Cz^`ڨF^Y.[S)\W^R^X^B!^@/^Z{^BoX^I,2¶j+֍^`y^I􇴫^Q^P^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 78 {^@^@^AbidX@տ^Kiς^N_^J*uGH?^Kan:>^\OҭD2M&=Z{^@BQ6Do^B^Zܔ^@dtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YD{^AA,K!^E^@^@^@^O^CXG0E^B p^XAh(Cz^`ڨF^Y.[S)\W^R^X^B!^@/^Z{^BoX^I,2¶j+֍^`y^I􇴫^Q^P^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ad.user3 b/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ad.user3 index a50f4cae572..e192ae0e3a0 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ad.user3 +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ad.user3 @@ -17,4 +17,4 @@ r 288 9H^N r 288 9H^N^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 9H^N^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 9H^N^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 268 9H^N^@^@^AbidX@տ^Kiς^N_^J*uGH?^Kan:>^\OҭD2M&=Z9H^N^@BQ6Do^B^Zܔ^@dtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YD9H^N^AA,K!^E^@^@^@^R^CXF0D^B ^V^C^DNnD)P^C3c|^Fd?D6Y^Ds^B ^Ca^\^N9H^N^B4_$J^S^]Rq׆x@^Vy8L^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 268 9H^N^@^@^AbidX@տ^Kiς^N_^J*uGH?^Kan:>^\OҭD2M&=Z9H^N^@BQ6Do^B^Zܔ^@dtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YD9H^N^AA,K!^E^@^@^@^R^CXF0D^B ^V^C^DNnD)P^C3c|^Fd?D6Y^Ds^B ^Ca^\^N9H^N^B4_$J^S^]Rq׆x@^Vy8L^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ipa.user1 b/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ipa.user1 index 6d55fdb1fb6..74f3fe808a6 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ipa.user1 +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ipa.user1 @@ -19,4 +19,4 @@ r 287 r 288 $^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 $^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 $^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 290 $^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@$^@^@^AbidX@J;f=3^Nz^]8М0^H^D7l^[^\^M*^Qь^B$^@#i%^@jD^Ts7dtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/o$^AC}.[^E^@^@^@^Y^CXF0D^B ="U=$^[^F^N$^AA^HsF0ۢ^B W^AP$^Bw^XtJn5^Y%^K> Iw^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 290 $^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@$^@^@^AbidX@J;f=3^Nz^]8М0^H^D7l^[^\^M*^Qь^B$^@#i%^@jD^Ts7dtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/o$^AC}.[^E^@^@^@^Y^CXF0D^B ="U=$^[^F^N$^AA^HsF0ۢ^B W^AP$^Bw^XtJn5^Y%^K> Iw^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ipa.user2 b/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ipa.user2 index 0dd6f4109a4..4e9b254939a 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ipa.user2 +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ipa.user2 @@ -20,4 +20,4 @@ r 290 r 288 K^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 K^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 K^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 54 K^@^@^AbidX@J;f=3^Nz^]8М0^H^D7l^[^\^M*^Qь^BK^@#i%^@jD^Ts7dtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/oK^AC}.[^E^@^@^@^^^CXF0D^B Wp^_rJNJS^_^Y"&}w$^_^B #+ᏵK^B>^Co^_U^]m^X^ID^LяǺI9%^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 54 K^@^@^AbidX@J;f=3^Nz^]8М0^H^D7l^[^\^M*^Qь^BK^@#i%^@jD^Ts7dtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/oK^AC}.[^E^@^@^@^^^CXF0D^B Wp^_rJNJS^_^Y"&}w$^_^B #+ᏵK^B>^Co^_U^]m^X^ID^LяǺI9%^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ipa.user3 b/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ipa.user3 index c4761f29517..a0c902e7a53 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ipa.user3 +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ipa.user3 @@ -18,4 +18,4 @@ r 289 r 287 ^O^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 ^O^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 289 ^O^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 173 ^O^@^@^AbidX@J;f=3^Nz^]8М0^H^D7l^[^\^M*^Qь^B^O^@#i%^@jD^Ts7dtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/o^O^AC}.[^E^@^@^@#^CXF0D^B B׻Ge'?AkB׻Ge'?AkB׻Ge'?Ak^B!^@|++^ByJ{>Tֆ^FF^\^@;Xf^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 150 |++^@^@^AbidX@p5^N^I>B׻Ge'?Ak^B!^@|++^ByJ{>Tֆ^FF^\^@;Xf^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ldap.user3 b/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ldap.user3 index e4530f667b2..abfea4af5c1 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ldap.user3 +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.ldap.user3 @@ -17,4 +17,4 @@ r 291 S r 288 S^\^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 287 S^\^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 289 S^\^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 125 S^\^@^@^AbidX@p5^N^I>B׻Ge'?AkB׻Ge'?Ak'N^A`^_W^E^@^@^@^L^CXG0E^B ^FI^KYoTI%^]OrN|^RZx>&^B!^@:'N^BIҢ&^Otl^F_y.^A2^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 28 'NԐ^@^@^AbidX@X^\^V^T^Y^HӜ^EE^]^Ys4}^^^N瑩]^Z<^Q'N^@@^L56^Z^^Oܔdtypejpublic-key^BX% ^S@^_n1n;Qm\<ִ>'N^A`^_W^E^@^@^@^L^CXG0E^B ^FI^KYoTI%^]OrN|^RZx>&^B!^@:'N^BIҢ&^Otl^F_y.^A2^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.samba.user2 b/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.samba.user2 index f9e2a6a99b0..0854c9dbdf2 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.samba.user2 +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.samba.user2 @@ -16,4 +16,4 @@ r 288 5ގ r 288 5ގ^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 5ގ^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 289 5ގ^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 196 5ގ^@^@^AbidX@X^\^V^T^Y^HӜ^EE^]^Ys4}^^^N瑩]^Z<^Q5ގ^@@^L56^Z^^Oܔdtypejpublic-key^BX% ^S@^_n1n;Qm\<ִ>5ގ^A`^_W^E^@^@^@^P^CXH0F^B!^@<^A^Xëwta-^E{c}^\>^Tr]CgPU^B!^@;^@5ގ^B^Ke^F^L^S#dEv%0^KIB%mx^I^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 196 5ގ^@^@^AbidX@X^\^V^T^Y^HӜ^EE^]^Ys4}^^^N瑩]^Z<^Q5ގ^@@^L56^Z^^Oܔdtypejpublic-key^BX% ^S@^_n1n;Qm\<ִ>5ގ^A`^_W^E^@^@^@^P^CXH0F^B!^@<^A^Xëwta-^E{c}^\>^Tr]CgPU^B!^@;^@5ގ^B^Ke^F^L^S#dEv%0^KIB%mx^I^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.samba.user3 b/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.samba.user3 index 67b9ac30848..bcb8cdef082 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.samba.user3 +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_same_key_for_other_users/umockdev.script.samba.user3 @@ -15,4 +15,4 @@ r 245 l r 288 l^TD^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 l^TD^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 l^TD^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 244 l^TD^@^@^AbidX@X^\^V^T^Y^HӜ^EE^]^Ys4}^^^N瑩]^Z<^Ql^TD^@@^L56^Z^^Oܔdtypejpublic-key^BX% ^S@^_n1n;Qm\<ִ>l^TD^A`^_W^E^@^@^@^T^CXG0E^B J^Mc7>^_:^Gh>^F^LN)^\0^B!^@U\l^TD^BJC^J`Wj^Nw^Jmo^I^CL^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 244 l^TD^@^@^AbidX@X^\^V^T^Y^HӜ^EE^]^Ys4}^^^N瑩]^Z<^Ql^TD^@@^L56^Z^^Oܔdtypejpublic-key^BX% ^S@^_n1n;Qm\<ִ>l^TD^A`^_W^E^@^@^@^T^CXG0E^B J^Mc7>^_:^Gh>^F^LN)^\0^B!^@U\l^TD^BJC^J`Wj^Nw^Jmo^I^CL^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_when_add_with_ssh_key_and_mapping/umockdev.script.ad b/src/tests/system/data/test_passkey/test_passkey__su_user_when_add_with_ssh_key_and_mapping/umockdev.script.ad index 00063d72881..1c351bf72db 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_when_add_with_ssh_key_and_mapping/umockdev.script.ad +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_when_add_with_ssh_key_and_mapping/umockdev.script.ad @@ -24,4 +24,4 @@ r 289 C^G*ܻ^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 C^G*ܻ^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 C^G*ܻ^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 C^G*ܻ^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 198 C^G*ܐ^@^@^AbidX@-USEP~/#^KM^B^\^O^Uhri{Z'8a"T^P^VrC^G*^@^WoU^N.)^IH3^Ldtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YDC^G*^AA,K!^E^@^@^@^K^CXG0E^B JԄ^L^I^_^Vi^B4 o^E^C/T^G^C4^Rԡc^B!^@^CC^G*^Brס\'^T^Kr%\n^K$ڵ^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 198 C^G*ܐ^@^@^AbidX@-USEP~/#^KM^B^\^O^Uhri{Z'8a"T^P^VrC^G*^@^WoU^N.)^IH3^Ldtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YDC^G*^AA,K!^E^@^@^@^K^CXG0E^B JԄ^L^I^_^Vi^B4 o^E^C/T^G^C4^Rԡc^B!^@^CC^G*^Brס\'^T^Kr%\n^K$ڵ^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_when_add_with_ssh_key_and_mapping/umockdev.script.ldap b/src/tests/system/data/test_passkey/test_passkey__su_user_when_add_with_ssh_key_and_mapping/umockdev.script.ldap index 20d3295f175..3443d736475 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_when_add_with_ssh_key_and_mapping/umockdev.script.ldap +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_when_add_with_ssh_key_and_mapping/umockdev.script.ldap @@ -17,4 +17,4 @@ r 287 r 291 G1^K^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 289 G1^K^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 291 G1^K^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 78 G1^K^@^@^AbidX@Aw1y*5c^Fkr^[^Vtu~^MR0)ե ^P^XЊ9xʗvG1^K^@^U=Dlq'5Rdtypejpublic-key^BX%ЁL}e/Z^UO^[+^K,G1^K^A]l^U^J^H^E^@^@^@^M^CXG0E^B!^@j^[iڡ^X^BN7X^E:^`]+/-^B ^_*rG1^K^Bj^N1E^D^@^[0x|൙߆T^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 78 G1^K^@^@^AbidX@Aw1y*5c^Fkr^[^Vtu~^MR0)ե ^P^XЊ9xʗvG1^K^@^U=Dlq'5Rdtypejpublic-key^BX%ЁL}e/Z^UO^[+^K,G1^K^A]l^U^J^H^E^@^@^@^M^CXG0E^B!^@j^[iڡ^X^BN7X^E:^`]+/-^B ^_*rG1^K^Bj^N1E^D^@^[0x|൙߆T^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_when_add_with_ssh_key_and_mapping/umockdev.script.samba b/src/tests/system/data/test_passkey/test_passkey__su_user_when_add_with_ssh_key_and_mapping/umockdev.script.samba index 3b3c7ee63a3..0a7f3bf7e37 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_when_add_with_ssh_key_and_mapping/umockdev.script.samba +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_when_add_with_ssh_key_and_mapping/umockdev.script.samba @@ -22,4 +22,4 @@ r 290 Z^F r 290 Z^F^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 291 Z^F^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 289 Z^F^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 174 Z^F^@^@^AbidX@W^O^Bl;^R8e^Y_^@(RA-`,(bSe^F͊@^O2JM;Z^F^@p^M^^o6] 9%^\^YOdtypejpublic-key^BX% ^S@^_n1n;Qm\<ִ>Z^F^A`^_W^E^@^@^@^F^CXG0E^B 8^S=e5p^V^^)>`5*^B!^@f?Z^F^B4RLi^N^[˚^A^H]"ϩ:^^/s<^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 174 Z^F^@^@^AbidX@W^O^Bl;^R8e^Y_^@(RA-`,(bSe^F͊@^O2JM;Z^F^@p^M^^o6] 9%^\^YOdtypejpublic-key^BX% ^S@^_n1n;Qm\<ִ>Z^F^A`^_W^E^@^@^@^F^CXG0E^B 8^S=e5p^V^^)>`5*^B!^@f?Z^F^B4RLi^N^[˚^A^H]"ϩ:^^/s<^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_when_offline/umockdev.script.ad b/src/tests/system/data/test_passkey/test_passkey__su_user_when_offline/umockdev.script.ad index e9864e7f2da..b76bae3ed35 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_when_offline/umockdev.script.ad +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_when_offline/umockdev.script.ad @@ -16,4 +16,4 @@ r 288 r 288 ^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 ^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 ^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 52 ^@^@^AbidX@Z;*?^O[u!^Z0=bUG^TqFp^O]^HחI7%^@Lb.Fm4m^Mcdtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YD^AA,K!^E^@^@^@^X^CXF0D^B ^N^A^Zb;u.^S^_v}v{^K'Y{^B 4^H^BɩXû^DAe3A'bU_^M`[Ay^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 52 ^@^@^AbidX@Z;*?^O[u!^Z0=bUG^TqFp^O]^HחI7%^@Lb.Fm4m^Mcdtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YD^AA,K!^E^@^@^@^X^CXF0D^B ^N^A^Zb;u.^S^_v}v{^K'Y{^B 4^H^BɩXû^DAe3A'bU_^M`[Ay^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_when_offline/umockdev.script.ipa b/src/tests/system/data/test_passkey/test_passkey__su_user_when_offline/umockdev.script.ipa index 1bc651a4465..3be6a85a87c 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_when_offline/umockdev.script.ipa +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_when_offline/umockdev.script.ipa @@ -19,4 +19,4 @@ r 288 r 290 p^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 p^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 p^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 174 p^@^@^AbidX@5FLEEo"=F^@^] l\^KP"~֦E^Gc^\;3p^@ۏ$^]^Aj~j^JL&dtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/op^AC}.[^E^@^@^@^\^CXG0E^B Eo]Wk%^H$-^W^BR^`x^P׀^B!^@ёqp^Bu9\^ZKR&>/͎[`t,^^eg^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 174 p^@^@^AbidX@5FLEEo"=F^@^] l\^KP"~֦E^Gc^\;3p^@ۏ$^]^Aj~j^JL&dtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/op^AC}.[^E^@^@^@^\^CXG0E^B Eo]Wk%^H$-^W^BR^`x^P׀^B!^@ёqp^Bu9\^ZKR&>/͎[`t,^^eg^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_when_offline/umockdev.script.ldap b/src/tests/system/data/test_passkey/test_passkey__su_user_when_offline/umockdev.script.ldap index db026662951..96f6c76fc47 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_when_offline/umockdev.script.ldap +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_when_offline/umockdev.script.ldap @@ -18,4 +18,4 @@ r 288 ^S r 288 ^S=^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 ^S=^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 ^S=^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 28 ^S=^@^@^AbidX@^A^TMgm^L-^CS^MW4y_ j\W-VcXG^L^B^W^Oޚt^S=^@4R5vv7M^Fdtypejpublic-key^BX%ЁL}e/Z^UO^[+^K,^S=^A]l^U^J^H^E^@^@^@^G^CXF0D^B j8^M\:H^O@%qt(^\/Ǻ~$$!>;^B u^S=^B{9AbF6^Xs5^K*ywv^L^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 28 ^S=^@^@^AbidX@^A^TMgm^L-^CS^MW4y_ j\W-VcXG^L^B^W^Oޚt^S=^@4R5vv7M^Fdtypejpublic-key^BX%ЁL}e/Z^UO^[+^K,^S=^A]l^U^J^H^E^@^@^@^G^CXF0D^B j8^M\:H^O@%qt(^\/Ǻ~$$!>;^B u^S=^B{9AbF6^Xs5^K*ywv^L^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_when_offline/umockdev.script.samba b/src/tests/system/data/test_passkey/test_passkey__su_user_when_offline/umockdev.script.samba index 4c0b6e23e92..45f9af8373e 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_when_offline/umockdev.script.samba +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_when_offline/umockdev.script.samba @@ -16,4 +16,4 @@ r 246 n r 289 n5Ż^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 287 n5Ż^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 289 n5Ż^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 75 n5Ő^@^@^AbidX@ŀn&X9T$HΚ'm^S^Ys^F$m7^Iksn5^@۬JL^Xqdtypejpublic-key^BX% ^S@^_n1n;Qm\<ִ>n5^A`^_W^E^@^@^@F^CXH0F^B!^@/2^_1,,0^ABd^FKZ@m^B!^@^S&n5^BO|FoзN$<ˣ!W\^V^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 75 n5Ő^@^@^AbidX@ŀn&X9T$HΚ'm^S^Ys^F$m7^Iksn5^@۬JL^Xqdtypejpublic-key^BX% ^S@^_n1n;Qm\<ִ>n5^A`^_W^E^@^@^@F^CXH0F^B!^@/2^_1,,0^ABd^FKZ@m^B!^@^S&n5^BO|FoзN$<ˣ!W\^V^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/passkey-mapping.ipa b/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/passkey-mapping.ipa index e529e2ee905..915689bd654 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/passkey-mapping.ipa +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/passkey-mapping.ipa @@ -1 +1 @@ -passkey:NUZMRUXIb/W8Ij1GqwCDHSCWxt/SxWxckwtQjLYi/X6Y1qZFB+HI8WO6khzAjzsz248kHbaeAf9qfmqfCky1Jg==,MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIasAa8ogjPCKXeA4KY3t0W3xBRmG+E4D+MNoRIAJrYuNLSYtAcOL7DCbIfgc+7c5Y4Mh/FzoEyeumKGYMoyTfg== \ No newline at end of file +passkey:NUZMRUXIb/W8Ij1GqwCDHSCWxt/SxWxckwtQjLYi/X6Y1qZFB+HI8WO6khzAjzsz248kHbaeAf9qfmqfCky1Jg==,MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIasAa8ogjPCKXeA4KY3t0W3xBRmG+E4D+MNoRIAJrYuNLSYtAcOL7DCbIfgc+7c5Y4Mh/FzoEyeumKGYMoyTfg== diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/passkey-mapping.ldap b/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/passkey-mapping.ldap index 27dbfd9af40..82d76d9b590 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/passkey-mapping.ldap +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/passkey-mapping.ldap @@ -1 +1 @@ -passkey:mQEUTWdtDJPELQNTDdxXNHlfIO1qXFf0LVZjWEfyDALFzvLZ4e4XD5bemqq+o3ThrzT6k1I1n3Z2N00GvLSmjQ==,MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqk7K5VAI7Evr4ar8X82L/sxm/Bnm5Ti31xnLfGO0BipwHucw8+/wT4+6T9j5gdMwZKUcXR4BILpmULEyrcZUfw== \ No newline at end of file +passkey:mQEUTWdtDJPELQNTDdxXNHlfIO1qXFf0LVZjWEfyDALFzvLZ4e4XD5bemqq+o3ThrzT6k1I1n3Z2N00GvLSmjQ==,MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqk7K5VAI7Evr4ar8X82L/sxm/Bnm5Ti31xnLfGO0BipwHucw8+/wT4+6T9j5gdMwZKUcXR4BILpmULEyrcZUfw== diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/umockdev.script.ad b/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/umockdev.script.ad index 35230af49bc..a8ff6b5e962 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/umockdev.script.ad +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/umockdev.script.ad @@ -16,4 +16,4 @@ r 288 r 290 ^Y^Zo^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 ^Y^Zo^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 ^Y^Zo^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 288 ^Y^Zo^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^Y^Zo^@^@^AbidX@0؉^L^W̥M ^C)^N OC<^[~&^UFE)s^Y^Zo^@Y^H^J^N'^]^T/a'Zdtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YD^Y^Zo^AA,K!^E^@^@^@^M^CXF0D^B CpQl^S];[(Ei#{"BY^Uܥ^B^B ;']>^Y^Zo^B^A^F^]?u;࿙^U߄9^O^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 288 ^Y^Zo^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^Y^Zo^@^@^AbidX@0؉^L^W̥M ^C)^N OC<^[~&^UFE)s^Y^Zo^@Y^H^J^N'^]^T/a'Zdtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YD^Y^Zo^AA,K!^E^@^@^@^M^CXF0D^B CpQl^S];[(Ei#{"BY^Uܥ^B^B ;']>^Y^Zo^B^A^F^]?u;࿙^U߄9^O^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/umockdev.script.ipa b/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/umockdev.script.ipa index 1bc651a4465..3be6a85a87c 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/umockdev.script.ipa +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/umockdev.script.ipa @@ -19,4 +19,4 @@ r 288 r 290 p^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 p^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 p^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 174 p^@^@^AbidX@5FLEEo"=F^@^] l\^KP"~֦E^Gc^\;3p^@ۏ$^]^Aj~j^JL&dtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/op^AC}.[^E^@^@^@^\^CXG0E^B Eo]Wk%^H$-^W^BR^`x^P׀^B!^@ёqp^Bu9\^ZKR&>/͎[`t,^^eg^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 174 p^@^@^AbidX@5FLEEo"=F^@^] l\^KP"~֦E^Gc^\;3p^@ۏ$^]^Aj~j^JL&dtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/op^AC}.[^E^@^@^@^\^CXG0E^B Eo]Wk%^H$-^W^BR^`x^P׀^B!^@ёqp^Bu9\^ZKR&>/͎[`t,^^eg^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/umockdev.script.ldap b/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/umockdev.script.ldap index db026662951..96f6c76fc47 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/umockdev.script.ldap +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/umockdev.script.ldap @@ -18,4 +18,4 @@ r 288 ^S r 288 ^S=^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 ^S=^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 ^S=^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 28 ^S=^@^@^AbidX@^A^TMgm^L-^CS^MW4y_ j\W-VcXG^L^B^W^Oޚt^S=^@4R5vv7M^Fdtypejpublic-key^BX%ЁL}e/Z^UO^[+^K,^S=^A]l^U^J^H^E^@^@^@^G^CXF0D^B j8^M\:H^O@%qt(^\/Ǻ~$$!>;^B u^S=^B{9AbF6^Xs5^K*ywv^L^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 28 ^S=^@^@^AbidX@^A^TMgm^L-^CS^MW4y_ j\W-VcXG^L^B^W^Oޚt^S=^@4R5vv7M^Fdtypejpublic-key^BX%ЁL}e/Z^UO^[+^K,^S=^A]l^U^J^H^E^@^@^@^G^CXF0D^B j8^M\:H^O@%qt(^\/Ǻ~$$!>;^B u^S=^B{9AbF6^Xs5^K*ywv^L^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/umockdev.script.samba b/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/umockdev.script.samba index c0e02fac1ba..65b4b610dc9 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/umockdev.script.samba +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_when_server_is_not_resolvable/umockdev.script.samba @@ -21,4 +21,4 @@ r 291 j r 287 j^J˻^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 289 j^J˻^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 287 j^J˻^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 246 j^Jː^@^@^AbidX@-^H&R ⦦{нy7VX!k^Rnx^G^ARų!wCtH^Ln^Rj^J^@^R(.H2Il&0dtypejpublic-key^BX% ^S@^_n1n;Qm\<ִ>j^J^A`^_W^E^@^@^@^L^CXF0D^B `Wo%^Q&-?g{^B_<@^V3@/l^B I^I&j^J^BH,\DW^^pEKb^Y%^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 246 j^Jː^@^@^AbidX@-^H&R ⦦{нy7VX!k^Rnx^G^ARų!wCtH^Ln^Rj^J^@^R(.H2Il&0dtypejpublic-key^BX% ^S@^_n1n;Qm\<ִ>j^J^A`^_W^E^@^@^@^L^CXF0D^B `Wo%^Q&-?g{^B_<@^V3@/l^B I^I&j^J^BH,\DW^^pEKb^Y%^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_with_failed_pin/umockdev.script.ad b/src/tests/system/data/test_passkey/test_passkey__su_user_with_failed_pin/umockdev.script.ad index 4283810b71d..7fefe1a2433 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_with_failed_pin/umockdev.script.ad +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_with_failed_pin/umockdev.script.ad @@ -9,4 +9,4 @@ r 82 ^H; w 7 ^@^H;^@^F^F^A^B^B^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 3 ^H;^@Q^@^A^A^B^C8^X ^A!X tV~EWpC~$z>Gqu|^W^Iw"X A^H;^@n^L}~2P浐^G|^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ w 7 ^@^H;^@x^F^A^B^B^E^C^A^B^C8^X ^A!X 3B1ˠ^S^\^Ev,^P)=0M^J^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ w 5 ^@^@x^F^A^B^B^E^C^A^B^C8^X ^A!X 3B1ˠ^S^\^Ev,^P^Rr^@^\k^A^Y^V7^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 315 ^\k^@^A1^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 315 ^\k^@^A1^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_with_incorrect_mapping/umockdev.script.ad b/src/tests/system/data/test_passkey/test_passkey__su_user_with_incorrect_mapping/umockdev.script.ad index 3f04d19aec5..5297e94bbfd 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_with_incorrect_mapping/umockdev.script.ad +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_with_incorrect_mapping/umockdev.script.ad @@ -5,4 +5,4 @@ r 3 w 3 ^@^[.^@^A^D^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 2 ^[.^@^@^AfU2F_V2hFIDO_2_0lFIDO_2_1_PRE^BkcredProtectkhmac-secr^[.^@et^CP/W^SG^VZ *^DbrkbupdplaticlientPinucredentialM^[.^AgmtPreview^E^Y^D^F^B^A^G^H^H^X^Icnfccusb^Jcalg&dtypejpublic-key^[.^Bcalg'dtypejpublic-key^M^D^N^Z^@^E^D^C^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ w 4 ^@^[.^@^B^Agad.test^BX ^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^CbidX@ۜ^@^[.^@^CՒN^]}PDv<=^I^Z]^W$Z^W^A^B^_ՍؓӰUvh#HV^R^@^[.^A^@dtypejpublic-key^Ebup^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 7 ^[.^@^A.^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 7 ^[.^@^A.^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_with_incorrect_mapping/umockdev.script.ipa b/src/tests/system/data/test_passkey/test_passkey__su_user_with_incorrect_mapping/umockdev.script.ipa index 8f1039f7cc1..1fc0dff1536 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_with_incorrect_mapping/umockdev.script.ipa +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_with_incorrect_mapping/umockdev.script.ipa @@ -5,4 +5,4 @@ r 2 w 2 ^@^LҐ^@^A^D^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 2 ^LҐ^@^@^AfU2F_V2hFIDO_2_0lFIDO_2_1_PRE^BkcredProtectkhmac-secr^L^@et^CP/W^SG^VZ *^DbrkbupdplaticlientPinucredentialM^L^AgmtPreview^E^Y^D^F^B^A^G^H^H^X^Icnfccusb^Jcalg&dtypejpublic-key^L^Bcalg'dtypejpublic-key^M^D^N^Z^@^E^D^C^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ w 1 ^@^LҐ^@^B^Ahipa.test^BX ^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^CbidX@^@^L^@^CՒN^]}PDv<=^I^Z]^W$Z^W^A^B^_ՍؓӰUvh#HV^@^L^A^R^@dtypejpublic-key^Ebup^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 6 ^LҐ^@^A.^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 6 ^LҐ^@^A.^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_with_incorrect_mapping/umockdev.script.ldap b/src/tests/system/data/test_passkey/test_passkey__su_user_with_incorrect_mapping/umockdev.script.ldap index 4d9aa650c51..0074e6da8ee 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_with_incorrect_mapping/umockdev.script.ldap +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_with_incorrect_mapping/umockdev.script.ldap @@ -5,4 +5,4 @@ r 3 w 1 ^@^@^A^D^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 2 ^@^@^AfU2F_V2hFIDO_2_0lFIDO_2_1_PRE^BkcredProtectkhmac-secr^@et^CP/W^SG^VZ *^DbrkbupdplaticlientPinucredentialM^AgmtPreview^E^Y^D^F^B^A^G^H^H^X^Icnfccusb^Jcalg&dtypejpublic-key^Bcalg'dtypejpublic-key^M^D^N^Z^@^E^D^C^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ w 2 ^@^@^B^Adtest^BX ^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^CbidX@hH^^Y^@^@:^Y5`,Mo~^ILGǧ~{WwX^X\I~]u#핷Md^@^Atypejpublic-key^Ebup^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 7 ^@^A.^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 7 ^@^A.^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_with_incorrect_mapping/umockdev.script.samba b/src/tests/system/data/test_passkey/test_passkey__su_user_with_incorrect_mapping/umockdev.script.samba index dfff5d24fb5..385fed921dc 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_with_incorrect_mapping/umockdev.script.samba +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_with_incorrect_mapping/umockdev.script.samba @@ -5,4 +5,4 @@ r 3 w 1 ^@+W^@^A^D^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 3 +W^@^@^AfU2F_V2hFIDO_2_0lFIDO_2_1_PRE^BkcredProtectkhmac-secr+W^@et^CP/W^SG^VZ *^DbrkbupdplaticlientPinucredentialM+W^AgmtPreview^E^Y^D^F^B^A^G^H^H^X^Icnfccusb^Jcalg&dtypejpublic-key+W^Bcalg'dtypejpublic-key^M^D^N^Z^@^E^D^C^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ w 4 ^@+W^@^B^Ajsamba.test^BX ^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^CbidX@^@+W^@hH^^Y:^Y5`,Mo~^ILGǧ~{WwX^X\I~]u#^@+W^A핷Mdtypejpublic-key^Ebup^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 7 +W^@^A.^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 7 +W^@^A.^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_with_multiple_keys/umockdev.script.ad b/src/tests/system/data/test_passkey/test_passkey__su_user_with_multiple_keys/umockdev.script.ad index b07444bf4b1..642864c4d10 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_with_multiple_keys/umockdev.script.ad +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_with_multiple_keys/umockdev.script.ad @@ -18,4 +18,4 @@ r 288 r 288 Z^A^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 Z^A^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 289 Z^A^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 78 Z^A^@^@^AbidX@O^^,˵^TCv7!.!^Hb]Fm^I-o֢fjZ^A^@^Mo^HOLx),dtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YDZ^A^AA,K!^E^@^@^@^N^CXH0F^B!^@^Vt]u{p$Ծ^J,?5^GMeab5^B!^@Z^A^B^[<^GGc~JSP^L7^M^P^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 78 Z^A^@^@^AbidX@O^^,˵^TCv7!.!^Hb]Fm^I-o֢fjZ^A^@^Mo^HOLx),dtypejpublic-key^BX%fCȣ܀ɽ5~^G^A-YDZ^A^AA,K!^E^@^@^@^N^CXH0F^B!^@^Vt]u{p$Ծ^J,?5^GMeab5^B!^@Z^A^B^[<^GGc~JSP^L7^M^P^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_with_multiple_keys/umockdev.script.ipa b/src/tests/system/data/test_passkey/test_passkey__su_user_with_multiple_keys/umockdev.script.ipa index 9ac4f1a1873..9054390298a 100755 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_with_multiple_keys/umockdev.script.ipa +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_with_multiple_keys/umockdev.script.ipa @@ -18,4 +18,4 @@ r 288 r 288 'L^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 'L^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 'L^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 148 'L^@^@^AbidX@^EIl58EXZ3^Q^M^B^[M܁#6ve^I;Y^KlkV'L^@ǧY`1T^NNJ^Qdtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/o'L^AC}.[^E^@^@^@^_^CXG0E^B r_a"1[?kLF^I*h^B!^@^U'L^BjZ-^]^`M73It8;r6$W^L^H^L^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 148 'L^@^@^AbidX@^EIl58EXZ3^Q^M^B^[M܁#6ve^I;Y^KlkV'L^@ǧY`1T^NNJ^Qdtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/o'L^AC}.[^E^@^@^@^_^CXG0E^B r_a"1[?kLF^I*h^B!^@^U'L^BjZ-^]^`M73It8;r6$W^L^H^L^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_with_multiple_keys/umockdev.script.ldap b/src/tests/system/data/test_passkey/test_passkey__su_user_with_multiple_keys/umockdev.script.ldap index 2ecea3a4298..c627356c396 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_with_multiple_keys/umockdev.script.ldap +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_with_multiple_keys/umockdev.script.ldap @@ -20,4 +20,4 @@ r 289 ^FX r 289 ^FX^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 289 ^FX^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 287 ^FX^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 247 ^FX^@^@^AbidX@#TVVC3z'#]7 4P^^U# f.Y0!ߙ^^C5//y^FX^@!]^^^R^X(֛dtypejpublic-key^BX%ЁL}e/Z^UO^[+^K,^FX^A]l^U^J^H^E^@^@^@^^^CXG0E^B ^Dq)qFlb@Z^_%RY^B^N<^B!^@m.^FX^BؙM^`I:^`^F1^Ht^K\9'ȁ^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 247 ^FX^@^@^AbidX@#TVVC3z'#]7 4P^^U# f.Y0!ߙ^^C5//y^FX^@!]^^^R^X(֛dtypejpublic-key^BX%ЁL}e/Z^UO^[+^K,^FX^A]l^U^J^H^E^@^@^@^^^CXG0E^B ^Dq)qFlb@Z^_%RY^B^N<^B!^@m.^FX^BؙM^`I:^`^F1^Ht^K\9'ȁ^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_user_with_multiple_keys/umockdev.script.samba b/src/tests/system/data/test_passkey/test_passkey__su_user_with_multiple_keys/umockdev.script.samba index 9ecebaa57d5..2047f695c00 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_user_with_multiple_keys/umockdev.script.samba +++ b/src/tests/system/data/test_passkey/test_passkey__su_user_with_multiple_keys/umockdev.script.samba @@ -17,4 +17,4 @@ r 287 r 289 C^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 C^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 289 C^@^A^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 29 C^@^@^AbidX@^Qso|4W^Gcp^PF^KA%ȃ#f^VeXx8]'X=uG8㶼C^@C^A`^_W^E^@^@^@^V^CXH0F^B!^@#Oq^BN6nօ*\-NNT^B!^@^@C^BV^Bg^EV^^v|&}^P^Jd~^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 29 C^@^@^AbidX@^Qso|4W^Gcp^PF^KA%ȃ#f^VeXx8]'X=uG8㶼C^@C^A`^_W^E^@^@^@^V^CXH0F^B!^@#Oq^BN6nօ*\-NNT^B!^@^@C^BV^Bg^EV^^v|&}^P^Jd~^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ diff --git a/src/tests/system/data/test_passkey/test_passkey__su_with_12_mappings/umockdev.script.ipa b/src/tests/system/data/test_passkey/test_passkey__su_with_12_mappings/umockdev.script.ipa index ecabd8f9ec5..909b8d90cc7 100644 --- a/src/tests/system/data/test_passkey/test_passkey__su_with_12_mappings/umockdev.script.ipa +++ b/src/tests/system/data/test_passkey/test_passkey__su_with_12_mappings/umockdev.script.ipa @@ -29,4 +29,4 @@ r 288 r 290 ^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 288 ^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ r 290 ^@^A^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ -r 148 ^@^@^AbidX@S^K^T$ca2풫^S^`"Dv&6^E~Q%'1^P^P^Bϒ^M^@IMڸ^[^S{HAti^`dtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/o^AC}.[^E^@^@^@D^CXH0F^B!^@^DI^^#HUFҺ^KQeؕKfpW^]0Σ^B!^@^P^Bqs_fj&^]rĎS^Fc^\X{e^_^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ \ No newline at end of file +r 148 ^@^@^AbidX@S^K^T$ca2풫^S^`"Dv&6^E~Q%'1^P^P^Bϒ^M^@IMڸ^[^S{HAti^`dtypejpublic-key^BX%^@|Dږ^Qx??d^Q/N4/o^AC}.[^E^@^@^@D^CXH0F^B!^@^DI^^#HUFҺ^KQeؕKfpW^]0Σ^B!^@^P^Bqs_fj&^]rĎS^Fc^\X{e^_^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ From 4b80eff0ea0f35ead7709162653a00c14d55bbaa Mon Sep 17 00:00:00 2001 From: Philippe Smadja Date: Thu, 19 Jun 2025 16:09:45 +0200 Subject: [PATCH 5/8] fix passkey test suite --- src/passkey_child/passkey_child_assert.c | 2 +- src/passkey_child/passkey_child_common.c | 18 +++++++++++------- src/tests/cmocka/test_passkey_child.c | 2 ++ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/passkey_child/passkey_child_assert.c b/src/passkey_child/passkey_child_assert.c index 92ef894f380..0b0475b684a 100644 --- a/src/passkey_child/passkey_child_assert.c +++ b/src/passkey_child/passkey_child_assert.c @@ -368,7 +368,7 @@ request_assert(struct passkey_data *data, fido_dev_t *dev, char *pin = NULL; bool has_pin; bool has_uv; - errno_t ret; + errno_t ret = FIDO_OK; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { diff --git a/src/passkey_child/passkey_child_common.c b/src/passkey_child/passkey_child_common.c index ef54ad935ba..6de4ea877dc 100644 --- a/src/passkey_child/passkey_child_common.c +++ b/src/passkey_child/passkey_child_common.c @@ -652,22 +652,26 @@ select_authenticator(struct passkey_data *data, fido_dev_t **_dev, if (has_pin && has_uv) { fd = creat("/var/run/passkey-pinuv", 0000); if (fd < 0) - DEBUG(SSSDBG_TRACE_FUNC, "error creat pinuv errno = %d\n", errno); - close(fd); + DEBUG(SSSDBG_TRACE_FUNC, + "error creat pinuv indicator errno = %d\n", errno); + else close(fd); } else { (void)remove ("/var/run/passkey-pinuv"); if (has_pin) { fd = creat("/var/run/passkey-pinonly", 0000); if (fd < 0) - DEBUG(SSSDBG_TRACE_FUNC, "eror creat pinonly errno = %d\n", errno); - close (fd); + DEBUG(SSSDBG_TRACE_FUNC, + "error creat pinonly indicator errno = %d\n", errno); + else close (fd); } else { /* no pin and no uv; */ (void)remove ("/var/run/passkey-pinonly"); - fido_dev_close(dev); - dev = NULL; - ret = FIDO_ERR_NOTFOUND; + fd = creat("/var/run/passkey-nopin-nouv", 0000); + if (fd < 0) + DEBUG(SSSDBG_TRACE_FUNC, + "error creat nopin-nouv indicator errno = %d\n", errno); + else close (fd); } } break; diff --git a/src/tests/cmocka/test_passkey_child.c b/src/tests/cmocka/test_passkey_child.c index 5003152c453..9ce6e37edd9 100644 --- a/src/tests/cmocka/test_passkey_child.c +++ b/src/tests/cmocka/test_passkey_child.c @@ -939,6 +939,8 @@ void test_select_authenticator(void **state) will_return(__wrap_fido_dev_info_path, TEST_PATH); will_return(__wrap_fido_dev_open, FIDO_OK); will_return(__wrap_fido_dev_is_fido2, true); + will_return(__wrap_fido_dev_has_uv, true); + will_return(__wrap_fido_dev_has_pin, true); will_return(__wrap_fido_dev_get_assert, FIDO_OK); ret = select_authenticator(&data, &dev, &assert, &index); From 0ee0642a3ff43bf9b4581a87f4a629ea6ce95c0e Mon Sep 17 00:00:00 2001 From: Philippe Smadja Date: Fri, 20 Jun 2025 15:51:39 +0200 Subject: [PATCH 6/8] fix2 passkey tests --- src/passkey_child/passkey_child_assert.c | 5 +- src/passkey_child/passkey_child_common.c | 78 ++++++++++++++---------- src/tests/cmocka/test_passkey_child.c | 2 - 3 files changed, 49 insertions(+), 36 deletions(-) diff --git a/src/passkey_child/passkey_child_assert.c b/src/passkey_child/passkey_child_assert.c index 0b0475b684a..f23f47a36ad 100644 --- a/src/passkey_child/passkey_child_assert.c +++ b/src/passkey_child/passkey_child_assert.c @@ -339,6 +339,7 @@ reset_public_key(struct pk_data_t *_pk_data) return EOK; } + #define DOPIN "/var/run/passkey-dopin" static @@ -433,10 +434,10 @@ request_assert(struct passkey_data *data, fido_dev_t *dev, if (ret == FIDO_OK && has_uv == true) { /* PIN or UV has been OK */ - disable_dopin(); + (void)disable_dopin(); } else if (ret == FIDO_ERR_PIN_REQUIRED || ret == FIDO_ERR_UV_INVALID || ret == FIDO_ERR_PIN_INVALID || ret == FIDO_ERR_UV_BLOCKED) { - enable_dopin(); + (void)enable_dopin(); } return ret; } diff --git a/src/passkey_child/passkey_child_common.c b/src/passkey_child/passkey_child_common.c index 6de4ea877dc..f8a9868f152 100644 --- a/src/passkey_child/passkey_child_common.c +++ b/src/passkey_child/passkey_child_common.c @@ -596,6 +596,42 @@ public_key_to_base64(TALLOC_CTX *mem_ctx, const struct passkey_data *data, return ret; } +static +errno_t save_device_info(fido_dev_t *dev) +{ + /* update device current configuration + */ + bool has_pin = fido_dev_has_pin(dev); + bool has_uv = fido_dev_has_uv (dev); + int fd = -1; + if (has_pin && has_uv) { + fd = creat("/var/run/passkey-pinuv", 0000); + if (fd < 0) + DEBUG(SSSDBG_TRACE_FUNC, + "error creat pinuv indicator errno = %d\n", errno); + else close(fd); + } + else { + (void)remove ("/var/run/passkey-pinuv"); + if (has_pin) { + fd = creat("/var/run/passkey-pinonly", 0000); + if (fd < 0) + DEBUG(SSSDBG_TRACE_FUNC, + "error creat pinonly indicator errno = %d\n", errno); + else close (fd); + } else { + /* no pin and no uv; */ + (void)remove ("/var/run/passkey-pinonly"); + fd = creat("/var/run/passkey-nopin-nouv", 0000); + if (fd < 0) + DEBUG(SSSDBG_TRACE_FUNC, + "error creat nopin-nouv indicator errno = %d\n", errno); + else close (fd); + } + } + return EOK; +} + errno_t select_authenticator(struct passkey_data *data, fido_dev_t **_dev, fido_assert_t **_assert, int *_index) @@ -644,36 +680,6 @@ select_authenticator(struct passkey_data *data, fido_dev_t **_dev, ret = select_device(data->action, dev_list, dev_list_len, assert, &dev); if (ret == EOK) { /* Key handle found in device */ - /* update device current configuration - */ - bool has_pin = fido_dev_has_pin(dev); - bool has_uv = fido_dev_has_uv (dev); - int fd = -1; - if (has_pin && has_uv) { - fd = creat("/var/run/passkey-pinuv", 0000); - if (fd < 0) - DEBUG(SSSDBG_TRACE_FUNC, - "error creat pinuv indicator errno = %d\n", errno); - else close(fd); - } - else { - (void)remove ("/var/run/passkey-pinuv"); - if (has_pin) { - fd = creat("/var/run/passkey-pinonly", 0000); - if (fd < 0) - DEBUG(SSSDBG_TRACE_FUNC, - "error creat pinonly indicator errno = %d\n", errno); - else close (fd); - } else { - /* no pin and no uv; */ - (void)remove ("/var/run/passkey-pinonly"); - fd = creat("/var/run/passkey-nopin-nouv", 0000); - if (fd < 0) - DEBUG(SSSDBG_TRACE_FUNC, - "error creat nopin-nouv indicator errno = %d\n", errno); - else close (fd); - } - } break; } @@ -788,6 +794,10 @@ authenticate(struct passkey_data *data) goto done; } + /* device OK */ + (void) save_device_info(dev); + + DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert options.\n"); ret = set_assert_options(FIDO_OPT_TRUE, data->user_verification, assert); if (ret != FIDO_OK) { @@ -819,7 +829,6 @@ authenticate(struct passkey_data *data) if (ret != FIDO_OK) { goto done; } - ret = FIDO_OK; done: @@ -862,6 +871,9 @@ get_assert_data(struct passkey_data *data) goto done; } + /* device OK */ + (void) save_device_info(dev); + DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert options.\n"); ret = set_assert_options(FIDO_OPT_TRUE, data->user_verification, assert); if (ret != FIDO_OK) { @@ -874,7 +886,6 @@ get_assert_data(struct passkey_data *data) if (ret != FIDO_OK) { goto done; } - DEBUG(SSSDBG_TRACE_FUNC, "Getting authentication data and signature.\n"); ret = get_assert_auth_data_signature(tmp_ctx, assert, &auth_data, &signature); @@ -918,6 +929,9 @@ get_device_info(struct passkey_data *data) /* unused */ fido_assert_free(&assert); + /* device OK */ + (void) save_device_info(dev); + done: if (dev != NULL) { fido_dev_close(dev); diff --git a/src/tests/cmocka/test_passkey_child.c b/src/tests/cmocka/test_passkey_child.c index 9ce6e37edd9..5003152c453 100644 --- a/src/tests/cmocka/test_passkey_child.c +++ b/src/tests/cmocka/test_passkey_child.c @@ -939,8 +939,6 @@ void test_select_authenticator(void **state) will_return(__wrap_fido_dev_info_path, TEST_PATH); will_return(__wrap_fido_dev_open, FIDO_OK); will_return(__wrap_fido_dev_is_fido2, true); - will_return(__wrap_fido_dev_has_uv, true); - will_return(__wrap_fido_dev_has_pin, true); will_return(__wrap_fido_dev_get_assert, FIDO_OK); ret = select_authenticator(&data, &dev, &assert, &index); From ae78fe3a359333fb0ec0f86ee0137adbf1a52196 Mon Sep 17 00:00:00 2001 From: Philippe Smadja Date: Fri, 20 Jun 2025 19:03:23 +0200 Subject: [PATCH 7/8] fix3 test passkey --- src/passkey_child/passkey_child_assert.c | 23 ++++++++++++++++------- src/tests/cmocka/test_passkey_child.c | 8 ++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/passkey_child/passkey_child_assert.c b/src/passkey_child/passkey_child_assert.c index f23f47a36ad..f360e4155d9 100644 --- a/src/passkey_child/passkey_child_assert.c +++ b/src/passkey_child/passkey_child_assert.c @@ -398,12 +398,6 @@ request_assert(struct passkey_data *data, fido_dev_t *dev, } DEBUG(SSSDBG_TRACE_FUNC, "fido_dev_get_assert PIN succeeded.\n"); - ret = fido_assert_set_uv(_assert, data->user_verification); - if (ret != FIDO_OK) { - DEBUG(SSSDBG_OP_FAILURE, - "fido_assert_set_uv failed [%d]: %s.\n", - ret, fido_strerr(ret)); - } /* error or not: finished */ goto done; } @@ -423,10 +417,25 @@ request_assert(struct passkey_data *data, fido_dev_t *dev, } else { DEBUG(SSSDBG_TRACE_FUNC, "fido_dev_get_assert UV succeeded.\n"); } - /* error not: finished */ + goto done; + } + + /* (no has_pin , no has_uv) or verification = FIDO_OPT_FALSE */ + ret = fido_dev_get_assert(dev, _assert, NULL); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "(no pin, no uv) fido_dev_get_assert failed [%d]: %s.\n", + ret, fido_strerr(ret)); } done: + if (ret == FIDO_OK) { + ret = fido_assert_set_uv(_assert, data->user_verification); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_uv failed [%d]: %s.\n", + ret, fido_strerr(ret)); + } + } if (pin != NULL) { sss_erase_mem_securely(pin, strlen(pin)); } diff --git a/src/tests/cmocka/test_passkey_child.c b/src/tests/cmocka/test_passkey_child.c index 5003152c453..b06b7106492 100644 --- a/src/tests/cmocka/test_passkey_child.c +++ b/src/tests/cmocka/test_passkey_child.c @@ -1235,11 +1235,15 @@ void test_authenticate_integration(void **state) } will_return(__wrap_fido_dev_has_uv, false); will_return(__wrap_fido_dev_has_pin, false); + will_return(__wrap_fido_dev_has_uv, false); + will_return(__wrap_fido_dev_has_pin, false); will_return(__wrap_fido_dev_supports_uv, false); will_return(__wrap_fido_assert_set_uv, FIDO_OK); will_return(__wrap_fido_assert_set_clientdata_hash, FIDO_OK); will_return(__wrap_fido_dev_has_uv, false); will_return(__wrap_fido_dev_has_pin, false); + will_return(__wrap_fido_dev_has_uv, false); + will_return(__wrap_fido_dev_has_pin, false); will_return(__wrap_fido_dev_get_assert, FIDO_OK); will_return(__wrap_fido_assert_set_uv, FIDO_OK); will_return(__wrap_fido_assert_verify, FIDO_OK); @@ -1286,10 +1290,14 @@ void test_get_assert_data_integration(void **state) } will_return(__wrap_fido_dev_has_uv, false); will_return(__wrap_fido_dev_has_pin, false); + will_return(__wrap_fido_dev_has_uv, false); + will_return(__wrap_fido_dev_has_pin, false); will_return(__wrap_fido_dev_supports_uv, false); will_return(__wrap_fido_assert_set_uv, FIDO_OK); will_return(__wrap_fido_dev_has_uv, false); will_return(__wrap_fido_dev_has_pin, false); + will_return(__wrap_fido_dev_has_uv, false); + will_return(__wrap_fido_dev_has_pin, false); will_return(__wrap_fido_dev_get_assert, FIDO_OK); will_return(__wrap_fido_assert_set_uv, FIDO_OK); will_return(__wrap_fido_assert_authdata_ptr, TEST_HEX_AUTH_DATA); From 3402ea8d06234a4937eb373cecf52515a98884ea Mon Sep 17 00:00:00 2001 From: Philippe Smadja Date: Sat, 21 Jun 2025 11:34:55 +0200 Subject: [PATCH 8/8] fix passkey integration tests --- src/tests/cmocka/test_passkey_child.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/tests/cmocka/test_passkey_child.c b/src/tests/cmocka/test_passkey_child.c index b06b7106492..4ae682ddebc 100644 --- a/src/tests/cmocka/test_passkey_child.c +++ b/src/tests/cmocka/test_passkey_child.c @@ -1242,8 +1242,6 @@ void test_authenticate_integration(void **state) will_return(__wrap_fido_assert_set_clientdata_hash, FIDO_OK); will_return(__wrap_fido_dev_has_uv, false); will_return(__wrap_fido_dev_has_pin, false); - will_return(__wrap_fido_dev_has_uv, false); - will_return(__wrap_fido_dev_has_pin, false); will_return(__wrap_fido_dev_get_assert, FIDO_OK); will_return(__wrap_fido_assert_set_uv, FIDO_OK); will_return(__wrap_fido_assert_verify, FIDO_OK); @@ -1296,8 +1294,6 @@ void test_get_assert_data_integration(void **state) will_return(__wrap_fido_assert_set_uv, FIDO_OK); will_return(__wrap_fido_dev_has_uv, false); will_return(__wrap_fido_dev_has_pin, false); - will_return(__wrap_fido_dev_has_uv, false); - will_return(__wrap_fido_dev_has_pin, false); will_return(__wrap_fido_dev_get_assert, FIDO_OK); will_return(__wrap_fido_assert_set_uv, FIDO_OK); will_return(__wrap_fido_assert_authdata_ptr, TEST_HEX_AUTH_DATA);