diff options
Diffstat (limited to '')
-rw-r--r-- | src/responder/pam/pamsrv_cmd.c | 3085 |
1 files changed, 3085 insertions, 0 deletions
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c new file mode 100644 index 0000000..c23ea7b --- /dev/null +++ b/src/responder/pam/pamsrv_cmd.c @@ -0,0 +1,3085 @@ +/* + SSSD + + PAM Responder + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2009 + Copyright (C) Sumit Bose <sbose@redhat.com> 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 <http://www.gnu.org/licenses/>. +*/ + +#define _GNU_SOURCE + +#include <time.h> +#include <string.h> +#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: + 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; + } + + 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; + } + + 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; + +#ifdef BUILD_FILES_PROVIDER + if (is_files_provider(preq->domain)) { + *_sc_allow = true; + *_passkey_allow = false; + *_local_policy = NULL; + + return EOK; + } +#endif + + 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.password_auth) { + 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 && + !is_files_provider(preq->domain)) { + 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; + } + + /* 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); + } + + /* 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->rctx, 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; + } + + if (cert_count > 1) { + 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. */ + 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; + } + } + + /* 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, 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->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 <type><size><data>"}, + {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; +} |