/* SSSD IPA Backend Module -- Session Management Authors: Fabiano FidĂȘncio Copyright (C) 2017 Red Hat This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "util/child_common.h" #include "providers/ldap/sdap_async.h" #include "providers/ipa/ipa_common.h" #include "providers/ipa/ipa_config.h" #include "providers/ipa/ipa_hosts.h" #include "providers/ipa/ipa_subdomains.h" #include "providers/ipa/ipa_session.h" #include "providers/ipa/ipa_rules_common.h" #include "providers/ipa/ipa_deskprofile_private.h" #include "providers/ipa/ipa_deskprofile_config.h" #include "providers/ipa/ipa_deskprofile_rules.h" #include "providers/ipa/ipa_deskprofile_rules_util.h" #include "sss_iface/sss_iface_async.h" /* Those here are used for sending a message to the deskprofile client * informing that our side is done. */ #define SSS_FLEETCOMMANDERCLIENT_BUS "org.freedesktop.FleetCommanderClient" #define SSS_FLEETCOMMANDERCLIENT_PATH "/org/freedesktop/FleetCommanderClient" #define SSS_FLEETCOMMANDERCLIENT_IFACE "org.freedesktop.FleetCommanderClient" #define MINUTE_IN_SECONDS 60 struct ipa_fetch_deskprofile_state { struct tevent_context *ev; struct be_ctx *be_ctx; struct sdap_id_ctx *sdap_ctx; struct ipa_session_ctx *session_ctx; struct sdap_id_op *sdap_op; struct dp_option *ipa_options; struct sdap_search_base **search_bases; const char *username; /* Hosts */ struct ipa_common_entries *hosts; struct sysdb_attrs *ipa_host; /* Rules */ struct ipa_common_entries *rules; struct sysdb_attrs *config; uint16_t priority; }; static errno_t ipa_fetch_deskprofile_retry(struct tevent_req *req); static void ipa_fetch_deskprofile_connect_done(struct tevent_req *subreq); static errno_t ipa_fetch_deskprofile_hostinfo(struct tevent_req *req); static void ipa_fetch_deskprofile_hostinfo_done(struct tevent_req *subreq); static void ipa_fetch_deskprofile_config_done(struct tevent_req *subreq); static void ipa_fetch_deskprofile_rules_done(struct tevent_req *subreq); static struct tevent_req * ipa_fetch_deskprofile_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct be_ctx *be_ctx, struct ipa_session_ctx *session_ctx, const char *username) { struct ipa_fetch_deskprofile_state *state; struct tevent_req *req; time_t now; time_t refresh_interval; time_t request_interval; time_t next_request; bool offline; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct ipa_fetch_deskprofile_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); return NULL; } state->ev = ev; state->be_ctx = be_ctx; state->session_ctx = session_ctx; state->sdap_ctx = session_ctx->sdap_ctx; state->ipa_options = session_ctx->ipa_options; state->search_bases = session_ctx->deskprofile_search_bases; state->username = username; state->hosts = talloc_zero(state, struct ipa_common_entries); if (state->hosts == NULL) { ret = ENOMEM; goto immediately; } state->rules = talloc_zero(state, struct ipa_common_entries); if (state->rules == NULL) { ret = ENOMEM; goto immediately; } if (state->search_bases == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "No Desktop Profile search base found.\n"); ret = EINVAL; goto immediately; } state->sdap_op = sdap_id_op_create(state, state->sdap_ctx->conn->conn_cache); if (state->sdap_op == NULL) { DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create() failed\n"); ret = ENOMEM; goto immediately; } now = time(NULL); request_interval = dp_opt_get_int(state->ipa_options, IPA_DESKPROFILE_REQUEST_INTERVAL); /* This value is in minutes ... */ request_interval *= MINUTE_IN_SECONDS; if (state->session_ctx->no_rules_found && now < session_ctx->last_request + request_interval) { next_request = (session_ctx->last_request + request_interval - now); /* This value is in seconds ... */ next_request /= 60; DEBUG(SSSDBG_TRACE_FUNC, "No rules were found in the last request.\n" "Next request will happen in any login after %"SPRItime" minutes\n", next_request); ret = ENOENT; goto immediately; } state->session_ctx->no_rules_found = false; offline = be_is_offline(be_ctx); DEBUG(SSSDBG_TRACE_ALL, "Connection status is [%s].\n", offline ? "offline" : "online"); refresh_interval = dp_opt_get_int(state->ipa_options, IPA_DESKPROFILE_REFRESH); if (offline || now < session_ctx->last_update + refresh_interval) { DEBUG(SSSDBG_TRACE_FUNC, "Performing cached Desktop Profile evaluation\n"); ret = EOK; goto immediately; } ret = ipa_fetch_deskprofile_retry(req); if (ret != EAGAIN) { goto immediately; } return req; immediately: if (ret == EOK) { tevent_req_done(req); } else { tevent_req_error(req, ret); } tevent_req_post(req, ev); return req; } static errno_t ipa_fetch_deskprofile_retry(struct tevent_req *req) { struct tevent_req *subreq; struct ipa_fetch_deskprofile_state *state; int ret; state = tevent_req_data(req, struct ipa_fetch_deskprofile_state); subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); if (subreq == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_connect_send() failed: %d (%s)\n", ret, strerror(ret)); return ret; } tevent_req_set_callback(subreq, ipa_fetch_deskprofile_connect_done, req); return EAGAIN; } static void ipa_fetch_deskprofile_connect_done(struct tevent_req *subreq) { struct tevent_req *req = NULL; int dp_error; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); ret = sdap_id_op_connect_recv(subreq, &dp_error); talloc_zfree(subreq); if (ret != EOK) { goto done; } ret = ipa_fetch_deskprofile_hostinfo(req); if (ret == EAGAIN) { return; } done: if (ret == EOK) { tevent_req_done(req); } else { tevent_req_error(req, ret); } } static errno_t ipa_fetch_deskprofile_hostinfo(struct tevent_req *req) { struct tevent_req *subreq; struct ipa_fetch_deskprofile_state *state; const char *hostname; state = tevent_req_data(req, struct ipa_fetch_deskprofile_state); hostname = dp_opt_get_string(state->ipa_options, IPA_HOSTNAME); subreq = ipa_host_info_send(state, state->ev, sdap_id_op_handle(state->sdap_op), state->sdap_ctx->opts, hostname, state->session_ctx->host_map, state->session_ctx->hostgroup_map, state->session_ctx->host_search_bases); if (subreq == NULL) { return ENOMEM; } tevent_req_set_callback(subreq, ipa_fetch_deskprofile_hostinfo_done, req); return EAGAIN; } static void ipa_fetch_deskprofile_hostinfo_done(struct tevent_req *subreq) { struct tevent_req *req; struct ipa_fetch_deskprofile_state *state; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct ipa_fetch_deskprofile_state); ret = ipa_host_info_recv(subreq, state, &state->hosts->entry_count, &state->hosts->entries, &state->hosts->group_count, &state->hosts->groups); state->hosts->entry_subdir = DESKPROFILE_HOSTS_SUBDIR; state->hosts->group_subdir = DESKPROFILE_HOSTGROUPS_SUBDIR; talloc_zfree(subreq); if (ret != EOK) { goto done; } ret = ipa_get_host_attrs(state->ipa_options, state->hosts->entry_count, state->hosts->entries, &state->ipa_host); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not locate IPA host.\n"); goto done; } subreq = ipa_deskprofile_get_config_send(state, state->ev, sdap_id_op_handle(state->sdap_op), state->sdap_ctx->opts, state->ipa_options); if (subreq == NULL) { ret = ENOMEM; goto done; } tevent_req_set_callback(subreq, ipa_fetch_deskprofile_config_done, req); return; done: tevent_req_error(req, ret); } static void ipa_fetch_deskprofile_config_done(struct tevent_req *subreq) { struct tevent_req *req; struct ipa_fetch_deskprofile_state *state; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct ipa_fetch_deskprofile_state); ret = ipa_deskprofile_get_config_recv(subreq, state, &state->config); talloc_zfree(subreq); if (ret != EOK) { goto done; } ret = sysdb_store_custom(state->be_ctx->domain, IPA_DESKPROFILE_PRIORITY, DESKPROFILE_CONFIG_SUBDIR, state->config); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to save Desktop Profile policy\n"); goto done; } subreq = ipa_deskprofile_rule_info_send(state, state->ev, sdap_id_op_handle(state->sdap_op), state->sdap_ctx->opts, state->search_bases, state->ipa_host, state->be_ctx->domain, state->username); if (subreq == NULL) { ret = ENOMEM; goto done; } tevent_req_set_callback(subreq, ipa_fetch_deskprofile_rules_done, req); return; done: tevent_req_error(req, ret); } static void ipa_fetch_deskprofile_rules_done(struct tevent_req *subreq) { struct tevent_req *req; struct ipa_fetch_deskprofile_state *state; int dp_error; errno_t ret; bool found; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct ipa_fetch_deskprofile_state); ret = ipa_deskprofile_rule_info_recv(subreq, state, &state->rules->entry_count, &state->rules->entries); state->rules->entry_subdir = DESKPROFILE_RULES_SUBDIR; talloc_zfree(subreq); if (ret == ENOENT) { /* Set ret to EOK so we can safely call sdap_id_op_done. */ ret = EOK; found = false; } else if (ret == EOK) { found = true; } else { goto done; } ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); if (dp_error == DP_ERR_OK && ret != EOK) { /* retry */ ret = ipa_fetch_deskprofile_retry(req); if (ret != EAGAIN) { tevent_req_error(req, ret); } return; } else if (ret != EOK) { tevent_req_error(req, ret); return; } /* For now, let's completely purge the previous stored * rules before saving the new ones */ ret = ipa_common_purge_rules(state->be_ctx->domain, DESKPROFILE_RULES_SUBDIR); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to remove Desktop Profile rules\n"); goto done; } if (!found) { ret = ENOENT; goto done; } ret = ipa_common_save_rules(state->be_ctx->domain, state->hosts, NULL, state->rules, &state->session_ctx->last_update); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to save Desktop Profile rules\n"); goto done; } ret = EOK; done: if (ret != EOK) { tevent_req_error(req, ret); return; } tevent_req_done(req); } static errno_t ipa_fetch_deskprofile_recv(struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } struct ipa_pam_session_handler_state { struct tevent_context *ev; struct be_ctx *be_ctx; struct ipa_session_ctx *session_ctx; struct pam_data *pd; /* Those attributes are used for: * - saving the deskprofile rules to the disk; * - deleting the deskprofile rules from the disk; * - contacting the deskprofile client that everything is ready; */ char *shortname; char *domain; char *user_dir; uid_t uid; gid_t gid; }; static errno_t ipa_pam_session_handler_get_deskprofile_user_info( TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *username, char **_shortname, char **_domain, char **_user_dir, uid_t *uid, gid_t *gid); static void ipa_pam_session_handler_done(struct tevent_req *subreq); static errno_t ipa_pam_session_handler_save_deskprofile_rules( struct be_ctx *be_ctx, struct sss_domain_info *domain, const char *username, /* fully-qualified */ const char *user_dir, const char *hostname, uid_t uid, gid_t gid); static errno_t ipa_pam_session_handler_notify_deskprofile_client(TALLOC_CTX *mem_ctx, struct tevent_context *ev, uid_t uid, const char *user_dir, uint16_t prio); struct tevent_req * ipa_pam_session_handler_send(TALLOC_CTX *mem_ctx, struct ipa_session_ctx *session_ctx, struct pam_data *pd, struct dp_req_params *params) { struct tevent_req *req; struct tevent_req *subreq; struct ipa_pam_session_handler_state *state; errno_t ret; DEBUG(SSSDBG_TRACE_FUNC, "Retrieving Desktop Profile rules\n"); req = tevent_req_create(mem_ctx, &state, struct ipa_pam_session_handler_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); return NULL; } state->pd = pd; state->ev = params->ev; state->be_ctx = params->be_ctx; state->session_ctx = session_ctx; /* Get all the user info that will be needed in order the delete the * user's deskprofile directory from the disk, create the user's directory, * save the fetched rules to the disk and notify the deskprofile client * that this operation is done. */ ret = ipa_pam_session_handler_get_deskprofile_user_info( state, params->domain, pd->user, &state->shortname, &state->domain, &state->user_dir, &state->uid, &state->gid); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "ipa_deskprofile_get_user_info() failed [%d]: %s\n", ret, sss_strerror(ret)); state->pd->pam_status = PAM_SESSION_ERR; goto done; } /* As no proper merging mechanism has been implemented yet ... * let's just remove the user directory stored in the disk as it's * going to be created again in case there's any rule fetched. */ ret = ipa_deskprofile_rules_remove_user_dir(state->user_dir, state->uid, state->gid); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "ipa_deskprofile_rules_remove_user_dir() failed.\n"); state->pd->pam_status = PAM_SESSION_ERR; goto done; } subreq = ipa_fetch_deskprofile_send(state, state->ev, state->be_ctx, state->session_ctx, pd->user); if (subreq == NULL) { state->pd->pam_status = PAM_SESSION_ERR; goto done; } tevent_req_set_callback(subreq, ipa_pam_session_handler_done, req); return req; done: tevent_req_done(req); tevent_req_post(req, params->ev); return req; } static void ipa_pam_session_handler_done(struct tevent_req *subreq) { struct tevent_req *req; struct ipa_pam_session_handler_state *state; const char *hostname; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct ipa_pam_session_handler_state); ret = ipa_fetch_deskprofile_recv(subreq); talloc_free(subreq); if (ret == ENOENT) { DEBUG(SSSDBG_FUNC_DATA, "No Desktop Profile rules found\n"); if (!state->session_ctx->no_rules_found) { state->session_ctx->no_rules_found = true; state->session_ctx->last_request = time(NULL); } state->pd->pam_status = PAM_SUCCESS; goto done; } else if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to fetch Desktop Profile rules [%d]: %s\n", ret, sss_strerror(ret)); state->pd->pam_status = PAM_SYSTEM_ERR; goto done; } state->session_ctx->last_request = time(NULL); hostname = dp_opt_get_string(state->session_ctx->ipa_options, IPA_HOSTNAME); ret = ipa_pam_session_handler_save_deskprofile_rules(state->be_ctx, state->be_ctx->domain, state->pd->user, state->user_dir, hostname, state->uid, state->gid); if (ret == EOK || ret == ENOENT) { state->pd->pam_status = PAM_SUCCESS; } else { state->pd->pam_status = PAM_SESSION_ERR; } done: /* TODO For backward compatibility we always return EOK to DP now. */ tevent_req_done(req); } errno_t ipa_pam_session_handler_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, struct pam_data **_data) { struct ipa_pam_session_handler_state *state = NULL; state = tevent_req_data(req, struct ipa_pam_session_handler_state); TEVENT_REQ_RETURN_ON_ERROR(req); *_data = talloc_steal(mem_ctx, state->pd); return EOK; } static errno_t ipa_pam_session_handler_get_deskprofile_user_info(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *username, char **_shortname, char **_domain, char **_user_dir, uid_t *_uid, gid_t *_gid) { TALLOC_CTX *tmp_ctx; struct ldb_result *res = NULL; char *shortname; char *domain_name; char *user_dir; uid_t uid; gid_t gid; errno_t ret; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } ret = sss_parse_internal_fqname(tmp_ctx, username, &shortname, &domain_name); if (ret != EOK) { DEBUG(SSSDBG_TRACE_FUNC, "Failed to parse \"%s\" [%d]: %s\n", username, ret, sss_strerror(ret)); goto done; } user_dir = talloc_asprintf(tmp_ctx, IPA_DESKPROFILE_RULES_USER_DIR"/%s/%s", domain_name, shortname); if (user_dir == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed!\n"); ret = ENOMEM; goto done; } ret = sysdb_getpwnam(tmp_ctx, domain, username, &res); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_getpwnam() failed [%d]: %s\n", ret, sss_strerror(ret)); goto done; } if (res->count != 1) { DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_getpwnam() returned unexpected amount of users. " "Expected [%d], got [%d]\n", 1, res->count); ret = EINVAL; goto done; } uid = ldb_msg_find_attr_as_uint64(res->msgs[0], SYSDB_UIDNUM, 0); gid = ldb_msg_find_attr_as_uint64(res->msgs[0], SYSDB_GIDNUM, 0); if (uid == 0 || gid == 0) { /* As IPA doesn't handle root users ou groups, we know for sure that's * something wrong in case we get uid = 0 or gid = 0. */ ret = EINVAL; goto done; } ret = EOK; *_shortname = talloc_steal(mem_ctx, shortname); *_domain = talloc_steal(mem_ctx, domain_name); *_user_dir = talloc_steal(mem_ctx, user_dir); *_uid = uid; *_gid = gid; done: talloc_free(tmp_ctx); return ret; } static errno_t ipa_pam_session_handler_save_deskprofile_rules( struct be_ctx *be_ctx, struct sss_domain_info *domain, const char *username, /* fully-qualified */ const char *user_dir, const char *hostname, uid_t uid, gid_t gid) { TALLOC_CTX *tmp_ctx; const char **attrs_get_cached_rules; size_t rule_count; struct sysdb_attrs **rules; uint16_t priority; errno_t ret; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } /* Get Desktop Profile priority from sysdb */ ret = deskprofile_get_cached_priority(be_ctx->domain, &priority); if (ret == ENOENT) { DEBUG(SSSDBG_FUNC_DATA, "No Desktop Profile priority found in sysdb\n"); goto done; } else if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "deskprofile_get_cached_priority() failed [%d]: %s\n", ret, sss_strerror(ret)); goto done; } /* Get Desktop Profile rules from sysdb */ attrs_get_cached_rules = deskprofile_get_attrs_to_get_cached_rules(tmp_ctx); if (attrs_get_cached_rules == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "deskprofile_get_attrs_get_cached_rules() failed\n"); ret = ENOMEM; goto done; } ret = ipa_common_get_cached_rules(tmp_ctx, be_ctx->domain, IPA_DESKPROFILE_RULE, DESKPROFILE_RULES_SUBDIR, attrs_get_cached_rules, &rule_count, &rules); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not retrieve Desktop Profile rules from the cache\n"); goto done; } /* nothing to do for FC */ if (!rule_count) { DEBUG(SSSDBG_FUNC_DATA, "No Desktop Profile rules found in sysdb\n"); ret = ENOENT; goto done; } /* Create the user directory where the rules are going to be stored */ ret = ipa_deskprofile_rules_create_user_dir(username, uid, gid); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create the user directory [%d]: %s\n", ret, sss_strerror(ret)); goto done; } /* Save the rules to the disk */ for (size_t i = 0; i < rule_count; i++) { ret = ipa_deskprofile_rules_save_rule_to_disk(tmp_ctx, priority, rules[i], domain, hostname, username, uid, gid); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to save a Desktop Profile Rule to disk [%d]: %s\n", ret, sss_strerror(ret)); continue; } } /* Notify FleetCommander that our side is done */ ret = ipa_pam_session_handler_notify_deskprofile_client(be_ctx, be_ctx->ev, uid, user_dir, priority); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "ipa_pam_session_handler_notify_deskprofile_client() " "failed [%d]: %s\n", ret, sss_strerror(ret)); goto done; } ret = EOK; done: talloc_free(tmp_ctx); return ret; } static void ipa_pam_session_handler_notify_deskprofile_client_done(struct tevent_req *subreq); static errno_t ipa_pam_session_handler_notify_deskprofile_client(TALLOC_CTX *mem_ctx, struct tevent_context *ev, uid_t uid, const char *user_dir, uint16_t prio) { struct sbus_connection *conn; struct tevent_req *subreq; conn = sbus_connect_system(mem_ctx, ev, NULL, NULL); if (conn == NULL) { return ENOMEM; } subreq = sbus_call_fleet_ProcessSSSDFiles_send(mem_ctx, conn, SSS_FLEETCOMMANDERCLIENT_BUS, SSS_FLEETCOMMANDERCLIENT_PATH, uid, user_dir, prio); if (subreq == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); talloc_free(conn); return ENOMEM; } tevent_req_set_callback(subreq, ipa_pam_session_handler_notify_deskprofile_client_done, conn); return EOK; } static void ipa_pam_session_handler_notify_deskprofile_client_done(struct tevent_req *subreq) { struct sbus_connection *conn; errno_t ret; conn = tevent_req_callback_data(subreq, struct sbus_connection); ret = sbus_call_fleet_ProcessSSSDFiles_recv(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Error sending sbus message [%d]: %s\n", ret, sss_strerror(ret)); } talloc_free(conn); }