summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorYassir Elley <yelley@redhat.com>2014-01-20 11:17:06 -0500
committerJakub Hrozek <jhrozek@redhat.com>2014-05-13 22:17:14 +0200
commit60cab26b12df9a2153823972cde0c38ca86e01b9 (patch)
treecc10c6da23140859116510f50cfa7dedbff48277 /src
parent66e1502f956ee71de6cd51c37f7752f8aa14f5f5 (diff)
downloadsssd-60cab26b12df9a2153823972cde0c38ca86e01b9.tar.gz
sssd-60cab26b12df9a2153823972cde0c38ca86e01b9.tar.xz
sssd-60cab26b12df9a2153823972cde0c38ca86e01b9.zip
Implemented LDAP component of GPO-based access control
Reviewed-by: Sumit Bose <sbose@redhat.com> Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
Diffstat (limited to 'src')
-rw-r--r--src/config/SSSDConfig/__init__.py.in1
-rw-r--r--src/config/etc/sssd.api.d/sssd-ad.conf1
-rw-r--r--src/man/sssd-ad.5.xml64
-rw-r--r--src/providers/ad/ad_access.c137
-rw-r--r--src/providers/ad/ad_access.h6
-rw-r--r--src/providers/ad/ad_common.h1
-rw-r--r--src/providers/ad/ad_domain_info.c31
-rw-r--r--src/providers/ad/ad_domain_info.h1
-rw-r--r--src/providers/ad/ad_gpo.c2248
-rw-r--r--src/providers/ad/ad_gpo.h55
-rw-r--r--src/providers/ad/ad_id.c2
-rw-r--r--src/providers/ad/ad_init.c18
-rw-r--r--src/providers/ad/ad_opts.h1
-rw-r--r--src/providers/ad/ad_subdomains.c3
-rw-r--r--src/providers/ldap/sdap_async.c180
-rw-r--r--src/providers/ldap/sdap_async.h14
-rw-r--r--src/util/sss_ldap.h14
-rw-r--r--src/util/util_errors.c1
-rw-r--r--src/util/util_errors.h1
19 files changed, 2734 insertions, 45 deletions
diff --git a/src/config/SSSDConfig/__init__.py.in b/src/config/SSSDConfig/__init__.py.in
index 3f17a923b..3e5bbe9a8 100644
--- a/src/config/SSSDConfig/__init__.py.in
+++ b/src/config/SSSDConfig/__init__.py.in
@@ -166,6 +166,7 @@ option_strings = {
'ad_enable_dns_sites' : _('Enable DNS sites - location based service discovery'),
'ad_access_filter' : _('LDAP filter to determine access privileges'),
'ad_enable_gc' : _('Whether to use the Global Catalog for lookups'),
+ 'ad_gpo_access_control' : _('Operation mode for GPO-based access control'),
# [provider/krb5]
'krb5_kdcip' : _('Kerberos server address'),
diff --git a/src/config/etc/sssd.api.d/sssd-ad.conf b/src/config/etc/sssd.api.d/sssd-ad.conf
index ed0189618..b70e74c0a 100644
--- a/src/config/etc/sssd.api.d/sssd-ad.conf
+++ b/src/config/etc/sssd.api.d/sssd-ad.conf
@@ -6,6 +6,7 @@ ad_hostname = str, None, false
ad_enable_dns_sites = bool, None, false
ad_access_filter = str, None, false
ad_enable_gc = bool, None, false
+ad_gpo_access_control = str, None, false
ldap_uri = str, None, false
ldap_backup_uri = str, None, false
ldap_search_base = str, None, false
diff --git a/src/man/sssd-ad.5.xml b/src/man/sssd-ad.5.xml
index 539310992..21f735e0a 100644
--- a/src/man/sssd-ad.5.xml
+++ b/src/man/sssd-ad.5.xml
@@ -253,6 +253,70 @@ FOREST:EXAMPLE.COM:(memberOf=cn=admins,ou=groups,dc=example,dc=com)
</varlistentry>
<varlistentry>
+ <term>ad_gpo_access_control (string)</term>
+ <listitem>
+ <para>
+ This option specifies the operation mode for
+ GPO-based access control functionality:
+ whether it operates in disabled mode, enforcing
+ mode, or permissive mode. Please note that the
+ <quote>access_provider</quote> option must be
+ explicitly set to <quote>ad</quote> in order for
+ this option to have an effect.
+ </para>
+ <para>
+ GPO-based access control functionality uses GPO
+ policy settings to determine whether or not a
+ particular user is allowed to logon to a particular
+ host.
+ </para>
+ <para>
+ NOTE: If the operation mode is set to enforcing, it
+ is possible that users that were previously allowed
+ logon access will now be denied logon access (as
+ dictated by the GPO policy settings). In order to
+ facilitate a smooth transition for administrators,
+ a permissive mode is available that will not enforce
+ the access control rules, but will evaluate them and
+ will output a syslog message if access would have
+ been denied. By examining the logs, administrators
+ can then make the necessary changes before setting
+ the mode to enforcing.
+ </para>
+ <para>
+ There are three supported values for this option:
+ <itemizedlist>
+ <listitem>
+ <para>
+ disabled: GPO-based access control rules
+ are neither evaluated nor enforced.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ enforcing: GPO-based access control
+ rules are evaluated and enforced.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ permissive: GPO-based access control
+ rules are evaluated, but not enforced.
+ Instead, a syslog message will be
+ emitted indicating that the user would
+ have been denied access if this option's
+ value were set to enforcing.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ <para>
+ Default: permissive
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term>dyndns_update (boolean)</term>
<listitem>
<para>
diff --git a/src/providers/ad/ad_access.c b/src/providers/ad/ad_access.c
index cae075d42..06ad47417 100644
--- a/src/providers/ad/ad_access.c
+++ b/src/providers/ad/ad_access.c
@@ -25,14 +25,10 @@
#include "src/providers/data_provider.h"
#include "src/providers/dp_backend.h"
#include "src/providers/ad/ad_access.h"
+#include "providers/ad/ad_gpo.h"
#include "src/providers/ad/ad_common.h"
#include "src/providers/ldap/sdap_access.h"
-static void
-ad_access_done(struct tevent_req *req);
-static errno_t
-ad_access_step(struct tevent_req *req, struct sdap_id_conn_ctx *conn);
-
/*
* More advanced format can be used to restrict the filter to a specific
* domain or a specific forest. This format is KEYWORD:NAME:FILTER
@@ -168,6 +164,7 @@ ad_parse_access_filter(TALLOC_CTX *mem_ctx,
char *spec;
int flags;
TALLOC_CTX *tmp_ctx;
+ int i = 0;
if (_filter == NULL) return EINVAL;
@@ -193,7 +190,7 @@ ad_parse_access_filter(TALLOC_CTX *mem_ctx,
best_match = NULL;
best_flags = 0;
- for (int i=0; i < nfilters; i++) {
+ for (i=0; i < nfilters; i++) {
ret = parse_filter(tmp_ctx, filters[i], &filter, &spec, &flags);
if (ret != EOK) {
/* Skip the faulty filter. At worst, the user won't be
@@ -243,6 +240,11 @@ struct ad_access_state {
int cindex;
};
+static errno_t
+ad_sdap_access_step(struct tevent_req *req, struct sdap_id_conn_ctx *conn);
+static void
+ad_sdap_access_done(struct tevent_req *req);
+
static struct tevent_req *
ad_access_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
@@ -280,7 +282,7 @@ ad_access_send(TALLOC_CTX *mem_ctx,
goto done;
}
- ret = ad_access_step(req, state->clist[state->cindex]);
+ ret = ad_sdap_access_step(req, state->clist[state->cindex]);
if (ret != EOK) {
goto done;
}
@@ -289,13 +291,14 @@ ad_access_send(TALLOC_CTX *mem_ctx,
done:
if (ret != EOK) {
tevent_req_error(req, ret);
+
tevent_req_post(req, ev);
}
return req;
}
static errno_t
-ad_access_step(struct tevent_req *req, struct sdap_id_conn_ctx *conn)
+ad_sdap_access_step(struct tevent_req *req, struct sdap_id_conn_ctx *conn)
{
struct tevent_req *subreq;
struct ad_access_state *state;
@@ -313,19 +316,22 @@ ad_access_step(struct tevent_req *req, struct sdap_id_conn_ctx *conn)
state->ctx->sdap_access_ctx->access_rule,
sizeof(int) * LDAP_ACCESS_LAST);
- subreq = sdap_access_send(req, state->ev, state->be_ctx,
+ subreq = sdap_access_send(state, state->ev, state->be_ctx,
state->domain, req_ctx,
conn, state->pd);
if (req == NULL) {
talloc_free(req_ctx);
return ENOMEM;
}
- tevent_req_set_callback(subreq, ad_access_done, req);
+ tevent_req_set_callback(subreq, ad_sdap_access_done, req);
return EOK;
}
static void
-ad_access_done(struct tevent_req *subreq)
+ad_gpo_access_done(struct tevent_req *subreq);
+
+static void
+ad_sdap_access_done(struct tevent_req *subreq)
{
struct tevent_req *req;
struct ad_access_state *state;
@@ -336,44 +342,93 @@ ad_access_done(struct tevent_req *subreq)
ret = sdap_access_recv(subreq);
talloc_zfree(subreq);
- switch (ret) {
- case EOK:
- tevent_req_done(req);
- return;
- case ERR_ACCOUNT_EXPIRED:
- tevent_req_error(req, ret);
+ if (ret != EOK) {
+ switch (ret) {
+ case ERR_ACCOUNT_EXPIRED:
+ tevent_req_error(req, ret);
+ return;
+
+ case ERR_ACCESS_DENIED:
+ /* Retry on ACCESS_DENIED, too, to make sure that we don't
+ * miss out any attributes not present in GC
+ * FIXME - this is slow. We should retry only if GC failed
+ * and LDAP succeeded after the first ACCESS_DENIED
+ */
+ break;
+
+ default:
+ break;
+ }
+
+ /* If possible, retry with LDAP */
+ state->cindex++;
+ if (state->clist[state->cindex] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Error retrieving access check result: %s\n",
+ sss_strerror(ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ ret = ad_sdap_access_step(req, state->clist[state->cindex]);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ /* Another check in progress */
+
return;
+ }
- case ERR_ACCESS_DENIED:
- /* Retry on ACCESS_DENIED, too, to make sure that we don't
- * miss out any attributes not present in GC
- * FIXME - this is slow. We should retry only if GC failed
- * and LDAP succeeded after the first ACCESS_DENIED
- */
+ switch (state->ctx->gpo_access_control_mode) {
+ case GPO_ACCESS_CONTROL_DISABLED:
+ /* do not evaluate gpos; mark request done */
+ tevent_req_done(req);
+ return;
+ case GPO_ACCESS_CONTROL_PERMISSIVE:
+ case GPO_ACCESS_CONTROL_ENFORCING:
+ /* continue on to evaluate gpos */
break;
-
default:
- break;
+ tevent_req_error(req, EINVAL);
+ return;
}
- /* If possible, retry with LDAP */
- state->cindex++;
- if (state->clist[state->cindex] == NULL) {
- DEBUG(SSSDBG_OP_FAILURE,
- "Error retrieving access check result: %s\n",
- sss_strerror(ret));
- tevent_req_error(req, ret);
+ subreq = ad_gpo_access_send(state,
+ state->be_ctx->ev,
+ state->domain,
+ state->ctx,
+ state->pd->user);
+
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
return;
}
- ret = ad_access_step(req, state->clist[state->cindex]);
- if (ret != EOK) {
+ tevent_req_set_callback(subreq, ad_gpo_access_done, req);
+
+}
+
+static void
+ad_gpo_access_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ errno_t ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+
+ ret = ad_gpo_access_recv(subreq);
+ talloc_zfree(subreq);
+
+ if (ret == EOK) {
+ DEBUG(SSSDBG_TRACE_FUNC, "GPO-based access control successful.\n");
+ tevent_req_done(req);
+ } else {
+ DEBUG(SSSDBG_OP_FAILURE, "GPO-based access control failed.\n");
tevent_req_error(req, ret);
- return;
}
-
- /* Another check in progress */
}
static errno_t
@@ -385,7 +440,7 @@ ad_access_recv(struct tevent_req *req)
}
static void
-ad_access_check_done(struct tevent_req *req);
+ad_access_done(struct tevent_req *req);
void
ad_access_handler(struct be_req *breq)
@@ -411,18 +466,18 @@ ad_access_handler(struct be_req *breq)
domain = be_ctx->domain;
}
- /* Verify that the account is not locked */
+ /* Verify access control: locked accounts, ldap policies, GPOs, etc */
req = ad_access_send(breq, be_ctx->ev, be_ctx, domain,
access_ctx, pd);
if (!req) {
be_req_terminate(breq, DP_ERR_FATAL, PAM_SYSTEM_ERR, NULL);
return;
}
- tevent_req_set_callback(req, ad_access_check_done, breq);
+ tevent_req_set_callback(req, ad_access_done, breq);
}
static void
-ad_access_check_done(struct tevent_req *req)
+ad_access_done(struct tevent_req *req)
{
errno_t ret;
struct be_req *breq =
diff --git a/src/providers/ad/ad_access.h b/src/providers/ad/ad_access.h
index 3bd19ccc5..4a9bd40a0 100644
--- a/src/providers/ad/ad_access.h
+++ b/src/providers/ad/ad_access.h
@@ -27,6 +27,12 @@ struct ad_access_ctx {
struct dp_option *ad_options;
struct sdap_access_ctx *sdap_access_ctx;
struct ad_id_ctx *ad_id_ctx;
+ /* supported GPO access control modes */
+ enum gpo_access_control_mode {
+ GPO_ACCESS_CONTROL_DISABLED,
+ GPO_ACCESS_CONTROL_PERMISSIVE,
+ GPO_ACCESS_CONTROL_ENFORCING
+ } gpo_access_control_mode;
};
void
diff --git a/src/providers/ad/ad_common.h b/src/providers/ad/ad_common.h
index bc11e54b0..84c4593c6 100644
--- a/src/providers/ad/ad_common.h
+++ b/src/providers/ad/ad_common.h
@@ -43,6 +43,7 @@ enum ad_basic_opt {
AD_ENABLE_DNS_SITES,
AD_ACCESS_FILTER,
AD_ENABLE_GC,
+ AD_GPO_ACCESS_CONTROL,
AD_OPTS_BASIC /* opts counter */
};
diff --git a/src/providers/ad/ad_domain_info.c b/src/providers/ad/ad_domain_info.c
index 95944840c..a71323849 100644
--- a/src/providers/ad/ad_domain_info.c
+++ b/src/providers/ad/ad_domain_info.c
@@ -44,7 +44,9 @@
static errno_t
netlogon_get_domain_info(TALLOC_CTX *mem_ctx,
struct sysdb_attrs *reply,
- char **_flat_name, char **_forest)
+ char **_flat_name,
+ char **_site,
+ char **_forest)
{
errno_t ret;
struct ldb_message_element *el;
@@ -53,6 +55,7 @@ netlogon_get_domain_info(TALLOC_CTX *mem_ctx,
enum ndr_err_code ndr_err;
struct netlogon_samlogon_response response;
const char *flat_name;
+ const char *site;
const char *forest;
ret = sysdb_attrs_get_el(reply, AD_AT_NETLOGON, &el);
@@ -129,6 +132,22 @@ netlogon_get_domain_info(TALLOC_CTX *mem_ctx,
goto done;
}
+ /* get site name */
+ if (response.data.nt5_ex.client_site != NULL
+ && response.data.nt5_ex.client_site[0] != '\0') {
+ site = response.data.nt5_ex.client_site;
+ } else {
+ ret = ENOENT;
+ goto done;
+ }
+
+ *_site = talloc_strdup(mem_ctx, site);
+ if (*_site == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
ret = EOK;
done:
talloc_free(ndr_pull);
@@ -146,6 +165,7 @@ struct ad_master_domain_state {
int base_iter;
char *flat;
+ char *site;
char *forest;
char *sid;
};
@@ -362,14 +382,16 @@ ad_master_domain_netlogon_done(struct tevent_req *subreq)
/* Exactly one flat name. Carry on */
ret = netlogon_get_domain_info(state, reply[0], &state->flat,
- &state->forest);
+ &state->site, &state->forest);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
"Could not get the flat name or forest\n");
/* Not fatal. Just quit. */
goto done;
}
+
DEBUG(SSSDBG_TRACE_FUNC, "Found flat name [%s].\n", state->flat);
+ DEBUG(SSSDBG_TRACE_FUNC, "Found site [%s].\n", state->site);
DEBUG(SSSDBG_TRACE_FUNC, "Found forest [%s].\n", state->forest);
done:
@@ -382,6 +404,7 @@ ad_master_domain_recv(struct tevent_req *req,
TALLOC_CTX *mem_ctx,
char **_flat,
char **_id,
+ char **_site,
char **_forest)
{
struct ad_master_domain_state *state = tevent_req_data(req,
@@ -393,6 +416,10 @@ ad_master_domain_recv(struct tevent_req *req,
*_flat = talloc_steal(mem_ctx, state->flat);
}
+ if (_site) {
+ *_site = talloc_steal(mem_ctx, state->site);
+ }
+
if (_forest) {
*_forest = talloc_steal(mem_ctx, state->forest);
}
diff --git a/src/providers/ad/ad_domain_info.h b/src/providers/ad/ad_domain_info.h
index d3a6416ce..b96e8a3c3 100644
--- a/src/providers/ad/ad_domain_info.h
+++ b/src/providers/ad/ad_domain_info.h
@@ -37,6 +37,7 @@ ad_master_domain_recv(struct tevent_req *req,
TALLOC_CTX *mem_ctx,
char **_flat,
char **_id,
+ char **_site,
char **_forest);
#endif /* _AD_MASTER_DOMAIN_H_ */
diff --git a/src/providers/ad/ad_gpo.c b/src/providers/ad/ad_gpo.c
new file mode 100644
index 000000000..ca8a5f66c
--- /dev/null
+++ b/src/providers/ad/ad_gpo.c
@@ -0,0 +1,2248 @@
+/*
+ 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
+ */
+
+#include <security/pam_modules.h>
+#include "util/util.h"
+#include "util/strtonum.h"
+#include "providers/data_provider.h"
+#include "providers/dp_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 <ndr.h>
+#include <gen_ndr/security.h>
+
+#define AD_AT_DN "distinguishedName"
+#define AD_AT_UAC "userAccountControl"
+#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_DISPLAY_NAME "displayName"
+#define AD_AT_FILE_SYS_PATH "gPCFileSysPath"
+#define AD_AT_VERSION_NUMBER "versionNumber"
+#define AD_AT_MACHINE_EXT_NAMES "gPCMachineExtensionNames"
+#define AD_AT_USER_EXT_NAMES "gPCUserExtensionNames"
+#define AD_AT_FUNC_VERSION "gPCFunctionalityVersion"
+#define AD_AT_FLAGS "flags"
+
+#define UAC_WORKSTATION_TRUST_ACCOUNT 0x00001000
+#define AD_AGP_GUID "edacfd8f-ffb3-11d1-b41d-00a0c968f939"
+#define AD_AUTHENTICATED_USERS_SID "S-1-5-11"
+#define SID_MAX_LEN 1024
+
+/* == 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 *gpo_display_name;
+ const char *gpo_file_sys_path;
+ uint32_t gpo_container_version;
+ const char **gpo_cse_guids;
+ int num_gpo_cse_guids;
+ int gpo_func_version;
+ int gpo_flags;
+};
+
+enum ace_eval_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,
+ 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,
+ 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);
+
+/* == ad_gpo_access_send/recv helpers =======================================*/
+
+/*
+ * 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,
+ 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;
+
+ 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));
+ return ret;
+ }
+
+ if (res->count == 0) {
+ ret = ENOENT;
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sysdb_initgroups returned empty result\n");
+ return ret;
+ }
+
+ user_sid = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_SID_STR, NULL);
+ num_group_sids = (res->count) - 1;
+
+ /* include space for AD_AUTHENTICATED_USERS_SID and NULL */
+ group_sids = talloc_array(tmp_ctx, const char *, num_group_sids + 1 + 1);
+ 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) {
+ continue;
+ }
+
+ 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);
+ group_sids[i] = NULL;
+
+ *_group_size = num_group_sids + 1;
+ *_group_sids = talloc_steal(mem_ctx, group_sids);
+ *_user_sid = talloc_steal(mem_ctx, user_sid);
+ return EOK;
+
+ done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+bool string_to_sid(struct dom_sid *sidout, const char *sidstr);
+int dom_sid_string_buf(const struct dom_sid *sid, char *buf, int buflen);
+bool dom_sid_equal(const struct dom_sid *sid1, const struct dom_sid *sid2);
+
+/*
+ * This function determines whether the input ACE includes 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 **group_sids,
+ int group_size,
+ struct security_ace *ace,
+ bool *_included)
+{
+ int i = 0;
+ struct dom_sid ace_dom_sid;
+ struct dom_sid user_dom_sid;
+ struct dom_sid group_dom_sid;
+ char buf[SID_MAX_LEN + 1];
+
+ ace_dom_sid = ace->trustee;
+
+ dom_sid_string_buf(&ace_dom_sid, buf, SID_MAX_LEN);
+
+ if (!string_to_sid(&user_dom_sid, user_sid)) {
+ DEBUG(SSSDBG_OP_FAILURE, "string_to_sid failed\n");
+ return EINVAL;
+ }
+
+ if (dom_sid_equal(&ace_dom_sid, &user_dom_sid)) {
+ *_included = true;
+ return EOK;
+ }
+
+ for (i = 0; i < group_size; i++) {
+ if (!string_to_sid(&group_dom_sid, group_sids[i])) {
+ DEBUG(SSSDBG_OP_FAILURE, "string_to_sid failed\n");
+ return EINVAL;
+ }
+ if (dom_sid_equal(&ace_dom_sid, &group_dom_sid)) {
+ *_included = true;
+ return EOK;
+ }
+ }
+
+ *_included = false;
+ return EOK;
+}
+
+/*
+ * This function determines whether use of the extended right
+ * named "ApplyGroupPolicy" (AGP) is allowed, by comparing the specified
+ * user_sid and group_sids against the specified access control entry (ACE).
+ * This function returns ALLOWED, DENIED, or NEUTRAL depending on whether
+ * the ACE explictly allows, explicitly denies, or does neither.
+ *
+ * Note that the 'M' abbreviation used in the evaluation algorithm stands for
+ * "access_mask", which represents the set of access rights associated with an
+ * individual 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.
+ *
+ * The ACE evaluation algorithm is specified in [MS-ADTS] 5.1.3.3.4:
+ * - Deny access by default
+ * - If the "Inherit Only" (IO) flag is set in the ACE, skip the ACE.
+ * - If the SID in the ACE does not match any SID in the requester's
+ * security context, skip the ACE
+ * - 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 either not present OR contains a GUID value equal
+ * to AGP, then grant requested control access right. Stop access checking.
+ * - 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 either not present OR contains a GUID value equal to
+ * AGP, then deny the requested control access right. Stop access checking.
+ */
+static enum ace_eval_status ad_gpo_evaluate_ace(struct security_ace *ace,
+ const char *user_sid,
+ const char **group_sids,
+ int group_size)
+{
+ bool agp_included = false;
+ 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, group_sids, group_size, ace,
+ &included);
+ if (ret != EOK) {
+ return AD_GPO_ACE_DENIED;
+ }
+
+ if (!included) {
+ return AD_GPO_ACE_NEUTRAL;
+ }
+
+ object = ace->object.object;
+ GUID_from_string(AD_AGP_GUID, &ext_right_agp_guid);
+
+ if (object.flags & SEC_ACE_OBJECT_TYPE_PRESENT) {
+ if (GUID_equal(&object.type.type, &ext_right_agp_guid)) {
+ agp_included = true;
+ }
+ } else {
+ agp_included = false;
+ }
+
+ if (ace->access_mask & SEC_ADS_CONTROL_ACCESS) {
+ if (agp_included) {
+ 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_DENIED;
+}
+
+/*
+ * 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 boolean result is assigned to the _access_allowed output parameter.
+ */
+static errno_t ad_gpo_evaluate_dacl(struct security_acl *dacl,
+ const char *user_sid,
+ const char **group_sids,
+ int group_size,
+ bool *_dacl_access_allowed)
+{
+ uint32_t num_aces = 0;
+ enum ace_eval_status ace_status;
+ int i = 0;
+ struct security_ace *ace = NULL;
+
+ num_aces = dacl->num_aces;
+
+ /*
+ * [MS-ADTS] 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;
+ }
+
+ for (i = 0; i < dacl->num_aces; i ++) {
+ ace = &dacl->aces[i];
+
+ ace_status = ad_gpo_evaluate_ace(ace, user_sid, group_sids, group_size);
+
+ switch (ace_status) {
+ case AD_GPO_ACE_NEUTRAL:
+ continue;
+ case AD_GPO_ACE_ALLOWED:
+ *_dacl_access_allowed = true;
+ return EOK;
+ case AD_GPO_ACE_DENIED:
+ *_dacl_access_allowed = false;
+ return EOK;
+ }
+ }
+
+ *_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,
+ struct sss_domain_info *domain,
+ 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;
+ 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, &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;
+ }
+
+ 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];
+ sd = candidate_gpo->gpo_sd;
+ dacl = candidate_gpo->gpo_sd->dacl;
+
+ DEBUG(SSSDBG_TRACE_ALL, "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_ALL,
+ "GPO not applicable to target per security filtering\n");
+ continue;
+ }
+
+ /* gpo_flags value of 2 means that GPO's computer portion is disabled */
+ if (candidate_gpo->gpo_flags == 2) {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "GPO not applicable to target per security filtering\n");
+ continue;
+ }
+
+ /*
+ * [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.
+ */
+
+ if ((!(sd->type & SEC_DESC_DACL_PRESENT)) || (dacl == NULL)) {
+ DEBUG(SSSDBG_TRACE_ALL, "DACL is not present\n");
+ access_allowed = true;
+ break;
+ }
+
+ ad_gpo_evaluate_dacl(dacl, user_sid, group_sids,
+ group_size, &access_allowed);
+ if (access_allowed) {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "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_ALL,
+ "GPO not applicable to target per security filtering\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;
+}
+
+/* == ad_gpo_access_send/recv implementation ================================*/
+
+struct ad_gpo_access_state {
+ struct tevent_context *ev;
+ struct ldb_context *ldb_ctx;
+ struct sdap_id_conn_ctx *conn;
+ struct sdap_id_op *sdap_op;
+ struct sdap_options *opts;
+ int timeout;
+ struct sss_domain_info *domain;
+ const char *user;
+ const char *ad_hostname;
+ const char *target_dn;
+ struct gp_gpo **dacl_filtered_gpos;
+ int num_dacl_filtered_gpos;
+};
+
+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);
+
+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)
+{
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct ad_gpo_access_state *state;
+ errno_t ret;
+
+ 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;
+ }
+
+ state->domain = domain;
+ state->dacl_filtered_gpos = NULL;
+ state->num_dacl_filtered_gpos = 0;
+ state->ev = ev;
+ state->user = user;
+ state->ldb_ctx = sysdb_ctx_get_ldb(domain->sysdb);
+ state->ad_hostname = dp_opt_get_string(ctx->ad_options, AD_HOSTNAME);
+ 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, 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);
+
+ ret = EOK;
+
+immediately:
+
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void
+ad_gpo_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct ad_gpo_access_state *state;
+ char* filter;
+ char *sam_account_name;
+ char *domain_dn;
+ int dp_error;
+ errno_t ret;
+
+ const char *attrs[] = {AD_AT_DN, AD_AT_UAC, NULL};
+
+ 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) {
+ /* TBD: handle (dp_error == DP_ERR_OFFLINE) case */
+
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Failed to connect to AD server: [%d](%s)\n",
+ ret, sss_strerror(ret));
+
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ sam_account_name = talloc_asprintf(state, "%s$", state->ad_hostname);
+ if (sam_account_name == NULL) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "sam_account_name is %s\n", sam_account_name);
+
+ /* Convert the domain name into domain DN */
+ ret = domain_to_basedn(state, state->domain->name, &domain_dn);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Cannot convert domain name [%s] to base DN [%d]: %s\n",
+ state->domain->name, ret, sss_strerror(ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ /* SDAP_OC_USER objectclass covers both users and computers */
+ filter = talloc_asprintf(state,
+ "(&(objectclass=%s)(%s=%s))",
+ state->opts->user_map[SDAP_OC_USER].name,
+ state->opts->user_map[SDAP_AT_USER_NAME].name,
+ sam_account_name);
+
+ if (filter == NULL) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ subreq = sdap_get_generic_send(state, state->ev, state->opts,
+ sdap_id_op_handle(state->sdap_op),
+ domain_dn, LDAP_SCOPE_SUBTREE,
+ filter, attrs, NULL, 0,
+ state->timeout,
+ false);
+
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "sdap_get_generic_send failed.\n");
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ tevent_req_set_callback(subreq, ad_gpo_target_dn_retrieval_done, req);
+}
+
+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;
+ size_t reply_count;
+ struct sysdb_attrs **reply;
+ const char *target_dn = NULL;
+ uint32_t uac;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct ad_gpo_access_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);
+ /* TBD: handle (dp_error == DP_ERR_OFFLINE) case */
+
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to get policy target's DN: [%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 DN retrieved for policy target.\n");
+ ret = ENOENT;
+ goto done;
+ } else if (reply_count > 1) {
+ DEBUG(SSSDBG_OP_FAILURE, "Multiple replies for policy target\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_DN, &target_dn);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sysdb_attrs_get_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;
+ }
+
+ ret = sysdb_attrs_get_uint32_t(reply[0], AD_AT_UAC, &uac);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sysdb_attrs_get_uint32_t failed: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ /* we only support computer policy targets, not users */
+ if (!(uac & UAC_WORKSTATION_TRUST_ACCOUNT)) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ subreq = ad_gpo_process_som_send(state,
+ state->ev,
+ state->conn,
+ state->ldb_ctx,
+ state->sdap_op,
+ state->opts,
+ state->timeout,
+ state->target_dn,
+ state->domain->name);
+ 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->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.
+ */
+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;
+
+ 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) {
+ /* TBD: handle (dp_error == DP_ERR_OFFLINE) case */
+
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to get GPO list: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ ret = ENOENT;
+ goto done;
+ }
+
+ ret = ad_gpo_filter_gpos_by_dacl(state, state->user, state->domain,
+ 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: [%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");
+ 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);
+ }
+
+ /* TBD: initiate SMB retrieval */
+ DEBUG(SSSDBG_TRACE_FUNC, "time for SMB retrieval\n");
+
+ ret = EOK;
+
+ 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);
+ rdn_count = ldb_dn_get_comp_num(ldb_target_dn);
+
+ 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_ALL, "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 = EINVAL;
+ 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) {
+ break;
+ }
+ *last = '\0';
+ last++;
+ dn = first;
+ if ( strncasecmp(dn, "LDAP://", 7)== 0 ) {
+ dn = dn + 7;
+ }
+ gplink_options = strchr(first, ';');
+ if (gplink_options == NULL) {
+ continue;
+ }
+ *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;
+ 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,
+ 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->timeout = timeout;
+ state->som_index = -1;
+ 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_master_domain_send(state, state->ev, conn,
+ state->sdap_op, domain_name);
+
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "ad_master_domain_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;
+ 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_master_domain_recv(subreq, state, NULL, NULL, &site, NULL);
+ talloc_zfree(subreq);
+
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Cannot retrieve master domain info\n");
+ tevent_req_error(req, ENOENT);
+ return;
+ }
+
+ state->site_name = talloc_asprintf(state, "cn=%s", site);
+ if (state->site_name == NULL) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ /*
+ * 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);
+ /* TBD: handle (dp_error == DP_ERR_OFFLINE) case */
+
+ 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);
+
+ state->som_index++;
+ 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);
+ /* TBD: handle (dp_error == DP_ERR_OFFLINE) case */
+
+ 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_OP_FAILURE, "no attrs found for SOM; try next SOM.\n");
+ 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_OP_FAILURE, "no attrs found for SOM; try next SOM\n");
+ 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;
+ }
+
+ 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 after 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 {Site, Domain, OU}]
+ *
+ * Note that in the case of conflicting policy settings, GPOs appearing later
+ * in the list will trump GPOs appearing earlier in the list.
+ */
+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;
+
+ 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++;
+ }
+
+ 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 = 0;
+ 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) {
+ 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++;
+ } else {
+
+ 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++;
+ }
+ 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 = num_unenforced - 1; i >= 0; 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 converts the input_path to an smb uri, which is used to
+ * populate the _converted_path output parameter. The conversion consists of:
+ * - prepending "smb:"
+ * - replacing each forward slash ('\') with a back slash character ('/')
+ */
+static errno_t
+ad_gpo_convert_to_smb_uri(TALLOC_CTX *mem_ctx,
+ char *input_path,
+ const char **_converted_path)
+{
+ char *ptr;
+ const char delim = '\\';
+ int ret;
+ int num_seps = 0;
+
+ DEBUG(SSSDBG_TRACE_ALL, "input_path: %s\n", input_path);
+
+ if (input_path == NULL ||
+ *input_path == '\0' ||
+ _converted_path == NULL) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ ptr = input_path;
+ while ((ptr = strchr(ptr, delim))) {
+ *ptr = '/';
+ ptr++;
+ num_seps++;
+ }
+
+ if (num_seps == 0) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ *_converted_path = talloc_asprintf(mem_ctx, "smb:%s", input_path);
+ 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
+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;
+
+ 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_pull_security_descriptor(ndr_pull, NDR_SCALARS|NDR_BUFFERS, &sd);
+
+ *_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 tevent_context *ev;
+ struct sdap_id_op *sdap_op;
+ struct sdap_options *opts;
+ 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,
+ 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->opts = opts;
+ state->timeout = timeout;
+ state->gpo_index = -1;
+ 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));
+ ret = ENOENT;
+ 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_AT_NT_SEC_DESC, AD_AT_CN, AD_AT_DISPLAY_NAME,
+ AD_AT_FILE_SYS_PATH, AD_AT_VERSION_NUMBER,
+ AD_AT_MACHINE_EXT_NAMES, AD_AT_FUNC_VERSION,
+ AD_AT_FLAGS, NULL};
+ struct tevent_req *subreq;
+ struct ad_gpo_process_gpo_state *state;
+
+ state = tevent_req_data(req, struct ad_gpo_process_gpo_state);
+
+ state->gpo_index++;
+ 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_get_generic_send failed.\n");
+ return ENOMEM;
+ }
+
+ tevent_req_set_callback(subreq, ad_gpo_get_gpo_attrs_done, req);
+ return EAGAIN;
+}
+
+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;
+ struct sysdb_attrs **results;
+ struct ldb_message_element *el = NULL;
+ const char *gpo_guid = NULL;
+ const char *smb_uri = NULL;
+ const char *gpo_display_name = NULL;
+ const char *raw_file_sys_path = NULL;
+ char *file_sys_path = NULL;
+ uint8_t *raw_machine_ext_names = NULL;
+
+ 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);
+ talloc_zfree(subreq);
+
+ if (ret != EOK) {
+ ret = sdap_id_op_done(state->sdap_op, ret, &dp_error);
+ /* TBD: handle (dp_error == DP_ERR_OFFLINE) case */
+
+ 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)) {
+ DEBUG(SSSDBG_OP_FAILURE, "no attrs found for GPO; try next GPO.\n");
+ ret = ad_gpo_get_gpo_attrs_step(req);
+ goto done;
+ }
+ else if (num_results > 1) {
+ DEBUG(SSSDBG_OP_FAILURE, "Received multiple replies\n");
+ ret = ERR_INTERNAL;
+ goto done;
+ }
+
+ struct gp_gpo *gp_gpo = state->candidate_gpos[state->gpo_index];
+
+ /* retrieve AD_AT_CN */
+ ret = sysdb_attrs_get_string(results[0], AD_AT_CN, &gpo_guid);
+ 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_DISPLAY_NAME */
+ ret = sysdb_attrs_get_string(results[0], AD_AT_DISPLAY_NAME,
+ &gpo_display_name);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sysdb_attrs_get_string failed: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ gp_gpo->gpo_display_name = talloc_steal(gp_gpo, gpo_display_name);
+ if (gp_gpo->gpo_display_name == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "gpo_display_name: %s\n",
+ gp_gpo->gpo_display_name);
+
+ /* retrieve AD_AT_FILE_SYS_PATH */
+ ret = sysdb_attrs_get_string(results[0],
+ AD_AT_FILE_SYS_PATH,
+ &raw_file_sys_path);
+
+ 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);
+ ad_gpo_convert_to_smb_uri(state, file_sys_path, &smb_uri);
+
+ gp_gpo->gpo_file_sys_path = talloc_asprintf(gp_gpo, "%s/Machine",
+ smb_uri);
+ if (gp_gpo->gpo_file_sys_path == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "gpo_file_sys_path: %s\n",
+ gp_gpo->gpo_file_sys_path);
+
+ /* retrieve AD_AT_VERSION_NUMBER */
+ ret = sysdb_attrs_get_uint32_t(results[0], AD_AT_VERSION_NUMBER,
+ &gp_gpo->gpo_container_version);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sysdb_attrs_get_uint32_t failed: [%d](%s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "gpo_container_version: %d\n",
+ gp_gpo->gpo_container_version);
+
+ /* retrieve AD_AT_MACHINE_EXT_NAMES */
+ ret = sysdb_attrs_get_el(results[0], 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)) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "machine_ext_names not found or has no value\n");
+ ret = ENOENT;
+ goto done;
+ }
+
+ 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;
+ }
+
+ /* retrieve AD_AT_FUNC_VERSION */
+ ret = sysdb_attrs_get_int32_t(results[0], AD_AT_FUNC_VERSION,
+ &gp_gpo->gpo_func_version);
+ 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(results[0], AD_AT_FLAGS,
+ &gp_gpo->gpo_flags);
+ 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(results[0], 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 = ENOENT;
+ 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;
+ }
+
+ ret = ad_gpo_get_gpo_attrs_step(req);
+
+ done:
+
+ if (ret == EOK) {
+ tevent_req_done(req);
+ } else if (ret != EAGAIN) {
+ tevent_req_error(req, 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;
+}
diff --git a/src/providers/ad/ad_gpo.h b/src/providers/ad/ad_gpo.h
new file mode 100644
index 000000000..856013e43
--- /dev/null
+++ b/src/providers/ad/ad_gpo.h
@@ -0,0 +1,55 @@
+/*
+ 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/>.
+*/
+
+#ifndef AD_GPO_H_
+#define AD_GPO_H_
+
+/*
+ * This pair of functions provides client-side GPO processing.
+ *
+ * While a GPO can target both user and computer objects, this
+ * implementation only supports targetting of computer objects.
+ *
+ * A GPO overview is at https://fedorahosted.org/sssd/wiki/GpoOverview
+ *
+ * In summary, client-side processing involves:
+ * - determining the target's DN
+ * - extracting the SOM object DNs (i.e. OUs and Domain) from target's DN
+ * - including the target's Site as another SOM object
+ * - determining which GPOs apply to the target's SOMs
+ * - prioritizing GPOs based on SOM, link order, and whether GPO is "enforced"
+ * - retrieving the corresponding GPO objects
+ * - sending the GPO DNs to the CSE processing engine for policy application
+ * - policy application currently consists of HBAC-like functionality
+ */
+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);
+
+errno_t ad_gpo_access_recv(struct tevent_req *req);
+
+struct security_descriptor;
+
+#endif /* AD_GPO_H_ */
diff --git a/src/providers/ad/ad_id.c b/src/providers/ad/ad_id.c
index 3425b3555..f9bf83ed0 100644
--- a/src/providers/ad/ad_id.c
+++ b/src/providers/ad/ad_id.c
@@ -558,7 +558,7 @@ ad_enumeration_master_done(struct tevent_req *subreq)
char *forest;
ret = ad_master_domain_recv(subreq, state,
- &flat_name, &master_sid, &forest);
+ &flat_name, &master_sid, NULL, &forest);
talloc_zfree(subreq);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "Cannot retrieve master domain info\n");
diff --git a/src/providers/ad/ad_init.c b/src/providers/ad/ad_init.c
index 74ef42304..0a54d3970 100644
--- a/src/providers/ad/ad_init.c
+++ b/src/providers/ad/ad_init.c
@@ -370,6 +370,7 @@ sssm_ad_access_init(struct be_ctx *bectx,
struct ad_access_ctx *access_ctx;
struct ad_id_ctx *ad_id_ctx;
const char *filter;
+ const char *gpo_access_control_mode;
access_ctx = talloc_zero(bectx, struct ad_access_ctx);
if (!access_ctx) return ENOMEM;
@@ -421,6 +422,23 @@ sssm_ad_access_init(struct be_ctx *bectx,
access_ctx->sdap_access_ctx->access_rule[1] = LDAP_ACCESS_EMPTY;
}
+ /* GPO access control mode */
+ gpo_access_control_mode =
+ dp_opt_get_string(access_ctx->ad_options, AD_GPO_ACCESS_CONTROL);
+ if (strcasecmp(gpo_access_control_mode, "disabled") == 0) {
+ access_ctx->gpo_access_control_mode = GPO_ACCESS_CONTROL_DISABLED;
+ } else if (strcasecmp(gpo_access_control_mode, "permissive") == 0) {
+ access_ctx->gpo_access_control_mode = GPO_ACCESS_CONTROL_PERMISSIVE;
+ } else if (strcasecmp(gpo_access_control_mode, "enforcing") == 0) {
+ access_ctx->gpo_access_control_mode = GPO_ACCESS_CONTROL_ENFORCING;
+ } else {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Unrecognized GPO access control mode: %s\n",
+ gpo_access_control_mode);
+ ret = EINVAL;
+ goto fail;
+ }
+
*ops = &ad_access_ops;
*pvt_data = access_ctx;
diff --git a/src/providers/ad/ad_opts.h b/src/providers/ad/ad_opts.h
index 69a4dfa8e..e793e2b1e 100644
--- a/src/providers/ad/ad_opts.h
+++ b/src/providers/ad/ad_opts.h
@@ -37,6 +37,7 @@ struct dp_option ad_basic_opts[] = {
{ "ad_enable_dns_sites", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
{ "ad_access_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING},
{ "ad_enable_gc", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
+ { "ad_gpo_access_control", DP_OPT_STRING, { "permissive" }, NULL_STRING },
DP_OPTION_TERMINATOR
};
diff --git a/src/providers/ad/ad_subdomains.c b/src/providers/ad/ad_subdomains.c
index 102b62fcd..3ba1059c1 100644
--- a/src/providers/ad/ad_subdomains.c
+++ b/src/providers/ad/ad_subdomains.c
@@ -92,6 +92,7 @@ struct ad_subdomains_req_ctx {
char *master_sid;
char *flat_name;
+ char *site;
char *forest;
};
@@ -592,7 +593,7 @@ static void ad_subdomains_master_dom_done(struct tevent_req *req)
ret = ad_master_domain_recv(req, ctx,
&ctx->flat_name, &ctx->master_sid,
- &ctx->forest);
+ &ctx->site, &ctx->forest);
talloc_zfree(req);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "Cannot retrieve master domain info\n");
diff --git a/src/providers/ldap/sdap_async.c b/src/providers/ldap/sdap_async.c
index a3da4acf0..b4645738b 100644
--- a/src/providers/ldap/sdap_async.c
+++ b/src/providers/ldap/sdap_async.c
@@ -19,6 +19,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+
#include <ctype.h>
#include "util/util.h"
#include "util/strtonum.h"
@@ -1829,6 +1830,185 @@ sdap_x_deref_search_recv(struct tevent_req *req,
return EOK;
}
+/* ==Security Descriptor (ACL) search=================================== */
+struct sdap_sd_search_state {
+ LDAPControl **ctrls;
+ struct sdap_options *opts;
+ size_t reply_count;
+ struct sysdb_attrs **reply;
+ struct sdap_reply sreply;
+};
+
+static int sdap_sd_search_create_control(struct sdap_handle *sh,
+ int val,
+ LDAPControl **ctrl);
+static int sdap_sd_search_ctrls_destructor(void *ptr);
+static errno_t sdap_sd_search_parse_entry(struct sdap_handle *sh,
+ struct sdap_msg *msg,
+ void *pvt);
+static void sdap_sd_search_done(struct tevent_req *subreq);
+
+struct tevent_req *
+sdap_sd_search_send(TALLOC_CTX *memctx, struct tevent_context *ev,
+ struct sdap_options *opts, struct sdap_handle *sh,
+ const char *base_dn, int sd_flags,
+ const char **attrs, int timeout)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct sdap_sd_search_state *state;
+ int ret;
+
+ req = tevent_req_create(memctx, &state, struct sdap_sd_search_state);
+ if (!req) return NULL;
+
+ state->ctrls = talloc_zero_array(state, LDAPControl *, 2);
+ state->opts = opts;
+ if (state->ctrls == NULL) {
+ ret = EIO;
+ goto fail;
+ }
+ talloc_set_destructor((TALLOC_CTX *) state->ctrls,
+ sdap_sd_search_ctrls_destructor);
+
+ ret = sdap_sd_search_create_control(sh, sd_flags, &state->ctrls[0]);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Could not create SD control\n");
+ ret = EIO;
+ goto fail;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Searching entry [%s] using SD\n", base_dn);
+ subreq = sdap_get_generic_ext_send(state, ev, opts, sh, base_dn,
+ LDAP_SCOPE_BASE, "(objectclass=*)", attrs,
+ false, state->ctrls, NULL, 0, timeout,
+ true, sdap_sd_search_parse_entry,
+ state);
+ if (!subreq) {
+ ret = EIO;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sdap_sd_search_done, req);
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static int sdap_sd_search_create_control(struct sdap_handle *sh,
+ int val,
+ LDAPControl **ctrl)
+{
+ struct berval *sdval;
+ int ret;
+ BerElement *ber = NULL;
+ ber = ber_alloc_t(LBER_USE_DER);
+ if (ber == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "ber_alloc_t failed.\n");
+ return ENOMEM;
+ }
+
+ ret = ber_printf(ber, "{i}", val);
+ if (ret == -1) {
+ DEBUG(SSSDBG_OP_FAILURE, "ber_printf failed.\n");
+ ber_free(ber, 1);
+ return EIO;
+ }
+
+ ret = ber_flatten(ber, &sdval);
+ ber_free(ber, 1);
+ if (ret == -1) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "ber_flatten failed.\n");
+ return EIO;
+ }
+
+ ret = sdap_control_create(sh, LDAP_SERVER_SD_OID, 1, sdval, 1, ctrl);
+ ber_bvfree(sdval);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sdap_control_create failed\n");
+ return ret;
+ }
+
+ return EOK;
+}
+
+static errno_t sdap_sd_search_parse_entry(struct sdap_handle *sh,
+ struct sdap_msg *msg,
+ void *pvt)
+{
+ errno_t ret;
+ struct sysdb_attrs *attrs;
+ struct sdap_sd_search_state *state =
+ talloc_get_type(pvt, struct sdap_sd_search_state);
+
+ bool disable_range_rtrvl = dp_opt_get_bool(state->opts->basic,
+ SDAP_DISABLE_RANGE_RETRIEVAL);
+
+ ret = sdap_parse_entry(state, sh, msg,
+ NULL, 0,
+ &attrs, NULL, disable_range_rtrvl);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "sdap_parse_entry failed [%d]: %s\n", ret, strerror(ret));
+ return ret;
+ }
+
+ ret = add_to_reply(state, &state->sreply, attrs);
+ if (ret != EOK) {
+ talloc_free(attrs);
+ DEBUG(SSSDBG_CRIT_FAILURE, "add_to_reply failed.\n");
+ return ret;
+ }
+
+ /* add_to_reply steals attrs, no need to free them here */
+ return EOK;
+}
+
+static void sdap_sd_search_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+ ret = sdap_get_generic_ext_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "sdap_get_generic_ext_recv failed [%d]: %s\n",
+ ret, sss_strerror(ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int sdap_sd_search_ctrls_destructor(void *ptr)
+{
+ LDAPControl **ctrls = talloc_get_type(ptr, LDAPControl *);;
+ if (ctrls && ctrls[0]) {
+ ldap_control_free(ctrls[0]);
+ }
+
+ return 0;
+}
+
+int sdap_sd_search_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ size_t *_reply_count,
+ struct sysdb_attrs ***_reply)
+{
+ struct sdap_sd_search_state *state = tevent_req_data(req,
+ struct sdap_sd_search_state);
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *_reply_count = state->sreply.reply_count;
+ *_reply = talloc_steal(mem_ctx, state->sreply.reply);
+
+ return EOK;
+}
+
/* ==Attribute scoped search============================================ */
struct sdap_asq_search_state {
struct sdap_attr_map_info *maps;
diff --git a/src/providers/ldap/sdap_async.h b/src/providers/ldap/sdap_async.h
index 593404af3..808254a24 100644
--- a/src/providers/ldap/sdap_async.h
+++ b/src/providers/ldap/sdap_async.h
@@ -219,6 +219,20 @@ sdap_posix_check_send(TALLOC_CTX *memctx, struct tevent_context *ev,
int sdap_posix_check_recv(struct tevent_req *req,
bool *_has_posix);
+struct tevent_req *
+sdap_sd_search_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ struct sdap_handle *sh,
+ const char *base_dn,
+ int sd_flags,
+ const char **attrs,
+ int timeout);
+int sdap_sd_search_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ size_t *reply_count,
+ struct sysdb_attrs ***reply);
+
errno_t
sdap_attrs_add_ldap_attr(struct sysdb_attrs *ldap_attrs,
const char *attr_name,
diff --git a/src/util/sss_ldap.h b/src/util/sss_ldap.h
index f298b2fbb..4172da3e4 100644
--- a/src/util/sss_ldap.h
+++ b/src/util/sss_ldap.h
@@ -55,6 +55,20 @@ int sss_ldap_get_diagnostic_msg(TALLOC_CTX *mem_ctx,
#define LDAP_SERVER_ASQ_OID "1.2.840.113556.1.4.1504"
#endif /* LDAP_SERVER_ASQ_OID */
+#ifndef LDAP_SERVER_SD_OID
+#define LDAP_SERVER_SD_OID "1.2.840.113556.1.4.801"
+#endif /* LDAP_SERVER_SD_OID */
+
+
+/*
+ * The following four flags specify which security descriptor parts to retrieve
+ * during sd_search (see http://msdn.microsoft.com/en-us/library/aa366987.aspx)
+ */
+#define SECINFO_OWNER ( 0x00000001 )
+#define SECINFO_GROUP ( 0x00000002 )
+#define SECINFO_DACL ( 0x00000004 )
+#define SECINFO_SACL ( 0x00000008 )
+
int sss_ldap_control_create(const char *oid, int iscritical,
struct berval *value, int dupval,
LDAPControl **ctrlp);
diff --git a/src/util/util_errors.c b/src/util/util_errors.c
index 90faa3e42..2b99faf74 100644
--- a/src/util/util_errors.c
+++ b/src/util/util_errors.c
@@ -58,6 +58,7 @@ struct err_string error_to_str[] = {
{ "Malformed extra attribute" }, /* ERR_INVALID_EXTRA_ATTR */
{ "Cannot get bus message sender" }, /* ERR_SBUS_GET_SENDER_ERROR */
{ "Bus message has no sender" }, /* ERR_SBUS_NO_SENDER */
+ { "User/Group SIDs not found" }, /* ERR_NO_SIDS */
};
diff --git a/src/util/util_errors.h b/src/util/util_errors.h
index 4d9f16c0a..da518272b 100644
--- a/src/util/util_errors.h
+++ b/src/util/util_errors.h
@@ -80,6 +80,7 @@ enum sssd_errors {
ERR_INVALID_EXTRA_ATTR,
ERR_SBUS_GET_SENDER_ERROR,
ERR_SBUS_NO_SENDER,
+ ERR_NO_SIDS,
ERR_LAST /* ALWAYS LAST */
};