/* SSSD Authors: Sumit Bose Copyright (C) 2016 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/util.h" #include "providers/ad/ad_pac.h" #include "providers/ad/ad_common.h" #include "providers/ad/ad_id.h" #include "providers/ldap/sdap_idmap.h" #include "providers/ldap/sdap_async_ad.h" static errno_t find_user_entry(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, struct be_acct_req *ar, struct ldb_message **_msg) { const char *user_attrs[] = { SYSDB_NAME, SYSDB_OBJECTCLASS, SYSDB_PAC_BLOB, SYSDB_PAC_BLOB_EXPIRE, NULL }; struct ldb_message *msg; struct ldb_result *res; int ret; TALLOC_CTX *tmp_ctx = NULL; if (dom == NULL || ar == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Missing arguments.\n"); return EINVAL; } tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); return ENOMEM; } if (ar->extra_value && strcmp(ar->extra_value, EXTRA_NAME_IS_UPN) == 0) { ret = sysdb_search_user_by_upn(tmp_ctx, dom, ar->filter_value, user_attrs, &msg); } else { switch (ar->filter_type) { case BE_FILTER_SECID: ret = sysdb_search_user_by_sid_str(tmp_ctx, dom, ar->filter_value, user_attrs, &msg); break; case BE_FILTER_UUID: ret = sysdb_search_object_by_uuid(tmp_ctx, dom, ar->filter_value, user_attrs, &res); if (ret == EOK) { if (res->count == 1) { msg = res->msgs[0]; } else { talloc_free(res); DEBUG(SSSDBG_CRIT_FAILURE, "Search by UUID returned multiple results.\n"); ret = EINVAL; goto done; } } break; case BE_FILTER_NAME: ret = sysdb_search_user_by_name(tmp_ctx, dom, ar->filter_value, user_attrs, &msg); break; default: DEBUG(SSSDBG_OP_FAILURE, "Unsupported filter type [%d].\n", ar->filter_type); ret = EINVAL; goto done; } } if (ret != EOK) { if (ret == ENOENT) { DEBUG(SSSDBG_TRACE_ALL, "No user found with filter [%s].\n", ar->filter_value); } else { DEBUG(SSSDBG_OP_FAILURE, "Looking up user in cache with filter [%s] failed.\n", ar->filter_value); } goto done; } *_msg = talloc_steal(mem_ctx, msg); ret = EOK; done: talloc_free(tmp_ctx); return ret; } errno_t check_if_pac_is_available(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, struct be_acct_req *ar, struct ldb_message **_msg) { struct ldb_message *msg; struct ldb_message_element *el; uint64_t pac_expires; time_t now; int ret; ret = find_user_entry(mem_ctx, dom, ar, &msg); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "find_user_entry failed.\n"); return ret; } el = ldb_msg_find_element(msg, SYSDB_PAC_BLOB); if (el == NULL) { DEBUG(SSSDBG_TRACE_ALL, "No PAC available.\n"); talloc_free(msg); return ENOENT; } pac_expires = ldb_msg_find_attr_as_uint64(msg, SYSDB_PAC_BLOB_EXPIRE, 0); now = time(NULL); if (pac_expires < now) { DEBUG(SSSDBG_TRACE_FUNC, "PAC avaiable but too old.\n"); talloc_free(msg); return ENOENT; } if (_msg != NULL) { *_msg = msg; } return EOK; } errno_t ad_get_sids_from_pac(TALLOC_CTX *mem_ctx, struct sss_idmap_ctx *idmap_ctx, struct PAC_LOGON_INFO *logon_info, char **_user_sid_str, char **_primary_group_sid_str, size_t *_num_sids, char *** _sid_list) { int ret; size_t s; struct netr_SamInfo3 *info3; char *sid_str = NULL; char *msid_str = NULL; char *user_dom_sid_str = NULL; size_t user_dom_sid_str_len; enum idmap_error_code err; hash_table_t *sid_table = NULL; hash_key_t key; hash_value_t value; char *rid_start; char *user_sid_str = NULL; char *primary_group_sid_str = NULL; size_t c; size_t num_sids; char **sid_list = NULL; struct hash_iter_context_t *iter = NULL; hash_entry_t *entry; TALLOC_CTX *tmp_ctx; if (idmap_ctx == NULL || logon_info == NULL || _num_sids == NULL || _sid_list == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Missing parameter.\n"); return EINVAL; } tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); return ENOMEM; } info3 = &logon_info->info3; ret = sss_hash_create(tmp_ctx, info3->sidcount + info3->base.groups.count + 2, &sid_table); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sss_hash_create failed.\n"); goto done; } key.type = HASH_KEY_STRING; value.type = HASH_VALUE_ULONG; err = sss_idmap_smb_sid_to_sid(idmap_ctx, info3->base.domain_sid, &user_dom_sid_str); if (err != IDMAP_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, "sss_idmap_smb_sid_to_sid failed.\n"); ret = EFAULT; goto done; } user_dom_sid_str_len = strlen(user_dom_sid_str); sid_str = talloc_zero_size(tmp_ctx, user_dom_sid_str_len + 12); if (sid_str == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_size failed.\n"); ret = ENOMEM; goto done; } rid_start = sid_str + user_dom_sid_str_len; memcpy(sid_str, user_dom_sid_str, user_dom_sid_str_len); memset(rid_start, '\0', 12); ret = snprintf(rid_start, 12, "-%lu", (unsigned long) info3->base.rid); if (ret < 0 || ret > 12) { DEBUG(SSSDBG_OP_FAILURE, "snprintf failed.\n"); ret = EIO; goto done; } user_sid_str = talloc_strdup(tmp_ctx, sid_str); if (user_sid_str == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); ret = ENOMEM; goto done; } key.str = sid_str; value.ul = 0; memset(rid_start, '\0', 12); ret = snprintf(rid_start, 12, "-%lu", (unsigned long) info3->base.primary_gid); if (ret < 0 || ret > 12) { DEBUG(SSSDBG_OP_FAILURE, "snprintf failed.\n"); ret = EIO; goto done; } primary_group_sid_str = talloc_strdup(tmp_ctx, sid_str); if (primary_group_sid_str == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); ret = ENOMEM; goto done; } key.str = sid_str; value.ul = 0; ret = hash_enter(sid_table, &key, &value); if (ret != HASH_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, "hash_enter failed [%d][%s].\n", ret, hash_error_string(ret)); ret = EIO; goto done; } for (s = 0; s < info3->base.groups.count; s++) { memset(rid_start, '\0', 12); ret = snprintf(rid_start, 12, "-%lu", (unsigned long) info3->base.groups.rids[s].rid); if (ret < 0 || ret > 12) { DEBUG(SSSDBG_OP_FAILURE, "snprintf failed.\n"); ret = EIO; goto done; } key.str = sid_str; value.ul = 0; ret = hash_enter(sid_table, &key, &value); if (ret != HASH_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, "hash_enter failed [%d][%s].\n", ret, hash_error_string(ret)); ret = EIO; goto done; } } for(s = 0; s < info3->sidcount; s++) { err = sss_idmap_smb_sid_to_sid(idmap_ctx, info3->sids[s].sid, &msid_str); if (err != IDMAP_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, "sss_idmap_smb_sid_to_sid failed.\n"); ret = EFAULT; goto done; } key.str = msid_str; value.ul = 0; ret = hash_enter(sid_table, &key, &value); sss_idmap_free_sid(idmap_ctx, msid_str); if (ret != HASH_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, "hash_enter failed [%d][%s].\n", ret, hash_error_string(ret)); ret = EIO; goto done; } } num_sids = hash_count(sid_table); sid_list = talloc_array(tmp_ctx, char *, num_sids); if (sid_list == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); ret = ENOMEM; goto done; } iter = new_hash_iter_context(sid_table); if (iter == NULL) { DEBUG(SSSDBG_OP_FAILURE, "new_hash_iter_context failed.\n"); ret = EINVAL; goto done; } c = 0; while ((entry = iter->next(iter)) != NULL) { sid_list[c] = talloc_strdup(sid_list, entry->key.str); if (sid_list[c] == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); ret = ENOMEM; goto done; } c++; } ret = EOK; done: sss_idmap_free_sid(idmap_ctx, user_dom_sid_str); hash_destroy(sid_table); if (ret == EOK) { *_sid_list = talloc_steal(mem_ctx, sid_list); *_user_sid_str = talloc_steal(mem_ctx, user_sid_str); *_num_sids = num_sids; *_primary_group_sid_str = talloc_steal(mem_ctx, primary_group_sid_str); } talloc_free(tmp_ctx); return ret; } errno_t ad_get_pac_data_from_user_entry(TALLOC_CTX *mem_ctx, struct ldb_message *msg, struct sss_idmap_ctx *idmap_ctx, char **_username, char **user_sid, char **primary_group_sid, size_t *num_sids, char ***group_sids) { int ret; struct ldb_message_element *el; struct PAC_LOGON_INFO *logon_info = NULL; const char *dummy; TALLOC_CTX *tmp_ctx = NULL; char *username; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); return ENOMEM; } el = ldb_msg_find_element(msg, SYSDB_PAC_BLOB); if (el == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Missing PAC blob.\n"); ret = EINVAL; goto done; } if (el->num_values != 1) { DEBUG(SSSDBG_OP_FAILURE, "Expected only one PAC blob."); ret = EINVAL; goto done; } ret = ad_get_data_from_pac(tmp_ctx, el->values[0].data, el->values[0].length, &logon_info); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "get_data_from_pac failed.\n"); goto done; } dummy = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); if (dummy == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Missing user name in cache entry.\n"); ret = EINVAL; goto done; } username = talloc_strdup(tmp_ctx, dummy); if (username == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); ret = ENOMEM; goto done; } ret = ad_get_sids_from_pac(mem_ctx, idmap_ctx, logon_info, user_sid, primary_group_sid, num_sids, group_sids); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "get_sids_from_pac failed.\n"); goto done; } *_username = talloc_steal(mem_ctx, username); ret = EOK; done: talloc_free(tmp_ctx); return ret; } struct ad_handle_pac_initgr_state { struct be_acct_req *ar; const char *err; int dp_error; int sdap_ret; size_t num_missing_sids; char **missing_sids; size_t num_cached_groups; char **cached_groups; char *username; struct sss_domain_info *user_dom; }; static void ad_handle_pac_initgr_lookup_sids_done(struct tevent_req *subreq); struct tevent_req *ad_handle_pac_initgr_send(TALLOC_CTX *mem_ctx, struct be_ctx *be_ctx, struct be_acct_req *ar, struct sdap_id_ctx *id_ctx, struct sdap_domain *sdom, struct sdap_id_conn_ctx *conn, bool noexist_delete, struct ldb_message *msg) { int ret; struct ad_handle_pac_initgr_state *state; struct tevent_req *req; struct tevent_req *subreq; char *user_sid; char *primary_group_sid; size_t num_sids; char **group_sids; bool use_id_mapping; req = tevent_req_create(mem_ctx, &state, struct ad_handle_pac_initgr_state); if (req == NULL) { DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); return NULL; } state->user_dom = sdom->dom; /* The following variables are currently unused because no sub-request * returns any of them. But they are needed to allow the same signature as * sdap_handle_acct_req_recv() from the alternative group-membership * lookup path. */ state->err = NULL; state->dp_error = DP_ERR_OK; state->sdap_ret = EOK; ret = ad_get_pac_data_from_user_entry(state, msg, id_ctx->opts->idmap_ctx->map, &state->username, &user_sid, &primary_group_sid, &num_sids, &group_sids); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "ad_get_pac_data_from_user_entry failed.\n"); goto done; } use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( id_ctx->opts->idmap_ctx, sdom->dom->name, sdom->dom->domain_id); if (use_id_mapping && sdom->dom->ignore_group_members == false) { /* In contrast to the tokenGroups based group-membership lookup the * PAC based approach can be used for sub-domains with id-mapping as * well because the PAC will only contain groups which are valid in * the target domain, i.e. it will not contain domain-local groups for * domains other than the user domain. This means the groups must not * be looked up immediately to determine if they are domain-local or * not. * * Additionally, as a temporary workaround until * https://fedorahosted.org/sssd/ticket/2522 is fixed, we also fetch * the group object if group members are ignored to avoid having to * transfer and retain members when the fake tokengroups object * without name is replaced by the full group object. */ DEBUG(SSSDBG_TRACE_ALL, "Running PAC processing with id-mapping.\n"); ret = sdap_ad_save_group_membership_with_idmapping(state->username, sdom->dom, id_ctx->opts->idmap_ctx, num_sids, group_sids); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sdap_ad_save_group_membership_with_idmapping failed.\n"); } /* this path only includes cache operation, so we can finish the * request immediately */ goto done; } else { DEBUG(SSSDBG_TRACE_ALL, "Running PAC processing with external IDs.\n"); ret = sdap_ad_tokengroups_get_posix_members(state, sdom->dom, num_sids, group_sids, &state->num_missing_sids, &state->missing_sids, &state->num_cached_groups, &state->cached_groups); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sdap_ad_tokengroups_get_posix_members failed.\n"); goto done; } /* download missing SIDs */ subreq = sdap_ad_resolve_sids_send(state, be_ctx->ev, id_ctx, conn, id_ctx->opts, sdom->dom, state->missing_sids); if (subreq == NULL) { DEBUG(SSSDBG_OP_FAILURE, "sdap_ad_resolve_sids_send failed.\n"); ret = ENOMEM; goto done; } tevent_req_set_callback(subreq, ad_handle_pac_initgr_lookup_sids_done, req); } return req; done: if (ret == EOK) { tevent_req_done(req); } else { tevent_req_error(req, ret); } tevent_req_post(req, be_ctx->ev); return req; } static void ad_handle_pac_initgr_lookup_sids_done(struct tevent_req *subreq) { struct ad_handle_pac_initgr_state *state; struct tevent_req *req = NULL; errno_t ret; char **cached_groups; size_t num_cached_groups; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct ad_handle_pac_initgr_state); ret = sdap_ad_resolve_sids_recv(subreq); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to resolve missing SIDs " "[%d]: %s\n", ret, strerror(ret)); goto done; } ret = sdap_ad_tokengroups_get_posix_members(state, state->user_dom, state->num_missing_sids, state->missing_sids, NULL, NULL, &num_cached_groups, &cached_groups); if (ret != EOK){ DEBUG(SSSDBG_MINOR_FAILURE, "sdap_ad_tokengroups_get_posix_members failed [%d]: %s\n", ret, strerror(ret)); goto done; } state->cached_groups = concatenate_string_array(state, state->cached_groups, state->num_cached_groups, cached_groups, num_cached_groups); if (state->cached_groups == NULL) { ret = ENOMEM; goto done; } /* update membership of existing groups */ ret = sdap_ad_tokengroups_update_members(state->username, state->user_dom->sysdb, state->user_dom, state->cached_groups); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Membership update failed [%d]: %s\n", ret, strerror(ret)); goto done; } done: if (ret != EOK) { tevent_req_error(req, ret); return; } tevent_req_done(req); } errno_t ad_handle_pac_initgr_recv(struct tevent_req *req, int *_dp_error, const char **_err, int *sdap_ret) { struct ad_handle_pac_initgr_state *state; state = tevent_req_data(req, struct ad_handle_pac_initgr_state); if (_dp_error) { *_dp_error = state->dp_error; } if (_err) { *_err = state->err; } if (sdap_ret) { *sdap_ret = state->sdap_ret; } TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; }