summaryrefslogtreecommitdiffstats
path: root/src/providers/ad/ad_gpo.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/providers/ad/ad_gpo.c')
-rw-r--r--src/providers/ad/ad_gpo.c5105
1 files changed, 5105 insertions, 0 deletions
diff --git a/src/providers/ad/ad_gpo.c b/src/providers/ad/ad_gpo.c
new file mode 100644
index 0000000..94959c3
--- /dev/null
+++ b/src/providers/ad/ad_gpo.c
@@ -0,0 +1,5105 @@
+/*
+ SSSD
+
+ Authors:
+ Yassir Elley <yelley@redhat.com>
+
+ Copyright (C) 2013 Red Hat
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * This file implements the following pair of *public* functions (see header):
+ * ad_gpo_access_send/recv: provides client-side GPO processing
+ *
+ * This file also implements the following pairs of *private* functions (which
+ * are used by the public functions):
+ * ad_gpo_process_som_send/recv: populate list of gp_som objects
+ * ad_gpo_process_gpo_send/recv: populate list of gp_gpo objects
+ * ad_gpo_process_cse_send/recv: retrieve policy file data
+ */
+
+#include <ctype.h>
+#include <security/pam_modules.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <ini_configobj.h>
+#include "util/util.h"
+#include "util/strtonum.h"
+#include "util/child_common.h"
+#include "providers/data_provider.h"
+#include "providers/backend.h"
+#include "providers/ad/ad_access.h"
+#include "providers/ad/ad_common.h"
+#include "providers/ad/ad_domain_info.h"
+#include "providers/ad/ad_gpo.h"
+#include "providers/ldap/sdap_access.h"
+#include "providers/ldap/sdap_async.h"
+#include "providers/ldap/sdap.h"
+#include "providers/ldap/sdap_idmap.h"
+#include "util/util_sss_idmap.h"
+#include "util/sss_chain_id.h"
+#include <ndr.h>
+#include <gen_ndr/security.h>
+
+/* == gpo-ldap constants =================================================== */
+
+#define AD_AT_DN "distinguishedName"
+#define AD_AT_UAC "userAccountControl"
+#define AD_AT_SAMACCOUNTNAME "sAMAccountName"
+#define AD_AT_CONFIG_NC "configurationNamingContext"
+#define AD_AT_GPLINK "gPLink"
+#define AD_AT_GPOPTIONS "gpOptions"
+#define AD_AT_NT_SEC_DESC "nTSecurityDescriptor"
+#define AD_AT_CN "cn"
+#define AD_AT_FILE_SYS_PATH "gPCFileSysPath"
+#define AD_AT_MACHINE_EXT_NAMES "gPCMachineExtensionNames"
+#define AD_AT_FUNC_VERSION "gPCFunctionalityVersion"
+#define AD_AT_FLAGS "flags"
+#define AD_AT_SID "objectSid"
+
+#define UAC_WORKSTATION_TRUST_ACCOUNT 0x00001000
+#define UAC_SERVER_TRUST_ACCOUNT 0x00002000
+#define AD_AGP_GUID "edacfd8f-ffb3-11d1-b41d-00a0c968f939"
+#define AD_AUTHENTICATED_USERS_SID "S-1-5-11"
+
+/* == gpo-smb constants ==================================================== */
+
+#define SMB_STANDARD_URI "smb://"
+#define BUFSIZE 65536
+
+#define RIGHTS_SECTION "Privilege Rights"
+#define ALLOW_LOGON_INTERACTIVE "SeInteractiveLogonRight"
+#define DENY_LOGON_INTERACTIVE "SeDenyInteractiveLogonRight"
+#define ALLOW_LOGON_REMOTE_INTERACTIVE "SeRemoteInteractiveLogonRight"
+#define DENY_LOGON_REMOTE_INTERACTIVE "SeDenyRemoteInteractiveLogonRight"
+#define ALLOW_LOGON_NETWORK "SeNetworkLogonRight"
+#define DENY_LOGON_NETWORK "SeDenyNetworkLogonRight"
+#define ALLOW_LOGON_BATCH "SeBatchLogonRight"
+#define DENY_LOGON_BATCH "SeDenyBatchLogonRight"
+#define ALLOW_LOGON_SERVICE "SeServiceLogonRight"
+#define DENY_LOGON_SERVICE "SeDenyServiceLogonRight"
+
+#define GP_EXT_GUID_SECURITY "{827D319E-6EAC-11D2-A4EA-00C04F79F83A}"
+#define GP_EXT_GUID_SECURITY_SUFFIX "/Machine/Microsoft/Windows NT/SecEdit/GptTmpl.inf"
+
+#ifndef SSSD_LIBEXEC_PATH
+#error "SSSD_LIBEXEC_PATH not defined"
+#else
+#define GPO_CHILD SSSD_LIBEXEC_PATH"/gpo_child"
+#endif
+
+#define GPO_CHILD_LOG_FILE "gpo_child"
+
+/* If INI_PARSE_IGNORE_NON_KVP is not defined, use 0 (no effect) */
+#ifndef INI_PARSE_IGNORE_NON_KVP
+#define INI_PARSE_IGNORE_NON_KVP 0
+#warning INI_PARSE_IGNORE_NON_KVP not defined.
+#endif
+
+/* == common data structures and declarations ============================= */
+
+struct gp_som {
+ const char *som_dn;
+ struct gp_gplink **gplink_list;
+ int num_gplinks;
+};
+
+struct gp_gplink {
+ const char *gpo_dn;
+ bool enforced;
+};
+
+struct gp_gpo {
+ struct security_descriptor *gpo_sd;
+ const char *gpo_dn;
+ const char *gpo_guid;
+ const char *smb_server;
+ const char *smb_share;
+ const char *smb_path;
+ const char **gpo_cse_guids;
+ int num_gpo_cse_guids;
+ int gpo_func_version;
+ int gpo_flags;
+ bool send_to_child;
+ const char *policy_filename;
+};
+
+enum ace_eval_agp_status {
+ AD_GPO_ACE_DENIED,
+ AD_GPO_ACE_ALLOWED,
+ AD_GPO_ACE_NEUTRAL
+};
+
+struct tevent_req *ad_gpo_process_som_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sdap_id_conn_ctx *conn,
+ struct ldb_context *ldb_ctx,
+ struct sdap_id_op *sdap_op,
+ struct sdap_options *opts,
+ struct dp_option *ad_options,
+ int timeout,
+ const char *target_dn,
+ const char *domain_name);
+int ad_gpo_process_som_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct gp_som ***som_list);
+
+struct tevent_req *
+ad_gpo_process_gpo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sdap_id_op *sdap_op,
+ struct sdap_options *opts,
+ char *server_hostname,
+ struct sss_domain_info *host_domain,
+ struct ad_access_ctx *access_ctx,
+ int timeout,
+ struct gp_som **som_list);
+int ad_gpo_process_gpo_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct gp_gpo ***candidate_gpos,
+ int *num_candidate_gpos);
+
+struct tevent_req *ad_gpo_process_cse_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ bool send_to_child,
+ struct sss_domain_info *domain,
+ const char *gpo_guid,
+ const char *smb_server,
+ const char *smb_share,
+ const char *smb_path,
+ const char *smb_cse_suffix,
+ int cached_gpt_version,
+ int gpo_timeout_option);
+
+int ad_gpo_process_cse_recv(struct tevent_req *req);
+
+/* == ad_gpo_parse_map_options and helpers ==================================*/
+
+#define GPO_LOGIN "login"
+#define GPO_SU "su"
+#define GPO_SU_L "su-l"
+#define GPO_GDM_FINGERPRINT "gdm-fingerprint"
+#define GPO_GDM_PASSWORD "gdm-password"
+#define GPO_GDM_SMARTCARD "gdm-smartcard"
+#define GPO_KDM "kdm"
+#define GPO_LIGHTDM "lightdm"
+#define GPO_LXDM "lxdm"
+#define GPO_SDDM "sddm"
+#define GPO_UNITY "unity"
+#define GPO_XDM "xdm"
+#define GPO_SSHD "sshd"
+#define GPO_FTP "ftp"
+#define GPO_SAMBA "samba"
+#ifdef HAVE_DEBIAN
+#define GPO_CROND "cron"
+#else
+#define GPO_CROND "crond"
+#endif
+#define GPO_POLKIT "polkit-1"
+#define GPO_SUDO "sudo"
+#define GPO_SUDO_I "sudo-i"
+#define GPO_SYSTEMD_USER "systemd-user"
+#define GPO_COCKPIT "cockpit"
+
+struct gpo_map_option_entry {
+ enum gpo_map_type gpo_map_type;
+ enum ad_basic_opt ad_basic_opt;
+ const char **gpo_map_defaults;
+ const char *allow_key;
+ const char *deny_key;
+};
+
+const char *gpo_map_interactive_defaults[] =
+ {GPO_LOGIN, GPO_SU, GPO_SU_L,
+ GPO_GDM_FINGERPRINT, GPO_GDM_PASSWORD, GPO_GDM_SMARTCARD, GPO_KDM,
+ GPO_LIGHTDM, GPO_LXDM, GPO_SDDM, GPO_UNITY, GPO_XDM, NULL};
+const char *gpo_map_remote_interactive_defaults[] = {GPO_SSHD, GPO_COCKPIT,
+ NULL};
+const char *gpo_map_network_defaults[] = {GPO_FTP, GPO_SAMBA, NULL};
+const char *gpo_map_batch_defaults[] = {GPO_CROND, NULL};
+const char *gpo_map_service_defaults[] = {NULL};
+const char *gpo_map_permit_defaults[] = {GPO_POLKIT,
+ GPO_SUDO, GPO_SUDO_I,
+ GPO_SYSTEMD_USER, NULL};
+const char *gpo_map_deny_defaults[] = {NULL};
+
+struct gpo_map_option_entry gpo_map_option_entries[] = {
+ {GPO_MAP_INTERACTIVE, AD_GPO_MAP_INTERACTIVE, gpo_map_interactive_defaults,
+ ALLOW_LOGON_INTERACTIVE, DENY_LOGON_INTERACTIVE},
+ {GPO_MAP_REMOTE_INTERACTIVE, AD_GPO_MAP_REMOTE_INTERACTIVE,
+ gpo_map_remote_interactive_defaults,
+ ALLOW_LOGON_REMOTE_INTERACTIVE, DENY_LOGON_REMOTE_INTERACTIVE},
+ {GPO_MAP_NETWORK, AD_GPO_MAP_NETWORK, gpo_map_network_defaults,
+ ALLOW_LOGON_NETWORK, DENY_LOGON_NETWORK},
+ {GPO_MAP_BATCH, AD_GPO_MAP_BATCH, gpo_map_batch_defaults,
+ ALLOW_LOGON_BATCH, DENY_LOGON_BATCH},
+ {GPO_MAP_SERVICE, AD_GPO_MAP_SERVICE, gpo_map_service_defaults,
+ ALLOW_LOGON_SERVICE, DENY_LOGON_SERVICE},
+ {GPO_MAP_PERMIT, AD_GPO_MAP_PERMIT, gpo_map_permit_defaults, NULL, NULL},
+ {GPO_MAP_DENY, AD_GPO_MAP_DENY, gpo_map_deny_defaults, NULL, NULL},
+};
+
+static const char* gpo_map_type_string(int gpo_map_type)
+{
+ switch(gpo_map_type) {
+ case GPO_MAP_INTERACTIVE: return "Interactive";
+ case GPO_MAP_REMOTE_INTERACTIVE: return "Remote Interactive";
+ case GPO_MAP_NETWORK: return "Network";
+ case GPO_MAP_BATCH: return "Batch";
+ case GPO_MAP_SERVICE: return "Service";
+ case GPO_MAP_PERMIT: return "Permitted";
+ case GPO_MAP_DENY: return "Denied";
+ }
+ return "-unknown-"; /* this helper is only used in logs */
+}
+
+static inline bool
+ad_gpo_service_in_list(char **list, size_t nlist, const char *str)
+{
+ size_t i;
+
+ for (i = 0; i < nlist; i++) {
+ if (strcasecmp(list[i], str) == 0) {
+ break;
+ }
+ }
+
+ return (i < nlist) ? true : false;
+}
+
+errno_t
+ad_gpo_parse_map_option_helper(enum gpo_map_type gpo_map_type,
+ hash_key_t key,
+ hash_table_t *options_table)
+{
+ hash_value_t val;
+ int hret;
+ int ret;
+
+ hret = hash_lookup(options_table, &key, &val);
+ if (hret != HASH_SUCCESS && hret != HASH_ERROR_KEY_NOT_FOUND) {
+ DEBUG(SSSDBG_OP_FAILURE, "Error checking hash table: [%s]\n",
+ hash_error_string(hret));
+ ret = EINVAL;
+ goto done;
+ } else if (hret == HASH_SUCCESS) {
+ /* handle unexpected case where mapping for key already exists */
+ if (val.i == gpo_map_type) {
+ /* mapping for key exists for same map type; no error */
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "PAM service %s maps to %s multiple times\n", key.str,
+ gpo_map_type_string(gpo_map_type));
+ ret = EOK;
+ } else {
+ /* mapping for key exists for different map type; error! */
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Configuration error: PAM service %s maps to both %s and "
+ "%s. If you are changing the default mappings of Group "
+ "Policy rules to PAM services using one of the ad_gpo_map_*"
+ " options make sure that the PAM service you add to one map "
+ "using the '+service' syntax is not already present in "
+ "another map by default (if it is then remove it from the "
+ "other map by using the '-service' syntax. Check manual "
+ "pages 'man sssd-ad' for details).\n", key.str,
+ gpo_map_type_string(val.i), gpo_map_type_string(gpo_map_type));
+ sss_log(SSS_LOG_ERR,
+ "Configuration error: PAM service %s maps to both %s and "
+ "%s. If you are changing the default mappings of Group "
+ "Policy rules to PAM services using one of the ad_gpo_map_*"
+ " options make sure that the PAM service you add to one map "
+ "using the '+service' syntax is not already present in "
+ "another map by default (if it is then remove it from the "
+ "other map by using the '-service' syntax. Check manual "
+ "pages 'man sssd-ad' for details).\n", key.str,
+ gpo_map_type_string(val.i), gpo_map_type_string(gpo_map_type));
+ ret = EINVAL;
+ }
+ goto done;
+ } else {
+ /* handle expected case where mapping for key doesn't already exist */
+ val.type = HASH_VALUE_INT;
+ val.i = gpo_map_type;
+
+ hret = hash_enter(options_table, &key, &val);
+ if (hret != HASH_SUCCESS) {
+ DEBUG(SSSDBG_OP_FAILURE, "Error checking hash table: [%s]\n",
+ hash_error_string(hret));
+ ret = EIO;
+ goto done;
+ }
+ ret = EOK;
+ }
+
+done:
+ return ret;
+}
+
+errno_t
+ad_gpo_parse_map_option(TALLOC_CTX *mem_ctx,
+ enum gpo_map_type gpo_map_type,
+ hash_table_t *options_table,
+ char *conf_str,
+ const char **defaults)
+{
+ TALLOC_CTX *tmp_ctx;
+ errno_t ret;
+ char **conf_list = NULL;
+ int conf_list_size = 0;
+ char **add_list = NULL;
+ char **remove_list = NULL;
+ int ai = 0, ri = 0;
+ int i;
+ hash_key_t key;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "gpo_map_type: %s\n",
+ gpo_map_type_string(gpo_map_type));
+
+ if (conf_str) {
+ ret = split_on_separator(tmp_ctx, conf_str, ',', true, true,
+ &conf_list, &conf_list_size);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Cannot parse list of service names %s: %d\n", conf_str, ret);
+ ret = EINVAL;
+ goto done;
+ }
+
+ add_list = talloc_zero_array(tmp_ctx, char *, conf_list_size);
+ remove_list = talloc_zero_array(tmp_ctx, char *, conf_list_size);
+ if (add_list == NULL || remove_list == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ for (i = 0; i < conf_list_size; i++) {
+ switch (conf_list[i][0]) {
+ case '+':
+ add_list[ai] = conf_list[i] + 1;
+ ai++;
+ continue;
+ case '-':
+ remove_list[ri] = conf_list[i] + 1;
+ ri++;
+ continue;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE, "ad_gpo_map values must start with"
+ "either '+' (for adding service) or '-' (for removing service), "
+ "got '%s'\n",
+ conf_list[i]);
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ /* Start by adding explicitly added services ('+') to hashtable */
+ for (i = 0; i < ai; i++) {
+ /* if the service is explicitly configured to be removed, skip it */
+ if (ad_gpo_service_in_list(remove_list, ri, add_list[i])) {
+ continue;
+ }
+
+ key.type = HASH_KEY_STRING;
+ key.str = (char *)add_list[i];
+
+ ret = ad_gpo_parse_map_option_helper(gpo_map_type, key, options_table);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Invalid configuration: %d\n", ret);
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "Explicitly added service: %s\n", key.str);
+ }
+
+ /* Add defaults to hashtable */
+ for (i = 0; defaults[i]; i++) {
+ /* if the service is explicitly configured to be removed, skip it */
+ if (ad_gpo_service_in_list(remove_list, ri, defaults[i])) {
+ continue;
+ }
+
+ key.type = HASH_KEY_STRING;
+ key.str = talloc_strdup(mem_ctx, defaults[i]);
+
+ ret = ad_gpo_parse_map_option_helper(gpo_map_type, key, options_table);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Invalid configuration: %d\n", ret);
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "Default service (not explicitly removed): %s\n",
+ key.str);
+ }
+
+ ret = EOK;
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+errno_t
+ad_gpo_parse_map_options(struct ad_access_ctx *access_ctx)
+{
+ char *gpo_default_right_config;
+ enum gpo_map_type gpo_default_right;
+ errno_t ret;
+ int i;
+
+ for (i = 0; i < GPO_MAP_NUM_OPTS; i++) {
+
+ struct gpo_map_option_entry entry = gpo_map_option_entries[i];
+
+ char *entry_config = dp_opt_get_string(access_ctx->ad_options,
+ entry.ad_basic_opt);
+
+ ret = ad_gpo_parse_map_option(access_ctx, entry.gpo_map_type,
+ access_ctx->gpo_map_options_table,
+ entry_config, entry.gpo_map_defaults);
+
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Invalid configuration: %d\n", ret);
+ ret = EINVAL;
+ goto fail;
+ }
+ }
+
+ /* default right (applicable for services without any mapping) */
+ gpo_default_right_config =
+ dp_opt_get_string(access_ctx->ad_options, AD_GPO_DEFAULT_RIGHT);
+
+ DEBUG(SSSDBG_TRACE_ALL, "gpo_default_right_config: %s\n",
+ gpo_default_right_config);
+
+ /* if default right not set in config, set them to DENY */
+ if (gpo_default_right_config == NULL) {
+ gpo_default_right = GPO_MAP_DENY;
+ } else if (strncasecmp(gpo_default_right_config, "interactive",
+ strlen("interactive")) == 0) {
+ gpo_default_right = GPO_MAP_INTERACTIVE;
+ } else if (strncasecmp(gpo_default_right_config, "remote_interactive",
+ strlen("remote_interactive")) == 0) {
+ gpo_default_right = GPO_MAP_REMOTE_INTERACTIVE;
+ } else if (strncasecmp(gpo_default_right_config, "network",
+ strlen("network")) == 0) {
+ gpo_default_right = GPO_MAP_NETWORK;
+ } else if (strncasecmp(gpo_default_right_config, "batch",
+ strlen("batch")) == 0) {
+ gpo_default_right = GPO_MAP_BATCH;
+ } else if (strncasecmp(gpo_default_right_config, "service",
+ strlen("service")) == 0) {
+ gpo_default_right = GPO_MAP_SERVICE;
+ } else if (strncasecmp(gpo_default_right_config, "permit",
+ strlen("permit")) == 0) {
+ gpo_default_right = GPO_MAP_PERMIT;
+ } else if (strncasecmp(gpo_default_right_config, "deny",
+ strlen("deny")) == 0) {
+ gpo_default_right = GPO_MAP_DENY;
+ } else {
+ ret = EINVAL;
+ goto fail;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "gpo_default_right: %d\n", gpo_default_right);
+ access_ctx->gpo_default_right = gpo_default_right;
+
+fail:
+ return ret;
+}
+
+/* == ad_gpo_access_send/recv helpers =======================================*/
+
+static bool
+ad_gpo_dom_sid_equal(const struct dom_sid *sid1, const struct dom_sid *sid2)
+{
+ int i;
+
+ if (sid1 == sid2) {
+ return true;
+ }
+
+ if (!sid1 || !sid2) {
+ return false;
+ }
+
+ if (sid1->sid_rev_num != sid2->sid_rev_num) {
+ return false;
+ }
+
+ for (i = 0; i < 6; i++) {
+ if (sid1->id_auth[i] != sid2->id_auth[i]) {
+ return false;
+ }
+ }
+
+ if (sid1->num_auths != sid2->num_auths) {
+ return false;
+ }
+
+ for (i = 0; i < sid1->num_auths; i++) {
+ if (sid1->sub_auths[i] != sid2->sub_auths[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * This function retrieves the SID of the group with given gid.
+ */
+static char *
+ad_gpo_get_primary_group_sid(TALLOC_CTX *mem_ctx,
+ gid_t gid,
+ struct sss_domain_info *domain,
+ struct sss_idmap_ctx *idmap_ctx)
+{
+ char *idmap_sid = NULL;
+ const char *cache_sid;
+ char *result;
+ const char *attrs[] = {
+ SYSDB_SID_STR,
+ NULL
+ };
+ struct ldb_message *msg;
+ int ret;
+
+ if (gid == 0) {
+ return NULL;
+ }
+
+ ret = sss_idmap_unix_to_sid(idmap_ctx, gid, &idmap_sid);
+ if (ret == EOK) {
+ result = talloc_strdup(mem_ctx, idmap_sid);
+ sss_idmap_free_sid(idmap_ctx, idmap_sid);
+ if (result == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "Out of memory while getting SID of the group\n");
+ }
+ return result;
+ }
+
+ if (ret == IDMAP_EXTERNAL) {
+ /* no ID mapping in this domain, search for the group object and get sid there */
+ ret = sysdb_search_group_by_gid(mem_ctx, domain, gid, attrs, &msg);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Search for group '%"SPRIgid"' failded with error '%d'\n", gid, ret);
+ return NULL;
+ }
+
+ cache_sid = ldb_msg_find_attr_as_string(msg, SYSDB_SID_STR, NULL);
+ if (cache_sid == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "Failed to get SID attribute of the group '%"SPRIgid"'\n", gid);
+ return NULL;
+ }
+
+ result = talloc_strdup(mem_ctx, cache_sid);
+ if (result == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "Out of memory while getting group SID\n");
+ }
+ return result;
+ }
+
+ DEBUG(SSSDBG_OP_FAILURE, "Failed to get SID of primary the group '%"SPRIgid"'\n", gid);
+ return NULL;
+}
+
+/*
+ * This function retrieves the SIDs corresponding to the input user and returns
+ * the user_sid, group_sids, and group_size in their respective output params.
+ *
+ * Note: since authentication must complete successfully before the
+ * gpo access checks are called, we can safely assume that the user/computer
+ * has been authenticated. As such, this function always adds the
+ * AD_AUTHENTICATED_USERS_SID to the group_sids.
+ */
+static errno_t
+ad_gpo_get_sids(TALLOC_CTX *mem_ctx,
+ const char *user,
+ struct sss_domain_info *domain,
+ struct sss_idmap_ctx *idmap_ctx,
+ const char **_user_sid,
+ const char ***_group_sids,
+ int *_group_size)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct ldb_result *res;
+ int ret = 0;
+ int i = 0;
+ int num_group_sids = 0;
+ const char *user_sid = NULL;
+ const char *group_sid = NULL;
+ const char **group_sids = NULL;
+ gid_t orig_gid = 0;
+ char *orig_gid_sid = NULL;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* first result from sysdb_initgroups is user_sid; rest are group_sids */
+ ret = sysdb_initgroups(tmp_ctx, domain, user, &res);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sysdb_initgroups failed: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ if (res->count == 0) {
+ ret = ENOENT;
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sysdb_initgroups returned empty result\n");
+ goto done;
+ }
+
+ user_sid = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_SID_STR, NULL);
+
+ /* if there is origPrimaryGroupGidNumber, it's SID must be added to list */
+ orig_gid = ldb_msg_find_attr_as_uint64(res->msgs[0],
+ SYSDB_PRIMARY_GROUP_GIDNUM,
+ 0);
+ orig_gid_sid = ad_gpo_get_primary_group_sid(tmp_ctx,
+ orig_gid,
+ domain,
+ idmap_ctx);
+ DEBUG(SSSDBG_TRACE_INTERNAL, "SID of the primary group with gid '%"SPRIgid"' is '%s'\n", orig_gid, orig_gid_sid);
+
+ num_group_sids = (res->count) - 1;
+
+ /* include space for AD_AUTHENTICATED_USERS_SID, original GID sid and NULL */
+ group_sids = talloc_array(tmp_ctx, const char *, num_group_sids + 3);
+ if (group_sids == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ for (i = 0; i < num_group_sids; i++) {
+ group_sid = ldb_msg_find_attr_as_string(res->msgs[i+1],
+ SYSDB_SID_STR, NULL);
+ if (group_sid == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Missing SID for cache entry [%s].\n",
+ ldb_dn_get_linearized(res->msgs[i+1]->dn));
+ ret = EINVAL;
+ goto done;
+ }
+
+ group_sids[i] = talloc_steal(group_sids, group_sid);
+ if (group_sids[i] == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+ group_sids[i++] = talloc_strdup(group_sids, AD_AUTHENTICATED_USERS_SID);
+ if (orig_gid_sid != NULL) {
+ group_sids[i++] = orig_gid_sid;
+ }
+ group_sids[i] = NULL;
+
+ *_group_size = i;
+ *_group_sids = talloc_steal(mem_ctx, group_sids);
+ *_user_sid = talloc_steal(mem_ctx, user_sid);
+ ret = EOK;
+
+ done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ * This function determines whether the input ace_dom_sid matches any of the
+ * client's SIDs. The boolean result is assigned to the _included output param.
+ */
+static errno_t
+ad_gpo_ace_includes_client_sid(const char *user_sid,
+ const char *host_sid,
+ const char **group_sids,
+ int group_size,
+ const char **host_group_sids,
+ int host_group_size,
+ struct dom_sid ace_dom_sid,
+ struct sss_idmap_ctx *idmap_ctx,
+ bool *_included)
+{
+ int i = 0;
+ struct dom_sid *user_dom_sid;
+ struct dom_sid *host_dom_sid;
+ struct dom_sid *group_dom_sid;
+ enum idmap_error_code err;
+ bool included = false;
+
+ err = sss_idmap_sid_to_smb_sid(idmap_ctx, user_sid, &user_dom_sid);
+ if (err != IDMAP_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sss_idmap_sid_to_smb_sid() failed for user_sid '%s': %d\n",
+ user_sid, err);
+ return EFAULT;
+ }
+
+ included = ad_gpo_dom_sid_equal(&ace_dom_sid, user_dom_sid);
+ sss_idmap_free_smb_sid(idmap_ctx, user_dom_sid);
+ if (included) {
+ *_included = true;
+ return EOK;
+ }
+
+ err = sss_idmap_sid_to_smb_sid(idmap_ctx, host_sid, &host_dom_sid);
+ if (err != IDMAP_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sss_idmap_sid_to_smb_sid() failed for host_sid '%s': %d\n",
+ host_sid, err);
+ return EFAULT;
+ }
+
+ included = ad_gpo_dom_sid_equal(&ace_dom_sid, host_dom_sid);
+ sss_idmap_free_smb_sid(idmap_ctx, host_dom_sid);
+ if (included) {
+ *_included = true;
+ return EOK;
+ }
+
+ for (i = 0; i < group_size; i++) {
+ err = sss_idmap_sid_to_smb_sid(idmap_ctx, group_sids[i], &group_dom_sid);
+ if (err != IDMAP_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sss_idmap_sid_to_smb_sid() failed for group_sid '%s': %d\n",
+ group_sids[i], err);
+ return EFAULT;
+ }
+ included = ad_gpo_dom_sid_equal(&ace_dom_sid, group_dom_sid);
+ sss_idmap_free_smb_sid(idmap_ctx, group_dom_sid);
+ if (included) {
+ *_included = true;
+ return EOK;
+ }
+ }
+
+ for (i = 0; i < host_group_size; i++) {
+ err = sss_idmap_sid_to_smb_sid(idmap_ctx, host_group_sids[i], &group_dom_sid);
+ if (err != IDMAP_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sss_idmap_sid_to_smb_sid() failed for group_sid '%s': %d\n",
+ group_sids[i], err);
+ return EFAULT;
+ }
+ included = ad_gpo_dom_sid_equal(&ace_dom_sid, group_dom_sid);
+ sss_idmap_free_smb_sid(idmap_ctx, group_dom_sid);
+ if (included) {
+ *_included = true;
+ return EOK;
+ }
+ }
+
+ *_included = false;
+ return EOK;
+}
+
+/*
+ * This function determines whether use of the extended right named
+ * "ApplyGroupPolicy" (AGP) is allowed for the GPO, by comparing the
+ * specified user_sid and group_sids against the passed access control
+ * entry (ACE).
+ * This function returns ALLOWED, DENIED, or NEUTRAL depending on whether
+ * the ACE explicitly allows, explicitly denies, or does neither.
+ *
+ * Notes:
+ * (1) Abbreviation 'M' used in the evaluation algorithm stands for
+ * "access_mask", which represents the set of access rights associated with
+ * the passed ACE. The access right of interest to the GPO code is
+ * RIGHT_DS_CONTROL_ACCESS, which serves as a container for all control access
+ * rights. The specific control access right is identified by a GUID in the
+ * ACE's ObjectType. In our case, this is the GUID corresponding to AGP.
+ * (2) ACE that require an evaluation algorithm different from [MS-ADTS]
+ * 5.1.3.3.4, e. g. RIGHT_DS_CONTROL_ACCESS (CR) is not present in M, are
+ * ignored.
+ *
+ * The ACE evaluation algorithm is specified in [MS-ADTS] 5.1.3.3.4:
+ * Evaluate the DACL by examining each ACE in sequence, starting with the first
+ * ACE. Perform the following sequence of actions for each ACE in the order as
+ * shown:
+ * 1. If the "Inherit Only" (IO) flag is set in the ACE, skip the ACE.
+ * 2. If the SID in the ACE does not match any SID in the requester's
+ * security context, skip the ACE.
+ * 3. If the ACE type is "Object Access Allowed", the access right
+ * RIGHT_DS_CONTROL_ACCESS (CR) is present in M, and the ObjectType
+ * field in the ACE is not present, then grant the requested control
+ * access right. Stop any further access checks.
+ * 4. If the ACE type is "Object Access Allowed" the access right
+ * RIGHT_DS_CONTROL_ACCESS (CR) is present in M, and the ObjectType
+ * field in the ACE contains a GUID value equal to AGP, then grant
+ * the requested control access right. Stop any further access checks.
+ * 5. If the ACE type is "Object Access Denied", the access right
+ * RIGHT_DS_CONTROL_ACCESS (CR) is present in M, and the ObjectType
+ * field in the ACE is not present, then deny the requested control
+ * access right. Stop any further access checks.
+ * 6. If the ACE type is "Object Access Denied" the access right
+ * RIGHT_DS_CONTROL_ACCESS (CR) is present in M, and the ObjectType
+ * field in the ACE contains a GUID value equal to AGP, then deny
+ * the requested control access right. Stop any further access checks.
+ */
+static enum ace_eval_agp_status ad_gpo_evaluate_ace(struct security_ace *ace,
+ struct sss_idmap_ctx *idmap_ctx,
+ const char *user_sid,
+ const char *host_sid,
+ const char **group_sids,
+ int group_size,
+ const char **host_group_sids,
+ int host_group_size)
+{
+ bool included = false;
+ int ret = 0;
+ struct security_ace_object object;
+ struct GUID ext_right_agp_guid;
+
+ if (ace->flags & SEC_ACE_FLAG_INHERIT_ONLY) {
+ return AD_GPO_ACE_NEUTRAL;
+ }
+
+ ret = ad_gpo_ace_includes_client_sid(user_sid, host_sid, group_sids,
+ group_size, host_group_sids,
+ host_group_size, ace->trustee,
+ idmap_ctx, &included);
+ if (ret != EOK) {
+ return AD_GPO_ACE_DENIED;
+ }
+
+ if (!included) {
+ return AD_GPO_ACE_NEUTRAL;
+ }
+
+ if (ace->access_mask & SEC_ADS_CONTROL_ACCESS) {
+ object = ace->object.object;
+ if (object.flags & SEC_ACE_OBJECT_TYPE_PRESENT) {
+ GUID_from_string(AD_AGP_GUID, &ext_right_agp_guid);
+ if (!GUID_equal(&object.type.type, &ext_right_agp_guid)) {
+ return AD_GPO_ACE_NEUTRAL;
+ }
+ }
+ if (ace->type == SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT) {
+ return AD_GPO_ACE_ALLOWED;
+ } else if (ace->type == SEC_ACE_TYPE_ACCESS_DENIED_OBJECT) {
+ return AD_GPO_ACE_DENIED;
+ }
+ }
+
+ return AD_GPO_ACE_NEUTRAL;
+}
+
+/*
+ * This function evaluates, which standard access rights the passed access
+ * control entry (ACE) allows or denies for the entire GPO.
+ *
+ * Notes:
+ * (1) Abbreviation 'M' used in the evaluation algorithm stands for
+ * "access_mask", which represents the set of access rights associated with
+ * the passed ACE.
+ * (2) Abbreviation 'G' used in the evaluation algorithm stands for
+ * "granted rights", which represents the set of access rights, that
+ * have already been granted by previously evaluated ACEs.
+ * (3) Abbreviation 'D' used in the evaluation algorithm stands for
+ * "denied rights", which represents the set of access rights, that
+ * have already been explicitly denied by previously evaluated ACEs.
+ *
+ * The simple ACE evaluation algorithm is specified in [MS-ADTS] 5.1.3.3.2:
+ * Evaluate the DACL by examining each ACE in sequence, starting with the first
+ * ACE. Perform the following sequence of actions for each ACE in the order as
+ * shown:
+ * 1. If the "Inherit Only" (IO) flag is set in the ACE, skip the ACE.
+ * 2. If the SID in the ACE does not match any SID in the requester's
+ * security context, skip the ACE.
+ * 3. If the ACE type is "Access Denied" and the access rights in M
+ * are not in G, then add the rights in M to D.
+ * 4. If the ACE type is "Access Allowed" and the access rights in M
+ * are not in D, then add the rights in M to G.
+ */
+static errno_t ad_gpo_simple_evaluate_ace(struct security_ace *ace,
+ struct sss_idmap_ctx *idmap_ctx,
+ const char *user_sid,
+ const char *host_sid,
+ const char **group_sids,
+ int group_size,
+ const char **host_group_sids,
+ int host_group_size,
+ uint32_t *_gpo_access_granted_status,
+ uint32_t *_gpo_access_denied_status)
+{
+ bool included = false;
+ uint32_t filtered_access_rights = 0;
+ int ret = 0;
+
+ if (ace->flags & SEC_ACE_FLAG_INHERIT_ONLY) {
+ return EOK;
+ }
+
+ ret = ad_gpo_ace_includes_client_sid(user_sid, host_sid, group_sids, group_size,
+ host_group_sids, host_group_size,
+ ace->trustee, idmap_ctx, &included);
+
+ if (ret != EOK || !included) {
+ return ret;
+ }
+
+ if (ace->type == SEC_ACE_TYPE_ACCESS_DENIED) {
+ filtered_access_rights = ace->access_mask & ~*_gpo_access_granted_status;
+ *_gpo_access_denied_status |= filtered_access_rights;
+ } else if (ace->type == SEC_ACE_TYPE_ACCESS_ALLOWED) {
+ filtered_access_rights = ace->access_mask & ~*_gpo_access_denied_status;
+ *_gpo_access_granted_status |= filtered_access_rights;
+ }
+
+ return ret;
+}
+
+
+/*
+ * This function extracts the GPO's DACL (discretionary access control list)
+ * from the GPO's specified security descriptor, and determines whether
+ * the GPO is applicable to the policy target, by comparing the specified
+ * user_sid and group_sids against each access control entry (ACE) in the DACL.
+ * The GPO is only applicable to the target, if the requester has been granted
+ * read access (RIGHT_DS_READ_PROPERTY) to the properties of the GPO and
+ * control access (RIGHT_DS_CONTROL_ACCESS) to apply the GPO (AGP).
+ * The required read and control access rights for a particular trustee are
+ * usually located in different ACEs, i.e. one ACE for control of read access
+ * and one for control access.
+ * If it comes to the end of the DACL, and the required access is still not
+ * explicitly allowed or denied, SSSD denies access to the object as specified
+ * in [MS-ADTS] 5.1.3.1.
+ */
+static errno_t ad_gpo_evaluate_dacl(struct security_acl *dacl,
+ struct sss_idmap_ctx *idmap_ctx,
+ const char *user_sid,
+ const char *host_sid,
+ const char **group_sids,
+ int group_size,
+ const char **host_group_sids,
+ int host_group_size,
+ bool *_dacl_access_allowed)
+{
+ uint32_t num_aces = 0;
+ uint32_t access_granted_status = 0;
+ uint32_t access_denied_status = 0;
+ enum ace_eval_agp_status ace_status;
+ struct security_ace *ace = NULL;
+ int i = 0;
+ int ret = 0;
+ enum idmap_error_code err;
+ char *trustee_dom_sid_str = NULL;
+
+ num_aces = dacl->num_aces;
+
+ /*
+ * [MS-ADTS] 5.1.3.3.2. and 5.1.3.3.4:
+ * If the DACL does not have any ACE, then deny the requester the
+ * requested control access right.
+ */
+ if (num_aces == 0) {
+ *_dacl_access_allowed = false;
+ return EOK;
+ }
+
+ /*
+ * [MS-GOPD] 2.4:
+ * To process a policy that applies to a Group Policy client, the core
+ * Group Policy engine must be able to read the policy data from the
+ * directory service so that the policy settings can be applied to the
+ * Group Policy client or the interactive user.
+ */
+ for (i = 0; i < dacl->num_aces; i++) {
+ ace = &dacl->aces[i];
+
+ ret = ad_gpo_simple_evaluate_ace(ace, idmap_ctx, user_sid, host_sid,
+ group_sids, group_size,
+ host_group_sids, host_group_size,
+ &access_granted_status,
+ &access_denied_status);
+
+ if (ret != EOK) {
+ err = sss_idmap_smb_sid_to_sid(idmap_ctx, &ace->trustee,
+ &trustee_dom_sid_str);
+ if (err != IDMAP_SUCCESS) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sss_idmap_smb_sid_to_sid failed.\n");
+ return EFAULT;
+ }
+
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Could not determine if ACE is applicable; "
+ " Trustee: %s\n", trustee_dom_sid_str);
+ sss_idmap_free_sid(idmap_ctx, trustee_dom_sid_str);
+ trustee_dom_sid_str = NULL;
+ continue;
+ }
+ }
+
+ for (i = 0; i < dacl->num_aces; i ++) {
+ ace = &dacl->aces[i];
+
+ err = sss_idmap_smb_sid_to_sid(idmap_ctx, &ace->trustee,
+ &trustee_dom_sid_str);
+ if (err != IDMAP_SUCCESS) {
+ DEBUG(SSSDBG_OP_FAILURE, "sss_idmap_smb_sid_to_sid failed.\n");
+ return EFAULT;
+ }
+
+ ace_status = ad_gpo_evaluate_ace(ace, idmap_ctx, user_sid, host_sid,
+ group_sids, group_size,
+ host_group_sids, host_group_size);
+
+ switch (ace_status) {
+ case AD_GPO_ACE_NEUTRAL:
+ break;
+ case AD_GPO_ACE_ALLOWED:
+ if (access_granted_status & SEC_ADS_READ_PROP) {
+ *_dacl_access_allowed = true;
+ sss_idmap_free_sid(idmap_ctx, trustee_dom_sid_str);
+ return EOK;
+ } else {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "GPO read properties access denied (security); "
+ " Trustee: %s\n", trustee_dom_sid_str);
+ break;
+ }
+ case AD_GPO_ACE_DENIED:
+ if (access_granted_status & SEC_ADS_READ_PROP) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "GPO denied (security); "
+ " Trustee: %s\n", trustee_dom_sid_str);
+ sss_idmap_free_sid(idmap_ctx, trustee_dom_sid_str);
+ *_dacl_access_allowed = false;
+ return EOK;
+ } else {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "GPO read properties access denied (security); "
+ " Trustee: %s\n", trustee_dom_sid_str);
+ break;
+ }
+ }
+ sss_idmap_free_sid(idmap_ctx, trustee_dom_sid_str);
+ trustee_dom_sid_str = NULL;
+ }
+
+ if (access_granted_status & SEC_ADS_READ_PROP) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "GPO apply group policy access denied (security)\n");
+ }
+
+ *_dacl_access_allowed = false;
+ return EOK;
+}
+
+/*
+ * This function takes candidate_gpos as input, filters out any gpo that is
+ * not applicable to the policy target and assigns the result to the
+ * _dacl_filtered_gpos output parameter. The filtering algorithm is
+ * defined in [MS-GPOL] 3.2.5.1.6
+ */
+static errno_t
+ad_gpo_filter_gpos_by_dacl(TALLOC_CTX *mem_ctx,
+ const char *user,
+ const char *host_fqdn,
+ struct sss_domain_info *domain,
+ struct sss_domain_info *host_domain,
+ struct sss_idmap_ctx *idmap_ctx,
+ struct gp_gpo **candidate_gpos,
+ int num_candidate_gpos,
+ struct gp_gpo ***_dacl_filtered_gpos,
+ int *_num_dacl_filtered_gpos)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ int i = 0;
+ int ret = 0;
+ struct gp_gpo *candidate_gpo = NULL;
+ struct security_descriptor *sd = NULL;
+ struct security_acl *dacl = NULL;
+ const char *user_sid = NULL;
+ const char **group_sids = NULL;
+ int group_size = 0;
+ const char *host_sid = NULL;
+ const char **host_group_sids = NULL;
+ int host_group_size = 0;
+ int gpo_dn_idx = 0;
+ bool access_allowed = false;
+ struct gp_gpo **dacl_filtered_gpos = NULL;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = ad_gpo_get_sids(tmp_ctx, user, domain, idmap_ctx, &user_sid,
+ &group_sids, &group_size);
+ if (ret != EOK) {
+ ret = ERR_NO_SIDS;
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to retrieve SIDs: [%d](%s)\n", ret, sss_strerror(ret));
+ goto done;
+ }
+
+ ret = ad_gpo_get_sids(tmp_ctx, host_fqdn, host_domain, idmap_ctx, &host_sid,
+ &host_group_sids, &host_group_size);
+ if (ret != EOK) {
+ ret = ERR_NO_SIDS;
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to retrieve host SIDs: [%d](%s)\n", ret, sss_strerror(ret));
+ goto done;
+ }
+
+ dacl_filtered_gpos = talloc_array(tmp_ctx,
+ struct gp_gpo *,
+ num_candidate_gpos + 1);
+
+ if (dacl_filtered_gpos == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ for (i = 0; i < num_candidate_gpos; i++) {
+
+ access_allowed = false;
+ candidate_gpo = candidate_gpos[i];
+
+ DEBUG(SSSDBG_TRACE_FUNC, "examining dacl candidate_gpo_guid:%s\n",
+ candidate_gpo->gpo_guid);
+
+ /* gpo_func_version must be set to version 2 */
+ if (candidate_gpo->gpo_func_version != 2) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "GPO not applicable to target per security filtering: "
+ "gPCFunctionalityVersion is not 2\n");
+ continue;
+ }
+
+ sd = candidate_gpo->gpo_sd;
+ if (sd == NULL) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Security descriptor is missing\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ dacl = candidate_gpo->gpo_sd->dacl;
+
+ /* gpo_flags value of 2 means that GPO's computer portion is disabled */
+ if (candidate_gpo->gpo_flags == 2) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "GPO not applicable to target per security filtering: "
+ "GPO's computer portion is disabled\n");
+ continue;
+ }
+
+ if ((sd->type & SEC_DESC_DACL_PRESENT) && (dacl != NULL)) {
+ ret = ad_gpo_evaluate_dacl(dacl, idmap_ctx, user_sid, host_sid,
+ group_sids, group_size, host_group_sids,
+ host_group_size, &access_allowed);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Could not determine if GPO is applicable\n");
+ continue;
+ }
+ } else {
+ /*
+ * [MS-ADTS] 5.1.3.3.4:
+ * If the security descriptor has no DACL or its "DACL Present" bit
+ * is not set, then grant requester the requested control access right.
+ */
+
+ DEBUG(SSSDBG_TRACE_ALL, "DACL is not present\n");
+ access_allowed = true;
+ }
+
+ if (access_allowed) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "GPO applicable to target per security filtering\n");
+ dacl_filtered_gpos[gpo_dn_idx] = talloc_steal(dacl_filtered_gpos,
+ candidate_gpo);
+ gpo_dn_idx++;
+ } else {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "GPO not applicable to target per security filtering: "
+ "result of DACL evaluation\n");
+ continue;
+ }
+ }
+
+ dacl_filtered_gpos[gpo_dn_idx] = NULL;
+
+ *_dacl_filtered_gpos = talloc_steal(mem_ctx, dacl_filtered_gpos);
+ *_num_dacl_filtered_gpos = gpo_dn_idx;
+
+ ret = EOK;
+
+ done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ * This function determines whether the input cse_guid matches any of the input
+ * gpo_cse_guids. The boolean result is assigned to the _included output param.
+ */
+static bool
+ad_gpo_includes_cse_guid(const char *cse_guid,
+ const char **gpo_cse_guids,
+ int num_gpo_cse_guids)
+{
+ int i = 0;
+ const char *gpo_cse_guid = NULL;
+
+ for (i = 0; i < num_gpo_cse_guids; i++) {
+ gpo_cse_guid = gpo_cse_guids[i];
+ if (strcmp(gpo_cse_guid, cse_guid) == 0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * This function takes an input dacl_filtered_gpos list, filters out any gpo
+ * that does not contain the input cse_guid, and assigns the result to the
+ * _cse_filtered_gpos output parameter.
+ */
+static errno_t
+ad_gpo_filter_gpos_by_cse_guid(TALLOC_CTX *mem_ctx,
+ const char *cse_guid,
+ struct gp_gpo **dacl_filtered_gpos,
+ int num_dacl_filtered_gpos,
+ struct gp_gpo ***_cse_filtered_gpos,
+ int *_num_cse_filtered_gpos)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ int i = 0;
+ int ret = 0;
+ struct gp_gpo *dacl_filtered_gpo = NULL;
+ int gpo_dn_idx = 0;
+ struct gp_gpo **cse_filtered_gpos = NULL;
+ bool included;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ cse_filtered_gpos = talloc_array(tmp_ctx,
+ struct gp_gpo *,
+ num_dacl_filtered_gpos + 1);
+ if (cse_filtered_gpos == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ for (i = 0; i < num_dacl_filtered_gpos; i++) {
+
+ dacl_filtered_gpo = dacl_filtered_gpos[i];
+
+ DEBUG(SSSDBG_TRACE_ALL, "examining cse candidate_gpo_guid: %s\n",
+ dacl_filtered_gpo->gpo_guid);
+
+ included = ad_gpo_includes_cse_guid(cse_guid,
+ dacl_filtered_gpo->gpo_cse_guids,
+ dacl_filtered_gpo->num_gpo_cse_guids);
+
+ if (included) {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "GPO applicable to target per cse_guid filtering\n");
+ cse_filtered_gpos[gpo_dn_idx] = talloc_steal(cse_filtered_gpos,
+ dacl_filtered_gpo);
+ dacl_filtered_gpos[i] = NULL;
+ gpo_dn_idx++;
+ } else {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "GPO not applicable to target per cse_guid filtering\n");
+ continue;
+ }
+ }
+
+ cse_filtered_gpos[gpo_dn_idx] = NULL;
+
+ *_cse_filtered_gpos = talloc_steal(mem_ctx, cse_filtered_gpos);
+ *_num_cse_filtered_gpos = gpo_dn_idx;
+
+ ret = EOK;
+
+ done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ * This cse-specific function (GP_EXT_GUID_SECURITY) returns a boolean value
+ * based on whether the input user_sid or any of the input group_sids appear
+ * in the input list of privilege_sids.
+ */
+static bool
+check_rights(char **privilege_sids,
+ int privilege_size,
+ const char *user_sid,
+ const char **group_sids,
+ int group_size)
+{
+ int i, j;
+
+ for (i = 0; i < privilege_size; i++) {
+ if (strcmp(user_sid, privilege_sids[i]) == 0) {
+ return true;
+ }
+ for (j = 0; j < group_size; j++) {
+ if (strcmp(group_sids[j], privilege_sids[i]) == 0) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * This function parses the input ini_config object (which represents
+ * the cse-specific filename), and returns the policy_setting_value
+ * corresponding to the input policy_setting_key.
+ */
+static errno_t
+ad_gpo_extract_policy_setting(TALLOC_CTX *mem_ctx,
+ struct ini_cfgobj *ini_config,
+ const char *policy_setting_key,
+ char **_policy_setting_value)
+{
+ struct value_obj *vobj = NULL;
+ int ret;
+ const char *policy_setting_value;
+
+ ret = ini_get_config_valueobj(RIGHTS_SECTION, policy_setting_key, ini_config,
+ INI_GET_FIRST_VALUE, &vobj);
+ if (ret != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "ini_get_config_valueobj failed [%d][%s]\n", ret, strerror(ret));
+ goto done;
+ }
+ if (vobj == NULL) {
+ DEBUG(SSSDBG_TRACE_ALL, "section/name not found: [%s][%s]\n",
+ RIGHTS_SECTION, policy_setting_key);
+ ret = ENOENT;
+ goto done;
+ }
+ policy_setting_value = ini_get_string_config_value(vobj, &ret);
+ if (ret != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "ini_get_string_config_value failed [%d][%s]\n",
+ ret, strerror(ret));
+ goto done;
+ }
+
+ if (policy_setting_value[0]) {
+ *_policy_setting_value = talloc_strdup(mem_ctx, policy_setting_value);
+ if (!*_policy_setting_value) {
+ ret = ENOMEM;
+ goto done;
+ }
+ } else {
+ /* This is an explicitly empty policy setting.
+ * We need to remove this from the LDB.
+ */
+ *_policy_setting_value = NULL;
+ }
+
+ ret = EOK;
+
+ done:
+
+ return ret;
+}
+
+/*
+ * This function parses the cse-specific (GP_EXT_GUID_SECURITY) filename,
+ * and stores the allow_key and deny_key of all of the gpo_map_types present
+ * in the file (as part of the GPO Result object in the sysdb cache).
+ */
+static errno_t
+ad_gpo_store_policy_settings(struct sss_domain_info *domain,
+ const char *filename)
+{
+ struct ini_cfgfile *file_ctx = NULL;
+ struct ini_cfgobj *ini_config = NULL;
+ int ret;
+ int i;
+ char *allow_value = NULL;
+ char *deny_value = NULL;
+ const char *empty_val = "NO_SID";
+ const char *allow_key = NULL;
+ const char *deny_key = NULL;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = ini_config_create(&ini_config);
+ if (ret != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "ini_config_create failed [%d][%s]\n", ret, strerror(ret));
+ goto done;
+ }
+
+ ret = ini_config_file_open(filename, 0, &file_ctx);
+ if (ret != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "ini_config_file_open failed [%d][%s]\n", ret, strerror(ret));
+ goto done;
+ }
+
+ ret = ini_config_parse(file_ctx, INI_STOP_ON_NONE, 0, 0, ini_config);
+ if (ret != 0) {
+ int lret;
+ char **errors;
+
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "[%s]: ini_config_parse failed [%d][%s]\n",
+ filename, ret, strerror(ret));
+
+ /* Now get specific errors if there are any */
+ lret = ini_config_get_errors(ini_config, &errors);
+ if (lret != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to get specific parse error [%d][%s]\n", lret,
+ strerror(lret));
+ goto done;
+ }
+
+ for (int a = 0; errors[a]; a++) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "%s\n", errors[a]);
+ }
+ ini_config_free_errors(errors);
+
+ /* Do not 'goto done' here. We will try to parse
+ * the GPO file again. */
+ }
+
+ if (ret != EOK) {
+ /* A problem occurred during parsing. Try again
+ * with INI_PARSE_IGNORE_NON_KVP flag */
+
+ ini_config_file_destroy(file_ctx);
+ file_ctx = NULL;
+ ini_config_destroy(ini_config);
+ ini_config = NULL;
+
+ ret = ini_config_file_open(filename, 0, &file_ctx);
+ if (ret != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "ini_config_file_open failed [%d][%s]\n",
+ ret, strerror(ret));
+ goto done;
+ }
+
+ ret = ini_config_create(&ini_config);
+ if (ret != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "ini_config_create failed [%d][%s]\n", ret, strerror(ret));
+ goto done;
+ }
+
+ ret = ini_config_parse(file_ctx, INI_STOP_ON_NONE, 0,
+ INI_PARSE_IGNORE_NON_KVP, ini_config);
+ if (ret != 0) {
+ int lret;
+ char **errors;
+
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "[%s]: ini_config_parse failed [%d][%s]\n",
+ filename, ret, strerror(ret));
+
+ /* Now get specific errors if there are any */
+ lret = ini_config_get_errors(ini_config, &errors);
+ if (lret != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to get specific parse error [%d][%s]\n", lret,
+ strerror(lret));
+ goto done;
+ }
+
+ for (int a = 0; errors[a]; a++) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "%s\n", errors[a]);
+ }
+ ini_config_free_errors(errors);
+
+ goto done;
+ }
+ }
+
+ for (i = 0; i < GPO_MAP_NUM_OPTS; i++) {
+ /* The NO_SID val is used as special SID value for the case when
+ * no SIDs are found in the rule, but we need to store some
+ * value (SID) with the key (rule name) so that it is clear
+ * that the rule is defined on the server. */
+ struct gpo_map_option_entry entry = gpo_map_option_entries[i];
+
+ allow_key = entry.allow_key;
+ if (allow_key != NULL) {
+ DEBUG(SSSDBG_TRACE_ALL, "allow_key = %s\n", allow_key);
+ ret = ad_gpo_extract_policy_setting(tmp_ctx,
+ ini_config,
+ allow_key,
+ &allow_value);
+ if (ret != EOK && ret != ENOENT) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "ad_gpo_extract_policy_setting failed for %s [%d][%s]\n",
+ allow_key, ret, sss_strerror(ret));
+ goto done;
+ } else if (ret != ENOENT) {
+ const char *value = allow_value ? allow_value : empty_val;
+ ret = sysdb_gpo_store_gpo_result_setting(domain,
+ allow_key,
+ value);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sysdb_gpo_store_gpo_result_setting failed for key:"
+ "'%s' value:'%s' [%d][%s]\n", allow_key, allow_value,
+ ret, sss_strerror(ret));
+ goto done;
+ }
+ }
+ }
+
+ deny_key = entry.deny_key;
+ if (deny_key != NULL) {
+ DEBUG(SSSDBG_TRACE_ALL, "deny_key = %s\n", deny_key);
+ ret = ad_gpo_extract_policy_setting(tmp_ctx,
+ ini_config,
+ deny_key,
+ &deny_value);
+ if (ret != EOK && ret != ENOENT) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "ad_gpo_extract_policy_setting failed for %s [%d][%s]\n",
+ deny_key, ret, sss_strerror(ret));
+ goto done;
+ } else if (ret != ENOENT) {
+ const char *value = deny_value ? deny_value : empty_val;
+ ret = sysdb_gpo_store_gpo_result_setting(domain,
+ deny_key,
+ value);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sysdb_gpo_store_gpo_result_setting failed for key:"
+ "'%s' value:'%s' [%d][%s]\n", deny_key, deny_value,
+ ret, sss_strerror(ret));
+ goto done;
+ }
+ }
+ }
+ }
+
+ ret = EOK;
+
+ done:
+
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Error encountered: %d.\n", ret);
+ }
+ ini_config_file_destroy(file_ctx);
+ ini_config_destroy(ini_config);
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ * This cse-specific function (GP_EXT_GUID_SECURITY) performs the access
+ * check for determining whether logon access is granted or denied for
+ * the {user,domain} tuple specified in the inputs. This function returns EOK
+ * to indicate that access is granted. Any other return value indicates that
+ * access is denied.
+ *
+ * The access control algorithm first determines whether the "principal_sids"
+ * (i.e. user_sid or group_sids) appear in allowed_sids and denied_sids.
+ *
+ * For access to be granted, both the "allowed_sids_condition" *and* the
+ * "denied_sids_condition" must be met (in all other cases, access is denied).
+ * 1) The "allowed_sids_condition" is satisfied if any of the principal_sids
+ * appears in allowed_sids OR if the allowed_sids list is empty
+ * 2) The "denied_sids_condition" is satisfied if none of the principal_sids
+ * appear in denied_sids
+ *
+ * Note that a deployment that is unaware of GPO-based access-control policy
+ * settings is unaffected by them (b/c absence of allowed_sids grants access).
+ *
+ * Note that if a principal_sid appears in both allowed_sids and denied_sids,
+ * the "allowed_sids_condition" is met, but the "denied_sids_condition" is not.
+ * In other words, Deny takes precedence over Allow.
+ */
+static errno_t
+ad_gpo_access_check(TALLOC_CTX *mem_ctx,
+ enum gpo_access_control_mode gpo_mode,
+ enum gpo_map_type gpo_map_type,
+ const char *user,
+ bool gpo_implicit_deny,
+ struct sss_domain_info *domain,
+ struct sss_idmap_ctx *idmap_ctx,
+ char **allowed_sids,
+ int allowed_size,
+ char **denied_sids,
+ int denied_size)
+{
+ const char *user_sid;
+ const char **group_sids;
+ int group_size = 0;
+ bool access_granted = false;
+ bool access_denied = false;
+ int ret;
+ int j;
+
+ DEBUG(SSSDBG_TRACE_FUNC, "RESULTANT POLICY:\n");
+ DEBUG(SSSDBG_TRACE_FUNC, "gpo_map_type: %s\n",
+ gpo_map_type_string(gpo_map_type));
+ DEBUG(SSSDBG_TRACE_FUNC, "allowed_size = %d\n", allowed_size);
+ for (j= 0; j < allowed_size; j++) {
+ DEBUG(SSSDBG_TRACE_FUNC, "allowed_sids[%d] = %s\n", j, allowed_sids[j]);
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "denied_size = %d\n", denied_size);
+ for (j= 0; j < denied_size; j++) {
+ DEBUG(SSSDBG_TRACE_FUNC, " denied_sids[%d] = %s\n", j, denied_sids[j]);
+ }
+
+ ret = ad_gpo_get_sids(mem_ctx, user, domain, idmap_ctx, &user_sid,
+ &group_sids, &group_size);
+ if (ret != EOK) {
+ ret = ERR_NO_SIDS;
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to retrieve SIDs: [%d](%s)\n", ret, sss_strerror(ret));
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "CURRENT USER:\n");
+ DEBUG(SSSDBG_TRACE_FUNC, " user_sid = %s\n", user_sid);
+
+ for (j= 0; j < group_size; j++) {
+ DEBUG(SSSDBG_TRACE_FUNC, " group_sids[%d] = %s\n", j,
+ group_sids[j]);
+ }
+
+ if (allowed_size == 0 && !gpo_implicit_deny) {
+ access_granted = true;
+ } else {
+ access_granted = check_rights(allowed_sids, allowed_size, user_sid,
+ group_sids, group_size);
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "POLICY DECISION:\n");
+
+ DEBUG(SSSDBG_TRACE_FUNC, " access_granted = %d\n", access_granted);
+
+ access_denied = check_rights(denied_sids, denied_size, user_sid,
+ group_sids, group_size);
+ DEBUG(SSSDBG_TRACE_FUNC, " access_denied = %d\n", access_denied);
+
+ if (access_granted && !access_denied) {
+ return EOK;
+ } else {
+ switch (gpo_mode) {
+ case GPO_ACCESS_CONTROL_ENFORCING:
+ return ERR_ACCESS_DENIED;
+ case GPO_ACCESS_CONTROL_PERMISSIVE:
+ DEBUG(SSSDBG_TRACE_FUNC, "access denied: permissive mode\n");
+ sss_log_ext(SSS_LOG_WARNING, LOG_AUTHPRIV, "Warning: user would " \
+ "have been denied GPO-based logon access if the " \
+ "ad_gpo_access_control option were set to enforcing " \
+ "mode.");
+ return EOK;
+ default:
+ return EINVAL;
+ }
+ }
+
+ done:
+
+ if (ret) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Error encountered: %d.\n", ret);
+ }
+
+ return ret;
+}
+
+/*
+ * This function retrieves the raw policy_setting_value for the input key from
+ * the GPO_Result object in the sysdb cache. It then parses the raw value and
+ * uses the results to populate the output parameters with the sids_list and
+ * the size of the sids_list.
+ */
+errno_t
+parse_policy_setting_value(TALLOC_CTX *mem_ctx,
+ struct sss_domain_info *domain,
+ const char *key,
+ char ***_sids_list,
+ int *_sids_list_size)
+{
+ int ret;
+ int i;
+ const char *value;
+ int sids_list_size;
+ char **sids_list = NULL;
+
+ ret = sysdb_gpo_get_gpo_result_setting(mem_ctx, domain, key, &value);
+ if (ret == ENOENT) {
+ DEBUG(SSSDBG_TRACE_FUNC, "No previous GPO result\n");
+ value = NULL;
+ } else if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Cannot retrieve settings from sysdb for key: '%s' [%d][%s].\n",
+ key, ret, sss_strerror(ret));
+ goto done;
+ }
+
+ if (value == NULL) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "No value for key [%s] found in gpo result\n", key);
+ sids_list_size = 0;
+ } else {
+ ret = split_on_separator(mem_ctx, value, ',', true, true,
+ &sids_list, &sids_list_size);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Cannot parse list of sids %s: %d\n", value, ret);
+ ret = EINVAL;
+ goto done;
+ }
+
+ for (i = 0; i < sids_list_size; i++) {
+ /* remove the asterisk prefix found on sids */
+ sids_list[i]++;
+ }
+ }
+
+ *_sids_list = talloc_steal(mem_ctx, sids_list);
+ *_sids_list_size = sids_list_size;
+
+ ret = EOK;
+
+ done:
+ return ret;
+}
+
+/*
+ * This cse-specific function (GP_EXT_GUID_SECURITY) performs HBAC policy
+ * processing and determines whether logon access is granted or denied for
+ * the {user,domain} tuple specified in the inputs. This function returns EOK
+ * to indicate that access is granted. Any other return value indicates that
+ * access is denied.
+ *
+ * Internally, this function retrieves the allow_value and deny_value for the
+ * input gpo_map_type from the GPO Result object in the sysdb cache, parses
+ * the values into allow_sids and deny_sids, and executes the access control
+ * algorithm which compares the allow_sids and deny_sids against the user_sid
+ * and group_sids for the input user.
+ */
+static errno_t
+ad_gpo_perform_hbac_processing(TALLOC_CTX *mem_ctx,
+ enum gpo_access_control_mode gpo_mode,
+ enum gpo_map_type gpo_map_type,
+ const char *user,
+ bool gpo_implicit_deny,
+ struct sss_domain_info *user_domain,
+ struct sss_domain_info *host_domain,
+ struct sss_idmap_ctx *idmap_ctx)
+{
+ int ret;
+ const char *allow_key = NULL;
+ char **allow_sids;
+ int allow_size ;
+ const char *deny_key = NULL;
+ char **deny_sids;
+ int deny_size;
+
+ allow_key = gpo_map_option_entries[gpo_map_type].allow_key;
+ DEBUG(SSSDBG_TRACE_ALL, "allow_key: %s\n", allow_key);
+ deny_key = gpo_map_option_entries[gpo_map_type].deny_key;
+ DEBUG(SSSDBG_TRACE_ALL, "deny_key: %s\n", deny_key);
+
+ ret = parse_policy_setting_value(mem_ctx, host_domain, allow_key,
+ &allow_sids, &allow_size);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "parse_policy_setting_value failed for key %s: [%d](%s)\n",
+ allow_key, ret, sss_strerror(ret));
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = parse_policy_setting_value(mem_ctx, host_domain, deny_key,
+ &deny_sids, &deny_size);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "parse_policy_setting_value failed for key %s: [%d](%s)\n",
+ deny_key, ret, sss_strerror(ret));
+ ret = EINVAL;
+ goto done;
+ }
+
+ /* perform access check with the final resultant allow_sids and deny_sids */
+ ret = ad_gpo_access_check(mem_ctx, gpo_mode, gpo_map_type, user,
+ gpo_implicit_deny, user_domain, idmap_ctx,
+ allow_sids, allow_size, deny_sids, deny_size);
+
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "GPO access check failed: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ done:
+ return ret;
+}
+
+/* == ad_gpo_access_send/recv implementation ================================*/
+
+struct ad_gpo_access_state {
+ struct tevent_context *ev;
+ struct ldb_context *ldb_ctx;
+ struct ad_access_ctx *access_ctx;
+ enum gpo_access_control_mode gpo_mode;
+ bool gpo_implicit_deny;
+ enum gpo_map_type gpo_map_type;
+ struct sdap_id_conn_ctx *conn;
+ struct sdap_id_op *sdap_op;
+ char *server_hostname;
+ struct sdap_options *opts;
+ int timeout;
+ struct sss_domain_info *user_domain;
+ struct sss_domain_info *host_domain;
+ const char *host_sam_account_name;
+ char *host_fqdn;
+ const char *user;
+ int gpo_timeout_option;
+ const char *ad_hostname;
+ const char *host_sid;
+ const char *target_dn;
+ struct gp_gpo **dacl_filtered_gpos;
+ int num_dacl_filtered_gpos;
+ struct gp_gpo **cse_filtered_gpos;
+ int num_cse_filtered_gpos;
+ int cse_gpo_index;
+ const char *ad_domain;
+};
+
+static void ad_gpo_connect_done(struct tevent_req *subreq);
+static void ad_gpo_target_dn_retrieval_done(struct tevent_req *subreq);
+static void ad_gpo_process_som_done(struct tevent_req *subreq);
+static void ad_gpo_process_gpo_done(struct tevent_req *subreq);
+
+static errno_t ad_gpo_cse_step(struct tevent_req *req);
+static void ad_gpo_cse_done(struct tevent_req *subreq);
+
+struct tevent_req *
+ad_gpo_access_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *domain,
+ struct ad_access_ctx *ctx,
+ const char *user,
+ const char *service)
+{
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct ad_gpo_access_state *state;
+ errno_t ret;
+ int hret;
+ hash_key_t key;
+ hash_value_t val;
+ enum gpo_map_type gpo_map_type;
+
+ req = tevent_req_create(mem_ctx, &state, struct ad_gpo_access_state);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n");
+ return NULL;
+ }
+
+ /* determine service's option_type (e.g. interactive, network, etc) */
+ key.type = HASH_KEY_STRING;
+ key.str = talloc_strdup(state, service);
+
+ hret = hash_lookup(ctx->gpo_map_options_table, &key, &val);
+ if (hret != HASH_SUCCESS && hret != HASH_ERROR_KEY_NOT_FOUND) {
+ DEBUG(SSSDBG_OP_FAILURE, "Error checking hash table: [%s]\n",
+ hash_error_string(hret));
+ ret = EINVAL;
+ goto immediately;
+ }
+
+ /* if service isn't mapped, map it to value of ad_gpo_default_right option */
+ if (hret == HASH_ERROR_KEY_NOT_FOUND) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Configuration hint: PAM service '%s' is not mapped to any Group"
+ " Policy rule. If you plan to use this PAM service it is "
+ "recommended to use the ad_gpo_map_* family of options to map "
+ "this PAM service to a Group Policy rule. PAM services not "
+ "present in any map will fall back to value set in "
+ "ad_gpo_default_right, which is currently set to %s (see manual "
+ "pages 'man sssd-ad' for more details).\n", service,
+ gpo_map_type_string(ctx->gpo_default_right));
+ gpo_map_type = ctx->gpo_default_right;
+ } else {
+ gpo_map_type = (enum gpo_map_type) val.i;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "service %s maps to %s\n", service,
+ gpo_map_type_string(gpo_map_type));
+
+ if (gpo_map_type == GPO_MAP_PERMIT) {
+ ret = EOK;
+ goto immediately;
+ }
+
+ if (gpo_map_type == GPO_MAP_DENY) {
+ switch (ctx->gpo_access_control_mode) {
+ case GPO_ACCESS_CONTROL_ENFORCING:
+ ret = ERR_ACCESS_DENIED;
+ goto immediately;
+ case GPO_ACCESS_CONTROL_PERMISSIVE:
+ DEBUG(SSSDBG_TRACE_FUNC, "access denied: permissive mode\n");
+ sss_log_ext(SSS_LOG_WARNING, LOG_AUTHPRIV, "Warning: user would " \
+ "have been denied GPO-based logon access if the " \
+ "ad_gpo_access_control option were set to enforcing " \
+ "mode.");
+ ret = EOK;
+ goto immediately;
+ default:
+ ret = EINVAL;
+ goto immediately;
+ }
+ }
+
+ /* GPO Operations all happen against the enrolled domain,
+ * not the user's domain (which may be a trusted realm)
+ */
+ state->user_domain = domain;
+ state->host_domain = get_domains_head(domain);
+ state->ad_domain = dp_opt_get_string(ctx->ad_id_ctx->ad_options->basic,
+ AD_DOMAIN);
+
+ state->gpo_map_type = gpo_map_type;
+ state->dacl_filtered_gpos = NULL;
+ state->num_dacl_filtered_gpos = 0;
+ state->cse_filtered_gpos = NULL;
+ state->num_cse_filtered_gpos = 0;
+ state->cse_gpo_index = 0;
+ state->ev = ev;
+ state->user = user;
+ state->ldb_ctx = sysdb_ctx_get_ldb(state->host_domain->sysdb);
+ state->gpo_mode = ctx->gpo_access_control_mode;
+ state->gpo_timeout_option = ctx->gpo_cache_timeout;
+ state->ad_hostname = dp_opt_get_string(ctx->ad_options, AD_HOSTNAME);
+ state->gpo_implicit_deny = dp_opt_get_bool(ctx->ad_options,
+ AD_GPO_IMPLICIT_DENY);
+ state->access_ctx = ctx;
+ state->opts = ctx->sdap_access_ctx->id_ctx->opts;
+ state->timeout = dp_opt_get_int(state->opts->basic, SDAP_SEARCH_TIMEOUT);
+ state->conn = ad_get_dom_ldap_conn(ctx->ad_id_ctx, state->host_domain);
+ state->sdap_op = sdap_id_op_create(state, state->conn->conn_cache);
+ if (state->sdap_op == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed.\n");
+ ret = ENOMEM;
+ goto immediately;
+ }
+
+
+ subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sdap_id_op_connect_send failed: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto immediately;
+ }
+ tevent_req_set_callback(subreq, ad_gpo_connect_done, req);
+
+ 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
+process_offline_gpos(TALLOC_CTX *mem_ctx,
+ const char *user,
+ bool gpo_implicit_deny,
+ enum gpo_access_control_mode gpo_mode,
+ struct sss_domain_info *user_domain,
+ struct sss_domain_info *host_domain,
+ struct sss_idmap_ctx *idmap_ctx,
+ enum gpo_map_type gpo_map_type)
+
+{
+ errno_t ret;
+
+ ret = ad_gpo_perform_hbac_processing(mem_ctx,
+ gpo_mode,
+ gpo_map_type,
+ user,
+ gpo_implicit_deny,
+ user_domain,
+ host_domain,
+ idmap_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "HBAC processing failed: [%d](%s}\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ /* we have successfully processed all offline gpos */
+ ret = EOK;
+
+ done:
+ return ret;
+}
+
+static void
+ad_gpo_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct ad_gpo_access_state *state;
+ int dp_error;
+ errno_t ret;
+ char *server_uri;
+ LDAPURLDesc *lud;
+ struct sdap_domain *sdom;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct ad_gpo_access_state);
+
+ ret = sdap_id_op_connect_recv(subreq, &dp_error);
+ talloc_zfree(subreq);
+
+ if (ret != EOK) {
+ if (dp_error != DP_ERR_OFFLINE) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Failed to connect to AD server: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ } else {
+ DEBUG(SSSDBG_TRACE_FUNC, "Preparing for offline operation.\n");
+ ret = process_offline_gpos(state,
+ state->user,
+ state->gpo_implicit_deny,
+ state->gpo_mode,
+ state->user_domain,
+ state->host_domain,
+ state->opts->idmap_ctx->map,
+ state->gpo_map_type);
+
+ if (ret == EOK) {
+ DEBUG(SSSDBG_TRACE_FUNC, "process_offline_gpos succeeded\n");
+ tevent_req_done(req);
+ goto done;
+ } else {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "process_offline_gpos failed [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+ }
+ }
+
+ /* extract server_hostname from server_uri */
+ server_uri = state->conn->service->uri;
+ ret = ldap_url_parse(server_uri, &lud);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to parse ldap URI (%s)!\n", server_uri);
+ ret = EINVAL;
+ goto done;
+ }
+
+ if (lud->lud_host == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "The LDAP URI (%s) did not contain a host name\n", server_uri);
+ ldap_free_urldesc(lud);
+ ret = EINVAL;
+ goto done;
+ }
+
+ state->server_hostname = talloc_strdup(state, lud->lud_host);
+ ldap_free_urldesc(lud);
+ if (!state->server_hostname) {
+ ret = ENOMEM;
+ goto done;
+ }
+ DEBUG(SSSDBG_TRACE_ALL, "server_hostname from uri: %s\n",
+ state->server_hostname);
+
+ /* SDAP_SASL_AUTHID contains the name used for kinit and SASL bind which
+ * in the AD case is the NetBIOS name. */
+ state->host_sam_account_name = dp_opt_get_string(state->opts->basic,
+ SDAP_SASL_AUTHID);
+ if (state->host_sam_account_name == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "sam_account_name is %s\n",
+ state->host_sam_account_name);
+
+ state->host_fqdn = sss_create_internal_fqname(state, state->host_sam_account_name,
+ state->host_domain->name);
+ if (state->host_fqdn == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Failed to create fully-qualified host name.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* AD handle computers the same as users */
+ sdom = sdap_domain_get(state->access_ctx->ad_id_ctx->sdap_id_ctx->opts,
+ state->host_domain);
+ if (sdom == NULL) {
+ ret = EIO;
+ goto done;
+ }
+
+ subreq = groups_by_user_send(state, state->ev,
+ state->access_ctx->ad_id_ctx->sdap_id_ctx,
+ sdom, state->conn,
+ state->host_fqdn,
+ BE_FILTER_NAME,
+ NULL,
+ true,
+ true);
+ tevent_req_set_callback(subreq, ad_gpo_target_dn_retrieval_done, req);
+
+ ret = EOK;
+
+ done:
+
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ }
+}
+
+static void
+ad_gpo_target_dn_retrieval_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct ad_gpo_access_state *state;
+ int ret;
+ int dp_error;
+ int sdap_ret;
+ const char *target_dn = NULL;
+ uint32_t uac;
+ static const char *host_attrs[] = { SYSDB_ORIG_DN, SYSDB_AD_USER_ACCOUNT_CONTROL, SYSDB_SID_STR, NULL };
+ struct ldb_result *res = NULL;
+ const char *tmp = NULL;
+ char *endptr;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct ad_gpo_access_state);
+ ret = groups_by_user_recv(subreq, &dp_error, &sdap_ret);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ if (sdap_ret == EAGAIN && dp_error == DP_ERR_OFFLINE) {
+ DEBUG(SSSDBG_TRACE_FUNC, "Preparing for offline operation.\n");
+ ret = process_offline_gpos(state,
+ state->user,
+ state->gpo_implicit_deny,
+ state->gpo_mode,
+ state->user_domain,
+ state->host_domain,
+ state->opts->idmap_ctx->map,
+ state->gpo_map_type);
+
+ if (ret == EOK) {
+ DEBUG(SSSDBG_TRACE_FUNC, "process_offline_gpos succeeded\n");
+ tevent_req_done(req);
+ goto done;
+ } else {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "process_offline_gpos failed [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+ }
+
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to get policy target's DN: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ ret = ENOENT;
+ goto done;
+ }
+
+ ret = sysdb_get_user_attr(state, state->host_domain,
+ state->host_fqdn,
+ host_attrs, &res);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Failed to read host attributes.\n");
+ goto done;
+ }
+ if (res->count != 1) {
+ DEBUG(SSSDBG_OP_FAILURE, "Unexpected number [%d] of results searching "
+ "for [%s], expected 1.\n", res->count,
+ state->host_sam_account_name);
+ ret = EINVAL;
+ goto done;
+ }
+
+ target_dn = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_ORIG_DN, NULL);
+ if (target_dn == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "ldb_msg_find_attr_as_string failed: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+ state->target_dn = talloc_steal(state, target_dn);
+ if (state->target_dn == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tmp = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_AD_USER_ACCOUNT_CONTROL,
+ NULL);
+ if (tmp == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "ldb_msg_find_attr_as_string failed: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ uac = strtouint32(tmp, &endptr, 10);
+ if (errno != 0) {
+ ret = errno;
+ DEBUG(SSSDBG_OP_FAILURE, "Failed to convert UAC [%s] into uint32_t.\n",
+ tmp);
+ goto done;
+ }
+ if (*endptr != '\0') {
+ ret = EINVAL;
+ DEBUG(SSSDBG_OP_FAILURE, "UAC [%s] is not a pure numerical value.\n",
+ tmp);
+ goto done;
+ }
+
+ /* we only support computer policy targets, not users */
+ if (!(uac & UAC_WORKSTATION_TRUST_ACCOUNT ||
+ uac & UAC_SERVER_TRUST_ACCOUNT)) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Invalid userAccountControl (%x) value for machine account.\n",
+ uac);
+ ret = EINVAL;
+ goto done;
+ }
+
+ state->host_sid = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_SID_STR,
+ NULL);
+ talloc_steal(state, state->host_sid);
+
+ subreq = ad_gpo_process_som_send(state,
+ state->ev,
+ state->conn,
+ state->ldb_ctx,
+ state->sdap_op,
+ state->opts,
+ state->access_ctx->ad_options,
+ state->timeout,
+ state->target_dn,
+ state->ad_domain);
+ if (subreq == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, ad_gpo_process_som_done, req);
+
+ ret = EOK;
+
+ done:
+
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ }
+
+}
+
+static void
+ad_gpo_process_som_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct ad_gpo_access_state *state;
+ int ret;
+ struct gp_som **som_list;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct ad_gpo_access_state);
+ ret = ad_gpo_process_som_recv(subreq, state, &som_list);
+ talloc_zfree(subreq);
+
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to get som list: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ ret = ENOENT;
+ goto done;
+ }
+
+ subreq = ad_gpo_process_gpo_send(state,
+ state->ev,
+ state->sdap_op,
+ state->opts,
+ state->server_hostname,
+ state->host_domain,
+ state->access_ctx,
+ state->timeout,
+ som_list);
+ if (subreq == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, ad_gpo_process_gpo_done, req);
+
+ ret = EOK;
+
+ done:
+
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ }
+}
+
+/*
+ * This function retrieves a list of candidate_gpos and potentially reduces it
+ * to a list of dacl_filtered_gpos, based on each GPO's DACL.
+ *
+ * This function then takes the list of dacl_filtered_gpos and potentially
+ * reduces it to a list of cse_filtered_gpos, based on whether each GPO's list
+ * of cse_guids includes the "SecuritySettings" CSE GUID (used for HBAC).
+ *
+ * Ultimately, this function then sends each cse_filtered_gpo to the gpo_child,
+ * which retrieves the GPT.INI and policy files (as needed). Once all files
+ * have been downloaded, the ad_gpo_cse_done function performs HBAC processing.
+ */
+static void
+ad_gpo_process_gpo_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct ad_gpo_access_state *state;
+ int ret;
+ int dp_error;
+ struct gp_gpo **candidate_gpos = NULL;
+ int num_candidate_gpos = 0;
+ int i = 0;
+ const char **cse_filtered_gpo_guids;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct ad_gpo_access_state);
+ ret = ad_gpo_process_gpo_recv(subreq, state, &candidate_gpos,
+ &num_candidate_gpos);
+
+ talloc_zfree(subreq);
+
+ ret = sdap_id_op_done(state->sdap_op, ret, &dp_error);
+
+ if (ret != EOK && ret != ENOENT) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to get GPO list from server %s: [%d](%s)\n",
+ state->ad_hostname ? state->ad_hostname : "NULL", ret, sss_strerror(ret));
+ goto done;
+ } else if (ret == ENOENT) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "No GPOs found that apply to this system.\n");
+ /*
+ * Delete the result object list, since there are no
+ * GPOs to include in it.
+ */
+ ret = sysdb_gpo_delete_gpo_result_object(state, state->host_domain);
+ if (ret != EOK) {
+ switch (ret) {
+ case ENOENT:
+ DEBUG(SSSDBG_TRACE_FUNC, "No GPO Result available in cache\n");
+ break;
+ default:
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Could not delete GPO Result from cache: [%s]\n",
+ sss_strerror(ret));
+ goto done;
+ }
+ }
+
+ if (state->gpo_implicit_deny == true) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "No applicable GPOs have been found and ad_gpo_implicit_deny"
+ " is set to 'true'. The user will be denied access.\n");
+ ret = ERR_ACCESS_DENIED;
+ } else {
+ ret = EOK;
+ }
+
+ goto done;
+ }
+
+ ret = ad_gpo_filter_gpos_by_dacl(state, state->user, state->host_fqdn,
+ state->user_domain,
+ state->host_domain,
+ state->opts->idmap_ctx->map,
+ candidate_gpos, num_candidate_gpos,
+ &state->dacl_filtered_gpos,
+ &state->num_dacl_filtered_gpos);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to filter GPO list by DACL: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ if (state->dacl_filtered_gpos[0] == NULL) {
+ /* since no applicable gpos were found, there is nothing to enforce */
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "no applicable gpos found after dacl filtering\n");
+
+ /*
+ * Delete the result object list, since there are no
+ * GPOs to include in it.
+ */
+ ret = sysdb_gpo_delete_gpo_result_object(state, state->host_domain);
+ if (ret != EOK) {
+ switch (ret) {
+ case ENOENT:
+ DEBUG(SSSDBG_TRACE_FUNC, "No GPO Result available in cache\n");
+ break;
+ default:
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Could not delete GPO Result from cache: [%s]\n",
+ sss_strerror(ret));
+ goto done;
+ }
+ }
+
+ if (state->gpo_implicit_deny == true) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "No applicable GPOs have been found and ad_gpo_implicit_deny"
+ " is set to 'true'. The user will be denied access.\n");
+ ret = ERR_ACCESS_DENIED;
+ } else {
+ ret = EOK;
+ }
+
+ goto done;
+ }
+
+ for (i = 0; i < state->num_dacl_filtered_gpos; i++) {
+ DEBUG(SSSDBG_TRACE_FUNC, "dacl_filtered_gpos[%d]->gpo_guid is %s\n", i,
+ state->dacl_filtered_gpos[i]->gpo_guid);
+ }
+
+ ret = ad_gpo_filter_gpos_by_cse_guid(state,
+ GP_EXT_GUID_SECURITY,
+ state->dacl_filtered_gpos,
+ state->num_dacl_filtered_gpos,
+ &state->cse_filtered_gpos,
+ &state->num_cse_filtered_gpos);
+
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to filter GPO list by CSE_GUID: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ if (state->cse_filtered_gpos[0] == NULL) {
+ /* no gpos contain "SecuritySettings" cse_guid, nothing to enforce */
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "no applicable gpos found after cse_guid filtering\n");
+
+ if (state->gpo_implicit_deny == true) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "No applicable GPOs have been found and ad_gpo_implicit_deny"
+ " is set to 'true'. The user will be denied access.\n");
+ ret = ERR_ACCESS_DENIED;
+ } else {
+ ret = EOK;
+ }
+
+ goto done;
+ }
+
+ /* we create and populate an array of applicable gpo-guids */
+ cse_filtered_gpo_guids =
+ talloc_array(state, const char *, state->num_cse_filtered_gpos);
+ if (cse_filtered_gpo_guids == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ for (i = 0; i < state->num_cse_filtered_gpos; i++) {
+ DEBUG(SSSDBG_TRACE_FUNC, "cse_filtered_gpos[%d]->gpo_guid is %s\n", i,
+ state->cse_filtered_gpos[i]->gpo_guid);
+ cse_filtered_gpo_guids[i] = talloc_steal(cse_filtered_gpo_guids,
+ state->cse_filtered_gpos[i]->gpo_guid);
+ if (cse_filtered_gpo_guids[i] == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "num_cse_filtered_gpos: %d\n",
+ state->num_cse_filtered_gpos);
+
+ /*
+ * before we start processing each gpo, we delete the GPO Result object
+ * from the sysdb cache so that any previous policy settings are cleared;
+ * subsequent functions will add the GPO Result object (and populate it
+ * with resultant policy settings) for this policy application
+ */
+ ret = sysdb_gpo_delete_gpo_result_object(state, state->host_domain);
+ if (ret != EOK) {
+ switch (ret) {
+ case ENOENT:
+ DEBUG(SSSDBG_TRACE_FUNC, "No GPO Result available in cache\n");
+ break;
+ default:
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Could not delete GPO Result from cache: [%s]\n",
+ sss_strerror(ret));
+ goto done;
+ }
+ }
+
+ ret = ad_gpo_cse_step(req);
+
+ done:
+
+ if (ret == EOK) {
+ tevent_req_done(req);
+ } else if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ }
+}
+
+static errno_t
+ad_gpo_cse_step(struct tevent_req *req)
+{
+ struct tevent_req *subreq;
+ struct ad_gpo_access_state *state;
+ int i = 0;
+ struct ldb_result *res;
+ errno_t ret;
+ bool send_to_child = true;
+ int cached_gpt_version = 0;
+ time_t policy_file_timeout = 0;
+
+ state = tevent_req_data(req, struct ad_gpo_access_state);
+
+ struct gp_gpo *cse_filtered_gpo =
+ state->cse_filtered_gpos[state->cse_gpo_index];
+
+ /* cse_filtered_gpo is NULL after all GPO policy files have been downloaded */
+ if (cse_filtered_gpo == NULL) return EOK;
+
+ DEBUG(SSSDBG_TRACE_FUNC, "cse filtered_gpos[%d]->gpo_guid is %s\n",
+ state->cse_gpo_index, cse_filtered_gpo->gpo_guid);
+ for (i = 0; i < cse_filtered_gpo->num_gpo_cse_guids; i++) {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "cse_filtered_gpos[%d]->gpo_cse_guids[%d]->gpo_guid is %s\n",
+ state->cse_gpo_index, i, cse_filtered_gpo->gpo_cse_guids[i]);
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "smb_server: %s\n", cse_filtered_gpo->smb_server);
+ DEBUG(SSSDBG_TRACE_FUNC, "smb_share: %s\n", cse_filtered_gpo->smb_share);
+ DEBUG(SSSDBG_TRACE_FUNC, "smb_path: %s\n", cse_filtered_gpo->smb_path);
+ DEBUG(SSSDBG_TRACE_FUNC, "gpo_guid: %s\n", cse_filtered_gpo->gpo_guid);
+
+ cse_filtered_gpo->policy_filename =
+ talloc_asprintf(state,
+ GPO_CACHE_PATH"%s%s",
+ cse_filtered_gpo->smb_path,
+ GP_EXT_GUID_SECURITY_SUFFIX);
+ if (cse_filtered_gpo->policy_filename == NULL) {
+ return ENOMEM;
+ }
+
+ /* retrieve gpo cache entry; set cached_gpt_version to -1 if unavailable */
+ DEBUG(SSSDBG_TRACE_FUNC, "retrieving GPO from cache [%s]\n",
+ cse_filtered_gpo->gpo_guid);
+ ret = sysdb_gpo_get_gpo_by_guid(state,
+ state->host_domain,
+ cse_filtered_gpo->gpo_guid,
+ &res);
+ if (ret == EOK) {
+ /*
+ * Note: if the timeout is valid, then we can later avoid downloading
+ * the GPT.INI file, as well as any policy files (i.e. we don't need
+ * to interact with the gpo_child at all). However, even if the timeout
+ * is not valid, while we will have to interact with the gpo child to
+ * download the GPT.INI file, we may still be able to avoid downloading
+ * the policy files (if the cached_gpt_version is the same as the
+ * GPT.INI version). In other words, the timeout is *not* an expiration
+ * for the entire cache entry; the cached_gpt_version never expires.
+ */
+
+ cached_gpt_version = ldb_msg_find_attr_as_int(res->msgs[0],
+ SYSDB_GPO_VERSION_ATTR,
+ 0);
+
+ policy_file_timeout = ldb_msg_find_attr_as_uint64
+ (res->msgs[0], SYSDB_GPO_TIMEOUT_ATTR, 0);
+
+ if (policy_file_timeout >= time(NULL)) {
+ send_to_child = false;
+ }
+ } else if (ret == ENOENT) {
+ DEBUG(SSSDBG_TRACE_FUNC, "ENOENT\n");
+ cached_gpt_version = -1;
+ } else {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Could not read GPO from cache: [%s]\n",
+ sss_strerror(ret));
+ return ret;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "send_to_child: %d\n", send_to_child);
+ DEBUG(SSSDBG_TRACE_FUNC, "cached_gpt_version: %d\n", cached_gpt_version);
+
+ cse_filtered_gpo->send_to_child = send_to_child;
+
+ subreq = ad_gpo_process_cse_send(state,
+ state->ev,
+ send_to_child,
+ state->host_domain,
+ cse_filtered_gpo->gpo_guid,
+ cse_filtered_gpo->smb_server,
+ cse_filtered_gpo->smb_share,
+ cse_filtered_gpo->smb_path,
+ GP_EXT_GUID_SECURITY_SUFFIX,
+ cached_gpt_version,
+ state->gpo_timeout_option);
+
+ tevent_req_set_callback(subreq, ad_gpo_cse_done, req);
+ return EAGAIN;
+}
+
+/*
+ * This cse-specific function (GP_EXT_GUID_SECURITY) increments the
+ * cse_gpo_index until the policy settings for all applicable GPOs have been
+ * stored as part of the GPO Result object in the sysdb cache. Once all
+ * GPOs have been processed, this functions performs HBAC processing by
+ * comparing the resultant policy setting values in the GPO Result object
+ * with the user_sid/group_sids of interest.
+ */
+static void
+ad_gpo_cse_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct ad_gpo_access_state *state;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct ad_gpo_access_state);
+
+ struct gp_gpo *cse_filtered_gpo =
+ state->cse_filtered_gpos[state->cse_gpo_index];
+
+ const char *gpo_guid = cse_filtered_gpo->gpo_guid;
+
+ DEBUG(SSSDBG_TRACE_FUNC, "gpo_guid: %s\n", gpo_guid);
+
+ ret = ad_gpo_process_cse_recv(subreq);
+
+ talloc_zfree(subreq);
+
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Unable to retrieve policy data: [%d](%s}\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ /*
+ * now that the policy file for this gpo have been downloaded to the
+ * GPO CACHE, we store all of the supported keys present in the file
+ * (as part of the GPO Result object in the sysdb cache).
+ */
+ ret = ad_gpo_store_policy_settings(state->host_domain,
+ cse_filtered_gpo->policy_filename);
+ if (ret != EOK && ret != ENOENT) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "ad_gpo_store_policy_settings failed: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ state->cse_gpo_index++;
+ ret = ad_gpo_cse_step(req);
+
+ if (ret == EOK) {
+ /* ret is EOK only after all GPO policy files have been downloaded */
+ ret = ad_gpo_perform_hbac_processing(state,
+ state->gpo_mode,
+ state->gpo_map_type,
+ state->user,
+ state->gpo_implicit_deny,
+ state->user_domain,
+ state->host_domain,
+ state->opts->idmap_ctx->map);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "HBAC processing failed: [%d](%s}\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ }
+
+ done:
+
+ if (ret == EOK) {
+ tevent_req_done(req);
+ } else if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ }
+}
+
+errno_t
+ad_gpo_access_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+/* == ad_gpo_process_som_send/recv helpers ================================= */
+
+/*
+ * This function returns the parent of an LDAP DN
+ */
+static errno_t
+ad_gpo_parent_dn(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb_ctx,
+ const char *dn,
+ const char **_parent_dn)
+{
+ struct ldb_dn *ldb_dn;
+ struct ldb_dn *parent_ldb_dn;
+ const char *p;
+ int ret;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ldb_dn = ldb_dn_new(tmp_ctx, ldb_ctx, dn);
+ parent_ldb_dn = ldb_dn_get_parent(tmp_ctx, ldb_dn);
+ p = ldb_dn_get_linearized(parent_ldb_dn);
+
+ *_parent_dn = talloc_steal(mem_ctx, p);
+ ret = EOK;
+
+ done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ * This function populates the _som_list output parameter by parsing the input
+ * DN into a list of gp_som objects. This function essentially repeatedly
+ * appends the input DN's parent to the SOM List (if the parent starts with
+ * "OU=" or "DC="), until the first "DC=" component is reached.
+ * Example: if input DN is "CN=MyComputer,CN=Computers,OU=Sales,DC=FOO,DC=COM",
+ * then SOM List has 2 SOM entries: {[OU=Sales,DC=FOO,DC=COM], [DC=FOO, DC=COM]}
+ */
+
+static errno_t
+ad_gpo_populate_som_list(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb_ctx,
+ const char *target_dn,
+ int *_num_soms,
+ struct gp_som ***_som_list)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ int ret;
+ int rdn_count = 0;
+ int som_idx = 0;
+ struct gp_som **som_list;
+ const char *parent_dn = NULL;
+ const char *tmp_dn = NULL;
+ struct ldb_dn *ldb_target_dn;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ldb_target_dn = ldb_dn_new(tmp_ctx, ldb_ctx, target_dn);
+ if (ldb_target_dn == NULL) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ rdn_count = ldb_dn_get_comp_num(ldb_target_dn);
+ if (rdn_count == -1) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ if (rdn_count == 0) {
+ *_som_list = NULL;
+ ret = EOK;
+ goto done;
+ }
+
+ /* assume the worst-case, in which every parent is a SOM */
+ /* include space for Site SOM and NULL: rdn_count + 1 + 1 */
+ som_list = talloc_array(tmp_ctx, struct gp_som *, rdn_count + 1 + 1);
+ if (som_list == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* first, populate the OU and Domain SOMs */
+ tmp_dn = target_dn;
+ while ((ad_gpo_parent_dn(tmp_ctx, ldb_ctx, tmp_dn, &parent_dn)) == EOK) {
+
+ if ((strncasecmp(parent_dn, "OU=", strlen("OU=")) == 0) ||
+ (strncasecmp(parent_dn, "DC=", strlen("DC=")) == 0)) {
+
+ som_list[som_idx] = talloc_zero(som_list, struct gp_som);
+ if (som_list[som_idx] == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ som_list[som_idx]->som_dn = talloc_steal(som_list[som_idx],
+ parent_dn);
+ if (som_list[som_idx]->som_dn == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ som_idx++;
+ }
+
+ if (strncasecmp(parent_dn, "DC=", strlen("DC=")) == 0) {
+ break;
+ }
+ tmp_dn = parent_dn;
+ }
+
+ som_list[som_idx] = NULL;
+
+ *_num_soms = som_idx;
+ *_som_list = talloc_steal(mem_ctx, som_list);
+
+ ret = EOK;
+
+ done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ * This function populates the _gplink_list output parameter by parsing the
+ * input raw_gplink_value into an array of gp_gplink objects, each consisting of
+ * a GPO DN and bool enforced field.
+ *
+ * The raw_gplink_value is single string consisting of multiple gplink strings.
+ * The raw_gplink_value is in the following format:
+ * "[GPO_DN_1;GPLinkOptions_1]...[GPO_DN_n;GPLinkOptions_n]"
+ *
+ * Each gplink string consists of a GPO DN and a GPLinkOptions field (which
+ * indicates whether its associated GPO DN is ignored, unenforced, or enforced).
+ * If a GPO DN is flagged as ignored, it is discarded and will not be added to
+ * the _gplink_list. If the allow_enforced_only input is true, AND a GPO DN is
+ * flagged as unenforced, it will also be discarded.
+ *
+ * Example: if raw_gplink_value="[OU=Sales,DC=FOO,DC=COM;0][DC=FOO,DC=COM;2]"
+ * and allow_enforced_only=FALSE, then the output would consist of following:
+ * _gplink_list[0]: {GPO DN: "OU=Sales,DC=FOO,DC=COM", enforced: FALSE}
+ * _gplink_list[1]: {GPO DN: "DC=FOO,DC=COM", enforced: TRUE}
+ */
+static errno_t
+ad_gpo_populate_gplink_list(TALLOC_CTX *mem_ctx,
+ const char *som_dn,
+ char *raw_gplink_value,
+ struct gp_gplink ***_gplink_list,
+ bool allow_enforced_only)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ char *ptr;
+ char *first;
+ char *last;
+ char *dn;
+ char *gplink_options;
+ const char delim = ']';
+ struct gp_gplink **gplink_list;
+ int i;
+ int ret;
+ uint32_t gplink_number;
+ int gplink_count = 0;
+ int num_enabled = 0;
+
+ if (raw_gplink_value == NULL ||
+ *raw_gplink_value == '\0' ||
+ _gplink_list == NULL) {
+ return EINVAL;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "som_dn: %s\n", som_dn);
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ptr = raw_gplink_value;
+
+ while ((ptr = strchr(ptr, delim))) {
+ ptr++;
+ gplink_count++;
+ }
+
+ if (gplink_count == 0) {
+ ret = EOK;
+ goto done;
+ }
+
+ gplink_list = talloc_array(tmp_ctx, struct gp_gplink *, gplink_count + 1);
+ if (gplink_list == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ num_enabled = 0;
+ ptr = raw_gplink_value;
+ for (i = 0; i < gplink_count; i++) {
+ first = ptr + 1;
+ last = strchr(first, delim);
+ if (last == NULL) {
+ ret = EINVAL;
+ goto done;
+ }
+ *last = '\0';
+ last++;
+ dn = first;
+ if ( strncasecmp(dn, "LDAP://", 7)== 0 ) {
+ dn = dn + 7;
+ }
+ gplink_options = strchr(first, ';');
+ if (gplink_options == NULL) {
+ ret = EINVAL;
+ goto done;
+ }
+ *gplink_options = '\0';
+ gplink_options++;
+
+ gplink_number = strtouint32(gplink_options, NULL, 10);
+ if (errno != 0) {
+ ret = errno;
+ DEBUG(SSSDBG_OP_FAILURE,
+ "strtouint32 failed: [%d](%s)\n", ret, sss_strerror(ret));
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL,
+ "gplink_list[%d]: [%s; %d]\n", num_enabled, dn, gplink_number);
+
+ if ((gplink_number == 1) || (gplink_number ==3)) {
+ /* ignore flag is set */
+ DEBUG(SSSDBG_TRACE_ALL, "ignored gpo skipped\n");
+ ptr = last;
+ continue;
+ }
+
+ if (allow_enforced_only && (gplink_number == 0)) {
+ /* unenforced flag is set; only enforced gpos allowed */
+ DEBUG(SSSDBG_TRACE_ALL, "unenforced gpo skipped\n");
+ ptr = last;
+ continue;
+ }
+
+ gplink_list[num_enabled] = talloc_zero(gplink_list, struct gp_gplink);
+ if (gplink_list[num_enabled] == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ gplink_list[num_enabled]->gpo_dn =
+ talloc_strdup(gplink_list[num_enabled], dn);
+
+ if (gplink_list[num_enabled]->gpo_dn == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ if (gplink_number == 0) {
+ gplink_list[num_enabled]->enforced = 0;
+ num_enabled++;
+ } else if (gplink_number == 2) {
+ gplink_list[num_enabled]->enforced = 1;
+ num_enabled++;
+ } else {
+ ret = EINVAL;
+ goto done;
+ }
+
+ ptr = last;
+ }
+ gplink_list[num_enabled] = NULL;
+
+ *_gplink_list = talloc_steal(mem_ctx, gplink_list);
+ ret = EOK;
+
+ done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/* == ad_gpo_process_som_send/recv implementation ========================== */
+
+struct ad_gpo_process_som_state {
+ struct tevent_context *ev;
+ struct sdap_id_op *sdap_op;
+ struct sdap_options *opts;
+ struct dp_option *ad_options;
+ int timeout;
+ bool allow_enforced_only;
+ char *site_name;
+ char *site_dn;
+ struct gp_som **som_list;
+ int som_index;
+ int num_soms;
+};
+
+static void ad_gpo_site_name_retrieval_done(struct tevent_req *subreq);
+static void ad_gpo_site_dn_retrieval_done(struct tevent_req *subreq);
+static errno_t ad_gpo_get_som_attrs_step(struct tevent_req *req);
+static void ad_gpo_get_som_attrs_done(struct tevent_req *subreq);
+
+/*
+ * This function uses the input target_dn and input domain_name to populate
+ * a list of gp_som objects. Each object in this list represents a SOM
+ * associated with the target (such as OU, Domain, and Site).
+ *
+ * The inputs are used to determine the DNs of each SOM associated with the
+ * target. In turn, the SOM object DNs are used to retrieve certain LDAP
+ * attributes of each SOM object, that are parsed into an array of gp_gplink
+ * objects, essentially representing the GPOs that have been linked to each
+ * SOM object. Note that it is perfectly valid for there to be *no* GPOs
+ * linked to a SOM object.
+ */
+struct tevent_req *
+ad_gpo_process_som_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sdap_id_conn_ctx *conn,
+ struct ldb_context *ldb_ctx,
+ struct sdap_id_op *sdap_op,
+ struct sdap_options *opts,
+ struct dp_option *ad_options,
+ int timeout,
+ const char *target_dn,
+ const char *domain_name)
+{
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct ad_gpo_process_som_state *state;
+ errno_t ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct ad_gpo_process_som_state);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n");
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->sdap_op = sdap_op;
+ state->opts = opts;
+ state->ad_options = ad_options;
+ state->timeout = timeout;
+ state->som_index = 0;
+ state->allow_enforced_only = 0;
+
+ ret = ad_gpo_populate_som_list(state, ldb_ctx, target_dn,
+ &state->num_soms, &state->som_list);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to retrieve SOM List : [%d](%s)\n",
+ ret, sss_strerror(ret));
+ ret = ENOENT;
+ goto immediately;
+ }
+
+ if (state->som_list == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "target dn must have at least one parent\n");
+ ret = EINVAL;
+ goto immediately;
+ }
+
+ subreq = ad_domain_info_send(state, state->ev, conn,
+ state->sdap_op, domain_name);
+
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "ad_domain_info_send failed.\n");
+ ret = ENOMEM;
+ goto immediately;
+ }
+
+ tevent_req_set_callback(subreq, ad_gpo_site_name_retrieval_done, req);
+
+ ret = EOK;
+
+ immediately:
+
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void
+ad_gpo_site_name_retrieval_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct ad_gpo_process_som_state *state;
+ int ret;
+ char *site = NULL;
+ char *site_override = NULL;
+ const char *attrs[] = {AD_AT_CONFIG_NC, NULL};
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct ad_gpo_process_som_state);
+
+ /* gpo code only cares about the site name */
+ ret = ad_domain_info_recv(subreq, state, NULL, NULL, &site, NULL);
+ talloc_zfree(subreq);
+
+ if (ret != EOK || site == NULL) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Could not autodiscover AD site. This is not fatal if "
+ "ad_site option was set.\n");
+ }
+
+ site_override = dp_opt_get_string(state->ad_options, AD_SITE);
+ if (site_override != NULL) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Overriding autodiscovered AD site value '%s' with '%s' from "
+ "configuration.\n", site ? site : "none", site_override);
+ }
+
+ if (site == NULL && site_override == NULL) {
+ sss_log(SSS_LOG_WARNING,
+ "Could not autodiscover AD site value using DNS and ad_site "
+ "option was not set in configuration. GPO will not work. "
+ "To work around this issue you can use ad_site option in SSSD "
+ "configuration.");
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Could not autodiscover AD site value using DNS and ad_site "
+ "option was not set in configuration. GPO will not work. "
+ "To work around this issue you can use ad_site option in SSSD "
+ "configuration.\n");
+ tevent_req_error(req, ENOENT);
+ return;
+ }
+
+ state->site_name = talloc_asprintf(state, "cn=%s",
+ site_override ? site_override
+ : site);
+ if (state->site_name == NULL) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Using AD site '%s'.\n", state->site_name);
+
+ /*
+ * note: the configNC attribute is being retrieved here from the rootDSE
+ * entry. In future, since we already make an LDAP query for the rootDSE
+ * entry when LDAP connection is made, this attribute should really be
+ * retrieved at that point (see https://fedorahosted.org/sssd/ticket/2276)
+ */
+ subreq = sdap_get_generic_send(state, state->ev, state->opts,
+ sdap_id_op_handle(state->sdap_op),
+ "", LDAP_SCOPE_BASE,
+ "(objectclass=*)", attrs, NULL, 0,
+ state->timeout,
+ false);
+
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "sdap_get_generic_send failed.\n");
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ tevent_req_set_callback(subreq, ad_gpo_site_dn_retrieval_done, req);
+}
+
+static void
+ad_gpo_site_dn_retrieval_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct ad_gpo_process_som_state *state;
+ int ret;
+ int dp_error;
+ int i = 0;
+ size_t reply_count;
+ struct sysdb_attrs **reply;
+ const char *configNC;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct ad_gpo_process_som_state);
+
+ ret = sdap_get_generic_recv(subreq, state,
+ &reply_count, &reply);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ ret = sdap_id_op_done(state->sdap_op, ret, &dp_error);
+
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to get configNC: [%d](%s)\n", ret, sss_strerror(ret));
+ ret = ENOENT;
+ goto done;
+ }
+
+ /* make sure there is only one non-NULL reply returned */
+
+ if (reply_count < 1) {
+ DEBUG(SSSDBG_OP_FAILURE, "No configNC retrieved\n");
+ ret = ENOENT;
+ goto done;
+ } else if (reply_count > 1) {
+ DEBUG(SSSDBG_OP_FAILURE, "Multiple replies for configNC\n");
+ ret = ERR_INTERNAL;
+ goto done;
+ } else if (reply == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "reply_count is 1, but reply is NULL\n");
+ ret = ERR_INTERNAL;
+ goto done;
+ }
+
+ /* reply[0] holds requested attributes of single reply */
+ ret = sysdb_attrs_get_string(reply[0], AD_AT_CONFIG_NC, &configNC);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sysdb_attrs_get_string failed: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+ state->site_dn =
+ talloc_asprintf(state, "%s,cn=Sites,%s", state->site_name, configNC);
+ if (state->site_dn == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* note that space was allocated for site_dn when allocating som_list */
+ state->som_list[state->num_soms] =
+ talloc_zero(state->som_list, struct gp_som);
+ if (state->som_list[state->num_soms] == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ state->som_list[state->num_soms]->som_dn =
+ talloc_steal(state->som_list[state->num_soms], state->site_dn);
+
+ if (state->som_list[state->num_soms]->som_dn == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ state->num_soms++;
+ state->som_list[state->num_soms] = NULL;
+
+ i = 0;
+ while (state->som_list[i]) {
+ DEBUG(SSSDBG_TRACE_FUNC, "som_list[%d]->som_dn is %s\n", i,
+ state->som_list[i]->som_dn);
+ i++;
+ }
+
+ ret = ad_gpo_get_som_attrs_step(req);
+
+ done:
+
+ if (ret == EOK) {
+ tevent_req_done(req);
+ } else if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ }
+
+}
+static errno_t
+ad_gpo_get_som_attrs_step(struct tevent_req *req)
+{
+ const char *attrs[] = {AD_AT_GPLINK, AD_AT_GPOPTIONS, NULL};
+ struct tevent_req *subreq;
+ struct ad_gpo_process_som_state *state;
+
+ state = tevent_req_data(req, struct ad_gpo_process_som_state);
+
+ struct gp_som *gp_som = state->som_list[state->som_index];
+
+ /* gp_som is NULL only after all SOMs have been processed */
+ if (gp_som == NULL) return EOK;
+
+ const char *som_dn = gp_som->som_dn;
+ subreq = sdap_get_generic_send(state, state->ev, state->opts,
+ sdap_id_op_handle(state->sdap_op),
+ som_dn, LDAP_SCOPE_BASE,
+ "(objectclass=*)", attrs, NULL, 0,
+ state->timeout,
+ false);
+
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "sdap_get_generic_send failed.\n");
+ return ENOMEM;
+ }
+
+ tevent_req_set_callback(subreq, ad_gpo_get_som_attrs_done, req);
+ return EAGAIN;
+}
+
+static void
+ad_gpo_get_som_attrs_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct ad_gpo_process_som_state *state;
+ int ret;
+ int dp_error;
+ size_t num_results;
+ struct sysdb_attrs **results;
+ struct ldb_message_element *el = NULL;
+ uint8_t *raw_gplink_value;
+ uint8_t *raw_gpoptions_value;
+ uint32_t allow_enforced_only = 0;
+ struct gp_som *gp_som;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct ad_gpo_process_som_state);
+ ret = sdap_get_generic_recv(subreq, state,
+ &num_results, &results);
+ talloc_zfree(subreq);
+
+ if (ret != EOK) {
+ ret = sdap_id_op_done(state->sdap_op, ret, &dp_error);
+
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to get SOM attributes: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ ret = ENOENT;
+ goto done;
+ }
+ if ((num_results < 1) || (results == NULL)) {
+ DEBUG(SSSDBG_FUNC_DATA, "no attrs found for SOM; try next SOM.\n");
+ state->som_index++;
+ ret = ad_gpo_get_som_attrs_step(req);
+ goto done;
+ } else if (num_results > 1) {
+ DEBUG(SSSDBG_OP_FAILURE, "Received multiple replies\n");
+ ret = ERR_INTERNAL;
+ goto done;
+ }
+
+ /* Get the gplink value, if available */
+ ret = sysdb_attrs_get_el(results[0], AD_AT_GPLINK, &el);
+
+ if (ret != EOK && ret != ENOENT) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sysdb_attrs_get_el() failed: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ if ((ret == ENOENT) || (el->num_values == 0)) {
+ DEBUG(SSSDBG_FUNC_DATA, "gpLink attr not found or has no values\n");
+ state->som_index++;
+ ret = ad_gpo_get_som_attrs_step(req);
+ goto done;
+ }
+
+ raw_gplink_value = el[0].values[0].data;
+
+ ret = sysdb_attrs_get_el(results[0], AD_AT_GPOPTIONS, &el);
+
+ if (ret != EOK && ret != ENOENT) {
+ DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_el() failed\n");
+ goto done;
+ }
+
+ if ((ret == ENOENT) || (el->num_values == 0)) {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "gpoptions attr not found or has no value; defaults to 0\n");
+ allow_enforced_only = 0;
+ } else {
+ raw_gpoptions_value = el[0].values[0].data;
+ allow_enforced_only = strtouint32((char *)raw_gpoptions_value, NULL, 10);
+ if (errno != 0) {
+ ret = errno;
+ DEBUG(SSSDBG_OP_FAILURE,
+ "strtouint32 failed: [%d](%s)\n", ret, sss_strerror(ret));
+ goto done;
+ }
+ }
+
+ gp_som = state->som_list[state->som_index];
+ ret = ad_gpo_populate_gplink_list(gp_som,
+ gp_som->som_dn,
+ (char *)raw_gplink_value,
+ &gp_som->gplink_list,
+ state->allow_enforced_only);
+
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "ad_gpo_populate_gplink_list() failed\n");
+ goto done;
+ }
+
+ if (allow_enforced_only) {
+ state->allow_enforced_only = 1;
+ }
+
+ state->som_index++;
+ ret = ad_gpo_get_som_attrs_step(req);
+
+ done:
+
+ if (ret == EOK) {
+ tevent_req_done(req);
+ } else if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ }
+}
+
+int
+ad_gpo_process_som_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct gp_som ***som_list)
+{
+
+ struct ad_gpo_process_som_state *state =
+ tevent_req_data(req, struct ad_gpo_process_som_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+ *som_list = talloc_steal(mem_ctx, state->som_list);
+ return EOK;
+}
+
+/* == ad_gpo_process_gpo_send/recv helpers ================================= */
+
+/*
+ * This function examines the gp_gplink objects in each gp_som object specified
+ * in the input som_list, and populates the _candidate_gpos output parameter's
+ * gpo_dn fields with prioritized list of GPO DNs. Prioritization ensures that:
+ * - GPOs linked to an OU will be applied after GPOs linked to a Domain,
+ * which will be applied after GPOs linked to a Site.
+ * - multiple GPOs linked to a single SOM are applied in their link order
+ * (i.e. 1st GPO linked to SOM is applied before 2nd GPO linked to SOM, etc).
+ * - enforced GPOs are applied after unenforced GPOs.
+ *
+ * As such, the _candidate_gpos output's dn fields looks like (in link order):
+ * [unenforced {Site, Domain, OU}; enforced {OU, Domain, Site}]
+ *
+ * Note that in the case of conflicting policy settings, GPOs appearing later
+ * in the list will trump GPOs appearing earlier in the list. Therefore the
+ * enforced GPOs are applied in revers order after the unenforced GPOs to
+ * make sure the enforced setting form the highest level will be applied.
+ *
+ * GPO processing details can be found e.g. at
+ * https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/dn581922(v%3Dws.11)
+ */
+static errno_t
+ad_gpo_populate_candidate_gpos(TALLOC_CTX *mem_ctx,
+ struct gp_som **som_list,
+ struct gp_gpo ***_candidate_gpos,
+ int *_num_candidate_gpos)
+{
+
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct gp_som *gp_som = NULL;
+ struct gp_gplink *gp_gplink = NULL;
+ struct gp_gpo **candidate_gpos = NULL;
+ int num_candidate_gpos = 0;
+ const char **enforced_gpo_dns = NULL;
+ const char **unenforced_gpo_dns = NULL;
+ int gpo_dn_idx = 0;
+ int num_enforced = 0;
+ int enforced_idx = 0;
+ int num_unenforced = 0;
+ int unenforced_idx = 0;
+ int i = 0;
+ int j = 0;
+ int ret;
+ size_t som_count = 0;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ while (som_list[i]) {
+ gp_som = som_list[i];
+ j = 0;
+ while (gp_som && gp_som->gplink_list && gp_som->gplink_list[j]) {
+ gp_gplink = gp_som->gplink_list[j];
+ if (gp_gplink == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "unexpected null gp_gplink\n");
+ ret = EINVAL;
+ goto done;
+ }
+ if (gp_gplink->enforced) {
+ num_enforced++;
+ } else {
+ num_unenforced++;
+ }
+ j++;
+ }
+ i++;
+ }
+ som_count = i;
+
+ num_candidate_gpos = num_enforced + num_unenforced;
+
+ if (num_candidate_gpos == 0) {
+ *_candidate_gpos = NULL;
+ *_num_candidate_gpos = 0;
+ ret = EOK;
+ goto done;
+ }
+
+ enforced_gpo_dns = talloc_array(tmp_ctx, const char *, num_enforced + 1);
+ if (enforced_gpo_dns == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ unenforced_gpo_dns = talloc_array(tmp_ctx, const char *, num_unenforced + 1);
+ if (unenforced_gpo_dns == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ i = som_count -1 ;
+ while (i >= 0) {
+ gp_som = som_list[i];
+
+ /* For unenforced_gpo_dns the most specific GPOs with the highest
+ * priority should be the last. We start with the top-level SOM and go
+ * down to the most specific one and add the unenforced following the
+ * gplink_list where the GPO with the highest priority comes last. */
+ j = 0;
+ while (gp_som && gp_som->gplink_list && gp_som->gplink_list[j]) {
+ gp_gplink = gp_som->gplink_list[j];
+
+ if (!gp_gplink->enforced) {
+ unenforced_gpo_dns[unenforced_idx] =
+ talloc_steal(unenforced_gpo_dns, gp_gplink->gpo_dn);
+
+ if (unenforced_gpo_dns[unenforced_idx] == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ unenforced_idx++;
+ }
+ j++;
+ }
+ i--;
+ }
+
+ i = 0;
+ while (som_list[i]) {
+ gp_som = som_list[i];
+
+ /* For enforced GPOs we start processing with the most specific SOM to
+ * make sur enforced GPOs from higher levels override to lower level
+ * ones. According to the 'Group Policy Inheritance' tab in the
+ * Windows 'Goup Policy Management' utility in the same SOM the link
+ * order is still observed and an enforced GPO with a lower link order
+ * value still overrides an enforced GPO with a higher link order. */
+ j = 0;
+ while (gp_som && gp_som->gplink_list && gp_som->gplink_list[j]) {
+ gp_gplink = gp_som->gplink_list[j];
+ if (gp_gplink == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "unexpected null gp_gplink\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ if (gp_gplink->enforced) {
+ enforced_gpo_dns[enforced_idx] =
+ talloc_steal(enforced_gpo_dns, gp_gplink->gpo_dn);
+ if (enforced_gpo_dns[enforced_idx] == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ enforced_idx++;
+ }
+ j++;
+ }
+ i++;
+ }
+ enforced_gpo_dns[num_enforced] = NULL;
+ unenforced_gpo_dns[num_unenforced] = NULL;
+
+ candidate_gpos = talloc_array(tmp_ctx,
+ struct gp_gpo *,
+ num_candidate_gpos + 1);
+
+ if (candidate_gpos == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ gpo_dn_idx = 0;
+ for (i = 0; i < num_unenforced; i++) {
+ candidate_gpos[gpo_dn_idx] = talloc_zero(candidate_gpos, struct gp_gpo);
+ if (candidate_gpos[gpo_dn_idx] == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ candidate_gpos[gpo_dn_idx]->gpo_dn =
+ talloc_steal(candidate_gpos[gpo_dn_idx], unenforced_gpo_dns[i]);
+
+ if (candidate_gpos[gpo_dn_idx]->gpo_dn == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "candidate_gpos[%d]->gpo_dn: %s\n",
+ gpo_dn_idx, candidate_gpos[gpo_dn_idx]->gpo_dn);
+ gpo_dn_idx++;
+ }
+
+ for (i = 0; i < num_enforced; i++) {
+
+ candidate_gpos[gpo_dn_idx] = talloc_zero(candidate_gpos, struct gp_gpo);
+ if (candidate_gpos[gpo_dn_idx] == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ candidate_gpos[gpo_dn_idx]->gpo_dn =
+ talloc_steal(candidate_gpos[gpo_dn_idx], enforced_gpo_dns[i]);
+ if (candidate_gpos[gpo_dn_idx]->gpo_dn == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "candidate_gpos[%d]->gpo_dn: %s\n",
+ gpo_dn_idx, candidate_gpos[gpo_dn_idx]->gpo_dn);
+ gpo_dn_idx++;
+ }
+
+ candidate_gpos[gpo_dn_idx] = NULL;
+
+ *_candidate_gpos = talloc_steal(mem_ctx, candidate_gpos);
+ *_num_candidate_gpos = num_candidate_gpos;
+
+ ret = EOK;
+
+ done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ * This function parses the input_path into its components, replaces each
+ * back slash ('\') with a forward slash ('/'), and populates the output params.
+ *
+ * The smb_server output is constructed by concatenating the following elements:
+ * - SMB_STANDARD_URI ("smb://")
+ * - server_hostname (which replaces domain_name in input path)
+ * The smb_share and smb_path outputs are extracted from the input_path.
+ *
+ * Example: if input_path = "\\foo.com\SysVol\foo.com\..." and
+ * server_hostname = "adserver.foo.com", then
+ * _smb_server = "smb://adserver.foo.com"
+ * _smb_share = "SysVol"
+ * _smb_path = "/foo.com/..."
+ *
+ * Note that the input_path must have at least four forward slash separators.
+ * For example, input_path = "\\foo.com\SysVol" is not a valid input_path,
+ * because it has only three forward slash separators.
+ */
+static errno_t
+ad_gpo_extract_smb_components(TALLOC_CTX *mem_ctx,
+ char *server_hostname,
+ char *input_path,
+ const char **_smb_server,
+ const char **_smb_share,
+ const char **_smb_path)
+{
+ char *ptr;
+ const char delim = '\\';
+ int ret;
+ int num_seps = 0;
+ char *smb_path = NULL;
+ char *smb_share = NULL;
+
+ DEBUG(SSSDBG_TRACE_ALL, "input_path: %s\n", input_path);
+
+ if (input_path == NULL ||
+ *input_path == '\0' ||
+ _smb_server == NULL ||
+ _smb_share == NULL ||
+ _smb_path == NULL) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ ptr = input_path;
+ while ((ptr = strchr(ptr, delim))) {
+ num_seps++;
+ if (num_seps == 3) {
+ /* replace the slash before the share name with null string */
+
+ *ptr = '\0';
+ ptr++;
+ smb_share = ptr;
+ continue;
+ } else if (num_seps == 4) {
+ /* replace the slash after the share name with null string */
+ *ptr = '\0';
+ ptr++;
+ smb_path = ptr;
+ continue;
+ }
+ *ptr = '/';
+ ptr++;
+ }
+
+ if (num_seps == 0) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ if (smb_path == NULL) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ *_smb_server = talloc_asprintf(mem_ctx, "%s%s",
+ SMB_STANDARD_URI,
+ server_hostname);
+ if (*_smb_server == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ *_smb_share = talloc_asprintf(mem_ctx, "/%s", smb_share);
+ if (*_smb_share == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ *_smb_path = talloc_asprintf(mem_ctx, "/%s", smb_path);
+ if (*_smb_path == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = EOK;
+
+ done:
+ return ret;
+}
+
+/*
+ * This function populates the _cse_guid_list output parameter by parsing the
+ * input raw_machine_ext_names_value into an array of cse_guid strings.
+ *
+ * The raw_machine_ext_names_value is a single string in the following format:
+ * "[{cse_guid_1}{tool_guid1}]...[{cse_guid_n}{tool_guid_n}]"
+ */
+static errno_t
+ad_gpo_parse_machine_ext_names(TALLOC_CTX *mem_ctx,
+ char *raw_machine_ext_names_value,
+ const char ***_gpo_cse_guids,
+ int *_num_gpo_cse_guids)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ char *ptr;
+ char *first;
+ char *last;
+ char *cse_guid;
+ char *tool_guid;
+ const char delim = ']';
+ const char **gpo_cse_guids;
+ int i;
+ int ret;
+ int num_gpo_cse_guids = 0;
+
+ if (raw_machine_ext_names_value == NULL ||
+ *raw_machine_ext_names_value == '\0' ||
+ _gpo_cse_guids == NULL) {
+ return EINVAL;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ptr = raw_machine_ext_names_value;
+ while ((ptr = strchr(ptr, delim))) {
+ ptr++;
+ num_gpo_cse_guids++;
+ }
+
+ if (num_gpo_cse_guids == 0) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ gpo_cse_guids = talloc_array(tmp_ctx, const char *, num_gpo_cse_guids + 1);
+ if (gpo_cse_guids == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ptr = raw_machine_ext_names_value;
+ for (i = 0; i < num_gpo_cse_guids; i++) {
+ first = ptr + 1;
+ last = strchr(first, delim);
+ if (last == NULL) {
+ break;
+ }
+ *last = '\0';
+ last++;
+ cse_guid = first;
+ first ++;
+ tool_guid = strchr(first, '{');
+ if (tool_guid == NULL) {
+ break;
+ }
+ *tool_guid = '\0';
+ gpo_cse_guids[i] = talloc_strdup(gpo_cse_guids, cse_guid);
+ ptr = last;
+ }
+ gpo_cse_guids[i] = NULL;
+
+ DEBUG(SSSDBG_TRACE_ALL, "num_gpo_cse_guids: %d\n", num_gpo_cse_guids);
+
+ for (i = 0; i < num_gpo_cse_guids; i++) {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "gpo_cse_guids[%d] is %s\n", i, gpo_cse_guids[i]);
+ }
+
+ *_gpo_cse_guids = talloc_steal(mem_ctx, gpo_cse_guids);
+ *_num_gpo_cse_guids = num_gpo_cse_guids;
+ ret = EOK;
+
+ done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+enum ndr_err_code
+ad_gpo_ndr_pull_security_descriptor(struct ndr_pull *ndr, int ndr_flags,
+ struct security_descriptor *r);
+
+/*
+ * This function parses the input data blob and assigns the resulting
+ * security_descriptor object to the _gpo_sd output parameter.
+ */
+static errno_t ad_gpo_parse_sd(TALLOC_CTX *mem_ctx,
+ uint8_t *data,
+ size_t length,
+ struct security_descriptor **_gpo_sd)
+{
+
+ struct ndr_pull *ndr_pull = NULL;
+ struct security_descriptor sd;
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+
+ blob.data = data;
+ blob.length = length;
+
+ ndr_pull = ndr_pull_init_blob(&blob, mem_ctx);
+ if (ndr_pull == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "ndr_pull_init_blob() failed.\n");
+ return EINVAL;
+ }
+
+ ndr_err = ad_gpo_ndr_pull_security_descriptor(ndr_pull,
+ NDR_SCALARS|NDR_BUFFERS,
+ &sd);
+
+ if (ndr_err != NDR_ERR_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to pull security descriptor\n");
+ return EINVAL;
+ }
+
+ *_gpo_sd = talloc_memdup(mem_ctx, &sd, sizeof(struct security_descriptor));
+
+ return EOK;
+}
+
+/* == ad_gpo_process_gpo_send/recv implementation ========================== */
+
+struct ad_gpo_process_gpo_state {
+ struct ad_access_ctx *access_ctx;
+ struct tevent_context *ev;
+ struct sdap_id_op *sdap_op;
+ struct dp_option *ad_options;
+ struct sdap_options *opts;
+ char *server_hostname;
+ struct sss_domain_info *host_domain;
+ int timeout;
+ struct gp_gpo **candidate_gpos;
+ int num_candidate_gpos;
+ int gpo_index;
+};
+
+static errno_t ad_gpo_get_gpo_attrs_step(struct tevent_req *req);
+static void ad_gpo_get_gpo_attrs_done(struct tevent_req *subreq);
+
+/*
+ * This function uses the input som_list to populate a prioritized list of
+ * gp_gpo objects, prioritized based on SOM type, link order, and whether the
+ * GPO is "enforced". This list represents the initial set of candidate GPOs
+ * that might be applicable to the target. This list can not be expanded, but
+ * it might be reduced based on subsequent filtering steps. The GPO object DNs
+ * are used to retrieve certain LDAP attributes of each GPO object, that are
+ * parsed into the various fields of the gp_gpo object.
+ */
+struct tevent_req *
+ad_gpo_process_gpo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sdap_id_op *sdap_op,
+ struct sdap_options *opts,
+ char *server_hostname,
+ struct sss_domain_info *host_domain,
+ struct ad_access_ctx *access_ctx,
+ int timeout,
+ struct gp_som **som_list)
+{
+ struct tevent_req *req;
+ struct ad_gpo_process_gpo_state *state;
+ errno_t ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct ad_gpo_process_gpo_state);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n");
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->sdap_op = sdap_op;
+ state->ad_options = access_ctx->ad_options;
+ state->opts = opts;
+ state->server_hostname = server_hostname;
+ state->host_domain = host_domain;
+ state->access_ctx = access_ctx;
+ state->timeout = timeout;
+ state->gpo_index = 0;
+ state->candidate_gpos = NULL;
+ state->num_candidate_gpos = 0;
+
+ ret = ad_gpo_populate_candidate_gpos(state,
+ som_list,
+ &state->candidate_gpos,
+ &state->num_candidate_gpos);
+
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to retrieve GPO List: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto immediately;
+ }
+
+ if (state->candidate_gpos == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "no gpos found\n");
+ ret = ENOENT;
+ goto immediately;
+ }
+
+ ret = ad_gpo_get_gpo_attrs_step(req);
+
+immediately:
+
+ if (ret == EOK) {
+ tevent_req_done(req);
+ tevent_req_post(req, ev);
+ } else if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static errno_t
+ad_gpo_get_gpo_attrs_step(struct tevent_req *req)
+{
+ const char *attrs[] = AD_GPO_ATTRS;
+ struct tevent_req *subreq;
+ struct ad_gpo_process_gpo_state *state;
+
+ state = tevent_req_data(req, struct ad_gpo_process_gpo_state);
+
+ struct gp_gpo *gp_gpo = state->candidate_gpos[state->gpo_index];
+
+ /* gp_gpo is NULL only after all GPOs have been processed */
+ if (gp_gpo == NULL) return EOK;
+
+ const char *gpo_dn = gp_gpo->gpo_dn;
+
+ subreq = sdap_sd_search_send(state, state->ev,
+ state->opts, sdap_id_op_handle(state->sdap_op),
+ gpo_dn, SECINFO_DACL, attrs, state->timeout);
+
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "sdap_sd_search_send failed.\n");
+ return ENOMEM;
+ }
+
+ tevent_req_set_callback(subreq, ad_gpo_get_gpo_attrs_done, req);
+ return EAGAIN;
+}
+
+static errno_t
+ad_gpo_sd_process_attrs(struct tevent_req *req,
+ char *smb_host,
+ struct sysdb_attrs *result);
+void
+ad_gpo_get_sd_referral_done(struct tevent_req *subreq);
+
+static struct tevent_req *
+ad_gpo_get_sd_referral_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ad_access_ctx *access_ctx,
+ struct sdap_options *opts,
+ const char *referral,
+ struct sss_domain_info *host_domain,
+ int timeout);
+errno_t
+ad_gpo_get_sd_referral_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ char **_smb_host,
+ struct sysdb_attrs **_reply);
+
+static void
+ad_gpo_get_gpo_attrs_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct ad_gpo_process_gpo_state *state;
+ int ret;
+ int dp_error;
+ size_t num_results, refcount;
+ struct sysdb_attrs **results;
+ char **refs;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct ad_gpo_process_gpo_state);
+
+ ret = sdap_sd_search_recv(subreq, state,
+ &num_results, &results,
+ &refcount, &refs);
+ talloc_zfree(subreq);
+
+ if (ret != EOK) {
+ ret = sdap_id_op_done(state->sdap_op, ret, &dp_error);
+
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to get GPO attributes: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ ret = ENOENT;
+ goto done;
+ }
+
+ if ((num_results < 1) || (results == NULL)) {
+ if (refcount == 1) {
+ /* If we were redirected to a referral, process it.
+ * There must be a single referral result here; if we get
+ * more than one (or zero) it's a bug.
+ */
+
+ subreq = ad_gpo_get_sd_referral_send(state, state->ev,
+ state->access_ctx,
+ state->opts,
+ refs[0],
+ state->host_domain,
+ state->timeout);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, ad_gpo_get_sd_referral_done, req);
+ ret = EAGAIN;
+ goto done;
+
+ } else {
+ const char *gpo_dn = state->candidate_gpos[state->gpo_index]->gpo_dn;
+
+ DEBUG(SSSDBG_OP_FAILURE,
+ "No attrs found for GPO [%s].\n", gpo_dn);
+ ret = ENOENT;
+ goto done;
+ }
+ } else if (num_results > 1) {
+ DEBUG(SSSDBG_OP_FAILURE, "Received multiple replies\n");
+ ret = ERR_INTERNAL;
+ goto done;
+ }
+
+ ret = ad_gpo_sd_process_attrs(req, state->server_hostname, results[0]);
+
+done:
+
+ if (ret == EOK) {
+ tevent_req_done(req);
+ } else if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ }
+}
+
+void
+ad_gpo_get_sd_referral_done(struct tevent_req *subreq)
+{
+ errno_t ret;
+ int dp_error;
+ struct sysdb_attrs *reply;
+ char *smb_host;
+
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct ad_gpo_process_gpo_state *state =
+ tevent_req_data(req, struct ad_gpo_process_gpo_state);
+
+ ret = ad_gpo_get_sd_referral_recv(subreq, state, &smb_host, &reply);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ /* Terminate the sdap_id_op */
+ ret = sdap_id_op_done(state->sdap_op, ret, &dp_error);
+
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to get referred GPO attributes: [%d](%s)\n",
+ ret, sss_strerror(ret));
+
+ goto done;
+ }
+
+ /* Lookup succeeded. Process it */
+ ret = ad_gpo_sd_process_attrs(req, smb_host, reply);
+
+done:
+
+ if (ret == EOK) {
+ tevent_req_done(req);
+ } else if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ }
+}
+
+static bool machine_ext_names_is_blank(char *attr_value)
+{
+ char *ptr;
+
+ if (attr_value == NULL) {
+ return true;
+ }
+
+ ptr = attr_value;
+ for (; *ptr != '\0'; ptr++) {
+ if (!isspace(*ptr)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static errno_t
+ad_gpo_missing_or_unreadable_attr(struct ad_gpo_process_gpo_state *state,
+ struct tevent_req *req)
+{
+ bool ignore_unreadable = dp_opt_get_bool(state->ad_options,
+ AD_GPO_IGNORE_UNREADABLE);
+
+ if (ignore_unreadable) {
+ /* If admins decided to skip GPOs with unreadable
+ * attributes just log the SID of skipped GPO */
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Group Policy Container with DN [%s] has unreadable or missing "
+ "attributes -> skipping this GPO "
+ "(ad_gpo_ignore_unreadable = True)\n",
+ state->candidate_gpos[state->gpo_index]->gpo_dn);
+ state->gpo_index++;
+ return ad_gpo_get_gpo_attrs_step(req);
+ } else {
+ /* Inform in logs and syslog that this GPO can
+ * not be processed due to unreadable or missing
+ * attributes and point to possible server side
+ * and client side solutions. */
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Group Policy Container with DN [%s] is unreadable or has "
+ "unreadable or missing attributes. In order to fix this "
+ "make sure that this AD object has following attributes "
+ "readable: nTSecurityDescriptor, cn, gPCFileSysPath, "
+ "gPCMachineExtensionNames, gPCFunctionalityVersion, flags. "
+ "Alternatively if you do not have access to the server or can "
+ "not change permissions on this object, you can use option "
+ "ad_gpo_ignore_unreadable = True which will skip this GPO. "
+ "See ad_gpo_ignore_unreadable in 'man sssd-ad' for details.\n",
+ state->candidate_gpos[state->gpo_index]->gpo_dn);
+ sss_log(SSS_LOG_ERR,
+ "Group Policy Container with DN [%s] is unreadable or has "
+ "unreadable or missing attributes. In order to fix this "
+ "make sure that this AD object has following attributes "
+ "readable: nTSecurityDescriptor, cn, gPCFileSysPath, "
+ "gPCMachineExtensionNames, gPCFunctionalityVersion, flags. "
+ "Alternatively if you do not have access to the server or can "
+ "not change permissions on this object, you can use option "
+ "ad_gpo_ignore_unreadable = True which will skip this GPO. "
+ "See ad_gpo_ignore_unreadable in 'man sssd-ad' for details.\n",
+ state->candidate_gpos[state->gpo_index]->gpo_dn);
+ return EFAULT;
+ }
+}
+
+static errno_t
+ad_gpo_sd_process_attrs(struct tevent_req *req,
+ char *smb_host,
+ struct sysdb_attrs *result)
+{
+ struct ad_gpo_process_gpo_state *state;
+ struct gp_gpo *gp_gpo;
+ int ret;
+ struct ldb_message_element *el = NULL;
+ const char *gpo_guid = NULL;
+ const char *raw_file_sys_path = NULL;
+ char *file_sys_path = NULL;
+ uint8_t *raw_machine_ext_names = NULL;
+
+ state = tevent_req_data(req, struct ad_gpo_process_gpo_state);
+ gp_gpo = state->candidate_gpos[state->gpo_index];
+
+ /* retrieve AD_AT_CN */
+ ret = sysdb_attrs_get_string(result, AD_AT_CN, &gpo_guid);
+ if (ret == ENOENT) {
+ ret = ad_gpo_missing_or_unreadable_attr(state, req);
+ goto done;
+ } else if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sysdb_attrs_get_string failed: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ gp_gpo->gpo_guid = talloc_steal(gp_gpo, gpo_guid);
+ if (gp_gpo->gpo_guid == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "populating attrs for gpo_guid: %s\n",
+ gp_gpo->gpo_guid);
+
+ /* retrieve AD_AT_FILE_SYS_PATH */
+ ret = sysdb_attrs_get_string(result,
+ AD_AT_FILE_SYS_PATH,
+ &raw_file_sys_path);
+
+ if (ret == ENOENT) {
+ ret = ad_gpo_missing_or_unreadable_attr(state, req);
+ goto done;
+ } else if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sysdb_attrs_get_string failed: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ file_sys_path = talloc_strdup(gp_gpo, raw_file_sys_path);
+
+ ret = ad_gpo_extract_smb_components(gp_gpo, smb_host,
+ file_sys_path, &gp_gpo->smb_server,
+ &gp_gpo->smb_share, &gp_gpo->smb_path);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "unable to extract smb components from file_sys_path: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "smb_server: %s\n", gp_gpo->smb_server);
+ DEBUG(SSSDBG_TRACE_ALL, "smb_share: %s\n", gp_gpo->smb_share);
+ DEBUG(SSSDBG_TRACE_ALL, "smb_path: %s\n", gp_gpo->smb_path);
+
+ /* retrieve AD_AT_FUNC_VERSION */
+ ret = sysdb_attrs_get_int32_t(result, AD_AT_FUNC_VERSION,
+ &gp_gpo->gpo_func_version);
+ if (ret == ENOENT) {
+ /* If this attribute is missing we can skip the GPO. It will
+ * be filtered out according to MS-GPOL:
+ * https://msdn.microsoft.com/en-us/library/cc232538.aspx */
+ DEBUG(SSSDBG_TRACE_ALL, "GPO with GUID %s is missing attribute "
+ AD_AT_FUNC_VERSION " and will be skipped.\n", gp_gpo->gpo_guid);
+ state->gpo_index++;
+ ret = ad_gpo_get_gpo_attrs_step(req);
+ goto done;
+ } else if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sysdb_attrs_get_int32_t failed: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "gpo_func_version: %d\n",
+ gp_gpo->gpo_func_version);
+
+ /* retrieve AD_AT_FLAGS */
+ ret = sysdb_attrs_get_int32_t(result, AD_AT_FLAGS,
+ &gp_gpo->gpo_flags);
+ if (ret == ENOENT) {
+ ret = ad_gpo_missing_or_unreadable_attr(state, req);
+ goto done;
+ } else if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sysdb_attrs_get_int32_t failed: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "gpo_flags: %d\n", gp_gpo->gpo_flags);
+
+ /* retrieve AD_AT_NT_SEC_DESC */
+ ret = sysdb_attrs_get_el(result, AD_AT_NT_SEC_DESC, &el);
+ if (ret != EOK && ret != ENOENT) {
+ DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_el() failed\n");
+ goto done;
+ }
+ if ((ret == ENOENT) || (el->num_values == 0)) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "nt_sec_desc attribute not found or has no value\n");
+ ret = ad_gpo_missing_or_unreadable_attr(state, req);
+ goto done;
+ }
+
+ ret = ad_gpo_parse_sd(gp_gpo, el[0].values[0].data, el[0].values[0].length,
+ &gp_gpo->gpo_sd);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "ad_gpo_parse_sd() failed\n");
+ goto done;
+ }
+
+ /* retrieve AD_AT_MACHINE_EXT_NAMES */
+ ret = sysdb_attrs_get_el(result, AD_AT_MACHINE_EXT_NAMES, &el);
+ if (ret != EOK && ret != ENOENT) {
+ DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_el() failed\n");
+ goto done;
+ }
+
+ if ((ret == ENOENT) || (el->num_values == 0)
+ || machine_ext_names_is_blank((char *) el[0].values[0].data)) {
+ /*
+ * if gpo has no machine_ext_names (which is perfectly valid: it could
+ * have only user_ext_names, for example), we continue to next gpo
+ */
+ DEBUG(SSSDBG_TRACE_ALL,
+ "machine_ext_names attribute not found or has no value\n");
+ state->gpo_index++;
+ } else {
+ raw_machine_ext_names = el[0].values[0].data;
+
+ ret = ad_gpo_parse_machine_ext_names(gp_gpo,
+ (char *)raw_machine_ext_names,
+ &gp_gpo->gpo_cse_guids,
+ &gp_gpo->num_gpo_cse_guids);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "ad_gpo_parse_machine_ext_names() failed\n");
+ goto done;
+ }
+
+ state->gpo_index++;
+ }
+
+ ret = ad_gpo_get_gpo_attrs_step(req);
+
+ done:
+
+ return ret;
+}
+
+int
+ad_gpo_process_gpo_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct gp_gpo ***candidate_gpos,
+ int *num_candidate_gpos)
+{
+ struct ad_gpo_process_gpo_state *state =
+ tevent_req_data(req, struct ad_gpo_process_gpo_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *candidate_gpos = talloc_steal(mem_ctx, state->candidate_gpos);
+ *num_candidate_gpos = state->num_candidate_gpos;
+ return EOK;
+}
+
+/* == ad_gpo_process_cse_send/recv helpers ================================= */
+static errno_t
+create_cse_send_buffer(TALLOC_CTX *mem_ctx,
+ const char *smb_server,
+ const char *smb_share,
+ const char *smb_path,
+ const char *smb_cse_suffix,
+ int cached_gpt_version,
+ struct io_buffer **io_buf)
+{
+ struct io_buffer *buf;
+ size_t rp;
+ int smb_server_length;
+ int smb_share_length;
+ int smb_path_length;
+ int smb_cse_suffix_length;
+
+ smb_server_length = strlen(smb_server);
+ smb_share_length = strlen(smb_share);
+ smb_path_length = strlen(smb_path);
+ smb_cse_suffix_length = strlen(smb_cse_suffix);
+
+ buf = talloc(mem_ctx, struct io_buffer);
+ if (buf == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n");
+ return ENOMEM;
+ }
+
+ buf->size = 5 * sizeof(uint32_t);
+ buf->size += smb_server_length + smb_share_length + smb_path_length +
+ smb_cse_suffix_length;
+
+ DEBUG(SSSDBG_TRACE_ALL, "buffer size: %zu\n", buf->size);
+
+ buf->data = talloc_size(buf, buf->size);
+ if (buf->data == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n");
+ talloc_free(buf);
+ return ENOMEM;
+ }
+
+ rp = 0;
+ /* cached_gpt_version */
+ SAFEALIGN_SET_UINT32(&buf->data[rp], cached_gpt_version, &rp);
+
+ /* smb_server */
+ SAFEALIGN_SET_UINT32(&buf->data[rp], smb_server_length, &rp);
+ safealign_memcpy(&buf->data[rp], smb_server, smb_server_length, &rp);
+
+ /* smb_share */
+ SAFEALIGN_SET_UINT32(&buf->data[rp], smb_share_length, &rp);
+ safealign_memcpy(&buf->data[rp], smb_share, smb_share_length, &rp);
+
+ /* smb_path */
+ SAFEALIGN_SET_UINT32(&buf->data[rp], smb_path_length, &rp);
+ safealign_memcpy(&buf->data[rp], smb_path, smb_path_length, &rp);
+
+ /* smb_cse_suffix */
+ SAFEALIGN_SET_UINT32(&buf->data[rp], smb_cse_suffix_length, &rp);
+ safealign_memcpy(&buf->data[rp], smb_cse_suffix, smb_cse_suffix_length, &rp);
+
+ *io_buf = buf;
+ return EOK;
+}
+
+static errno_t
+ad_gpo_parse_gpo_child_response(uint8_t *buf,
+ ssize_t size,
+ uint32_t *_sysvol_gpt_version,
+ uint32_t *_result)
+{
+
+ int ret;
+ size_t p = 0;
+ uint32_t sysvol_gpt_version;
+ uint32_t result;
+
+ /* sysvol_gpt_version */
+ SAFEALIGN_COPY_UINT32_CHECK(&sysvol_gpt_version, buf + p, size, &p);
+
+ /* operation result code */
+ SAFEALIGN_COPY_UINT32_CHECK(&result, buf + p, size, &p);
+
+ *_sysvol_gpt_version = sysvol_gpt_version;
+ *_result = result;
+
+ ret = EOK;
+ return ret;
+}
+
+/* == ad_gpo_process_cse_send/recv implementation ========================== */
+
+struct ad_gpo_process_cse_state {
+ struct tevent_context *ev;
+ struct sss_domain_info *domain;
+ int gpo_timeout_option;
+ const char *gpo_guid;
+ const char *smb_path;
+ const char *smb_cse_suffix;
+ pid_t child_pid;
+ uint8_t *buf;
+ ssize_t len;
+ struct child_io_fds *io;
+};
+
+static errno_t gpo_fork_child(struct tevent_req *req);
+static void gpo_cse_step(struct tevent_req *subreq);
+static void gpo_cse_done(struct tevent_req *subreq);
+
+/*
+ * This cse-specific function (GP_EXT_GUID_SECURITY) sends the input smb uri
+ * components and cached_gpt_version to the gpo child, which, in turn,
+ * will download the GPT.INI file and policy files (as needed) and store
+ * them in the GPO_CACHE directory. Note that if the send_to_child input is
+ * false, this function simply completes the request.
+ */
+struct tevent_req *
+ad_gpo_process_cse_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ bool send_to_child,
+ struct sss_domain_info *domain,
+ const char *gpo_guid,
+ const char *smb_server,
+ const char *smb_share,
+ const char *smb_path,
+ const char *smb_cse_suffix,
+ int cached_gpt_version,
+ int gpo_timeout_option)
+{
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct ad_gpo_process_cse_state *state;
+ struct io_buffer *buf = NULL;
+ errno_t ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct ad_gpo_process_cse_state);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n");
+ return NULL;
+ }
+
+ if (!send_to_child) {
+ /*
+ * if we don't need to talk to child (b/c cache timeout is still valid),
+ * we simply complete the request
+ */
+ ret = EOK;
+ goto immediately;
+ }
+
+ state->ev = ev;
+ state->buf = NULL;
+ state->len = 0;
+ state->domain = domain;
+ state->gpo_timeout_option = gpo_timeout_option;
+ state->gpo_guid = gpo_guid;
+ state->smb_path = smb_path;
+ state->smb_cse_suffix = smb_cse_suffix;
+ state->io = talloc(state, struct child_io_fds);
+ if (state->io == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n");
+ ret = ENOMEM;
+ goto immediately;
+ }
+
+ state->io->write_to_child_fd = -1;
+ state->io->read_from_child_fd = -1;
+ talloc_set_destructor((void *) state->io, child_io_destructor);
+
+ /* prepare the data to pass to child */
+ ret = create_cse_send_buffer(state, smb_server, smb_share, smb_path,
+ smb_cse_suffix, cached_gpt_version, &buf);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "create_cse_send_buffer failed.\n");
+ goto immediately;
+ }
+
+ ret = gpo_fork_child(req);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "gpo_fork_child failed.\n");
+ goto immediately;
+ }
+
+ subreq = write_pipe_send(state, ev, buf->data, buf->size,
+ state->io->write_to_child_fd);
+ if (subreq == NULL) {
+ ret = ENOMEM;
+ goto immediately;
+ }
+ tevent_req_set_callback(subreq, gpo_cse_step, req);
+
+ return req;
+
+immediately:
+
+ if (ret == EOK) {
+ tevent_req_done(req);
+ tevent_req_post(req, ev);
+ } else {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void gpo_cse_step(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct ad_gpo_process_cse_state *state;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct ad_gpo_process_cse_state);
+
+ ret = write_pipe_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ PIPE_FD_CLOSE(state->io->write_to_child_fd);
+
+ subreq = read_pipe_send(state, state->ev, state->io->read_from_child_fd);
+
+ if (subreq == NULL) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, gpo_cse_done, req);
+}
+
+static void gpo_cse_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct ad_gpo_process_cse_state *state;
+ uint32_t sysvol_gpt_version = -1;
+ uint32_t child_result;
+ time_t now;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct ad_gpo_process_cse_state);
+ int ret;
+
+ ret = read_pipe_recv(subreq, state, &state->buf, &state->len);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ PIPE_FD_CLOSE(state->io->read_from_child_fd);
+
+ ret = ad_gpo_parse_gpo_child_response(state->buf, state->len,
+ &sysvol_gpt_version, &child_result);
+ if (ret != EOK) {
+ if (ret == EINVAL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "ad_gpo_parse_gpo_child_response failed: [%d][%s]. "
+ "Broken GPO data received from AD. Check AD child logs for "
+ "more information.\n",
+ ret, sss_strerror(ret));
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "ad_gpo_parse_gpo_child_response failed: [%d][%s]\n",
+ ret, sss_strerror(ret));
+ }
+
+ tevent_req_error(req, ret);
+ return;
+ } else if (child_result != 0){
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Error in gpo_child: [%d][%s]\n",
+ child_result, strerror(child_result));
+ tevent_req_error(req, child_result);
+ return;
+ }
+
+ now = time(NULL);
+ DEBUG(SSSDBG_TRACE_FUNC, "sysvol_gpt_version: %d\n", sysvol_gpt_version);
+ ret = sysdb_gpo_store_gpo(state->domain, state->gpo_guid, sysvol_gpt_version,
+ state->gpo_timeout_option, now);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Unable to store gpo cache entry: [%d](%s}\n",
+ ret, sss_strerror(ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+int ad_gpo_process_cse_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+ return EOK;
+}
+
+static errno_t
+gpo_fork_child(struct tevent_req *req)
+{
+ int pipefd_to_child[2] = PIPE_INIT;
+ int pipefd_from_child[2] = PIPE_INIT;
+ pid_t pid;
+ errno_t ret;
+ const char **extra_args;
+ int c = 0;
+ struct ad_gpo_process_cse_state *state;
+
+ state = tevent_req_data(req, struct ad_gpo_process_cse_state);
+
+ extra_args = talloc_array(state, const char *, 2);
+
+ extra_args[c] = talloc_asprintf(extra_args, "--chain-id=%lu",
+ sss_chain_id_get());
+ if (extra_args[c] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
+ ret = ENOMEM;
+ goto fail;
+ }
+ c++;
+
+ extra_args[c] = NULL;
+
+ ret = pipe(pipefd_from_child);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "pipe (from) failed [%d][%s].\n", errno, strerror(errno));
+ goto fail;
+ }
+ ret = pipe(pipefd_to_child);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "pipe (to) failed [%d][%s].\n", errno, strerror(errno));
+ goto fail;
+ }
+
+ pid = fork();
+
+ if (pid == 0) { /* child */
+ exec_child_ex(state,
+ pipefd_to_child, pipefd_from_child,
+ GPO_CHILD, GPO_CHILD_LOG_FILE, extra_args, false,
+ STDIN_FILENO, AD_GPO_CHILD_OUT_FILENO);
+
+ /* We should never get here */
+ DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec gpo_child:\n");
+ } else if (pid > 0) { /* parent */
+ state->child_pid = pid;
+ state->io->read_from_child_fd = pipefd_from_child[0];
+ PIPE_FD_CLOSE(pipefd_from_child[1]);
+ state->io->write_to_child_fd = pipefd_to_child[1];
+ PIPE_FD_CLOSE(pipefd_to_child[0]);
+ sss_fd_nonblocking(state->io->read_from_child_fd);
+ sss_fd_nonblocking(state->io->write_to_child_fd);
+
+ ret = child_handler_setup(state->ev, pid, NULL, NULL, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Could not set up child signal handler\n");
+ goto fail;
+ }
+ } else { /* error */
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "fork failed [%d][%s].\n", errno, strerror(errno));
+ goto fail;
+ }
+
+ return EOK;
+
+fail:
+ PIPE_CLOSE(pipefd_from_child);
+ PIPE_CLOSE(pipefd_to_child);
+ return ret;
+}
+
+struct ad_gpo_get_sd_referral_state {
+ struct tevent_context *ev;
+ struct ad_access_ctx *access_ctx;
+ struct sdap_options *opts;
+ struct sss_domain_info *host_domain;
+ struct sss_domain_info *ref_domain;
+ struct sdap_id_conn_ctx *conn;
+ struct sdap_id_op *ref_op;
+ int timeout;
+ char *gpo_dn;
+ char *smb_host;
+
+
+ struct sysdb_attrs *reply;
+};
+
+static void
+ad_gpo_get_sd_referral_conn_done(struct tevent_req *subreq);
+
+static struct tevent_req *
+ad_gpo_get_sd_referral_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ad_access_ctx *access_ctx,
+ struct sdap_options *opts,
+ const char *referral,
+ struct sss_domain_info *host_domain,
+ int timeout)
+{
+ errno_t ret;
+ struct tevent_req *req;
+ struct ad_gpo_get_sd_referral_state *state;
+ struct tevent_req *subreq;
+ LDAPURLDesc *lud = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct ad_gpo_get_sd_referral_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->access_ctx = access_ctx;
+ state->opts = opts;
+ state->host_domain = host_domain;
+ state->timeout = timeout;
+
+ /* Parse the URL for the domain */
+ ret = ldap_url_parse(referral, &lud);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to parse referral URI (%s)!\n", referral);
+ ret = EINVAL;
+ goto done;
+ }
+
+ state->gpo_dn = talloc_strdup(state, lud->lud_dn);
+ if (!state->gpo_dn) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Could not copy referral DN (%s)!\n", lud->lud_dn);
+ ldap_free_urldesc(lud);
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* Active Directory returns the domain name as the hostname
+ * in these referrals, so we can use that to look up the
+ * necessary connection.
+ */
+ state->ref_domain = find_domain_by_name(state->host_domain,
+ lud->lud_host, true);
+ if (!state->ref_domain) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Could not find domain matching [%s]\n",
+ lud->lud_host);
+ ldap_free_urldesc(lud);
+ ret = EIO;
+ goto done;
+ }
+
+ ldap_free_urldesc(lud);
+ lud = NULL;
+
+ state->conn = ad_get_dom_ldap_conn(state->access_ctx->ad_id_ctx,
+ state->ref_domain);
+ if (!state->conn) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "No connection for %s\n", state->ref_domain->name);
+ ret = EINVAL;
+ goto done;
+ }
+
+ /* Get the hostname we're going to connect to.
+ * We'll need this later for performing the samba
+ * connection.
+ */
+ ret = ldap_url_parse(state->conn->service->uri, &lud);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to parse service URI (%s)!\n", referral);
+ ret = EINVAL;
+ goto done;
+ }
+
+ state->smb_host = talloc_strdup(state, lud->lud_host);
+ ldap_free_urldesc(lud);
+ if (!state->smb_host) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* Start an ID operation for the referral */
+ state->ref_op = sdap_id_op_create(state, state->conn->conn_cache);
+ if (!state->ref_op) {
+ DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* Establish the sdap_id_op connection */
+ subreq = sdap_id_op_connect_send(state->ref_op, state, &ret);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: %d(%s).\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+ tevent_req_set_callback(subreq, ad_gpo_get_sd_referral_conn_done, req);
+
+done:
+
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void
+ad_gpo_get_sd_referral_search_done(struct tevent_req *subreq);
+
+static void
+ad_gpo_get_sd_referral_conn_done(struct tevent_req *subreq)
+{
+ errno_t ret;
+ int dp_error;
+ const char *attrs[] = AD_GPO_ATTRS;
+
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct ad_gpo_get_sd_referral_state *state =
+ tevent_req_data(req, struct ad_gpo_get_sd_referral_state);
+
+ ret = sdap_id_op_connect_recv(subreq, &dp_error);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ if (dp_error == DP_ERR_OFFLINE) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Backend is marked offline, retry later!\n");
+ tevent_req_done(req);
+ } else {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Cross-realm GPO processing failed to connect to " \
+ "referred LDAP server: (%d)[%s]\n",
+ ret, sss_strerror(ret));
+ tevent_req_error(req, ret);
+ }
+ return;
+ }
+
+ /* Request the referred GPO data */
+ subreq = sdap_sd_search_send(state, state->ev, state->opts,
+ sdap_id_op_handle(state->ref_op),
+ state->gpo_dn,
+ SECINFO_DACL,
+ attrs,
+ state->timeout);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "sdap_sd_search_send failed.\n");
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, ad_gpo_get_sd_referral_search_done, req);
+
+}
+
+static void
+ad_gpo_get_sd_referral_search_done(struct tevent_req *subreq)
+{
+ errno_t ret;
+ int dp_error;
+ size_t num_results, num_refs;
+ struct sysdb_attrs **results = NULL;
+ char **refs;
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct ad_gpo_get_sd_referral_state *state =
+ tevent_req_data(req, struct ad_gpo_get_sd_referral_state);
+
+ ret = sdap_sd_search_recv(subreq, NULL,
+ &num_results, &results,
+ &num_refs, &refs);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ ret = sdap_id_op_done(state->ref_op, ret, &dp_error);
+
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to get GPO attributes: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ ret = ENOENT;
+ goto done;
+
+ }
+
+ if ((num_results < 1) || (results == NULL)) {
+ /* TODO:
+ * It's strictly possible for the referral search to return
+ * another referral value here, but it shouldn't actually
+ * happen with Active Directory. Properly handling (and
+ * limiting) the referral chain would be fairly complex, so
+ * we will do it later if it ever becomes necessary.
+ */
+ DEBUG(SSSDBG_OP_FAILURE,
+ "No attrs found for referred GPO [%s].\n", state->gpo_dn);
+ ret = ENOENT;
+ goto done;
+
+ } else if (num_results > 1) {
+ DEBUG(SSSDBG_OP_FAILURE, "Received multiple replies\n");
+ ret = ERR_INTERNAL;
+ goto done;
+ }
+
+ state->reply = talloc_steal(state, results[0]);
+
+done:
+ talloc_free(results);
+
+ if (ret == EOK) {
+ tevent_req_done(req);
+ } else if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ }
+}
+
+errno_t
+ad_gpo_get_sd_referral_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ char **_smb_host,
+ struct sysdb_attrs **_reply)
+{
+ struct ad_gpo_get_sd_referral_state *state =
+ tevent_req_data(req, struct ad_gpo_get_sd_referral_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *_smb_host = talloc_steal(mem_ctx, state->smb_host);
+ *_reply = talloc_steal(mem_ctx, state->reply);
+
+ return EOK;
+}