3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov/*
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov SSSD
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov Authors:
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov Yassir Elley <yelley@redhat.com>
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov Copyright (C) 2013 Red Hat
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov This program is free software; you can redistribute it and/or modify
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov it under the terms of the GNU General Public License as published by
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov the Free Software Foundation; either version 3 of the License, or
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov (at your option) any later version.
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov This program is distributed in the hope that it will be useful,
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov but WITHOUT ANY WARRANTY; without even the implied warranty of
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov GNU General Public License for more details.
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov You should have received a copy of the GNU General Public License
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov along with this program. If not, see <http://www.gnu.org/licenses/>.
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov*/
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov/*
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov * This file implements the following pair of *public* functions (see header):
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov * ad_gpo_access_send/recv: provides client-side GPO processing
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov *
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov * This file also implements the following pairs of *private* functions (which
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov * are used by the public functions):
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov * ad_gpo_process_som_send/recv: populate list of gp_som objects
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov * ad_gpo_process_gpo_send/recv: populate list of gp_gpo objects
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov * ad_gpo_process_cse_send/recv: retrieve policy file data
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov */
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov#include <security/pam_modules.h>
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov#include <syslog.h>
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov#include <fcntl.h>
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#include <ini_configobj.h>
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#include "util/util.h"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#include "util/strtonum.h"
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov#include "util/child_common.h"
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov#include "providers/data_provider.h"
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov#include "providers/backend.h"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#include "providers/ad/ad_access.h"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#include "providers/ad/ad_common.h"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#include "providers/ad/ad_domain_info.h"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#include "providers/ad/ad_gpo.h"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#include "providers/ldap/sdap_access.h"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#include "providers/ldap/sdap_async.h"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#include "providers/ldap/sdap.h"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#include "providers/ldap/sdap_idmap.h"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#include "util/util_sss_idmap.h"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#include <ndr.h>
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#include <gen_ndr/security.h>
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov/* == gpo-ldap constants =================================================== */
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define AD_AT_DN "distinguishedName"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define AD_AT_UAC "userAccountControl"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define AD_AT_CONFIG_NC "configurationNamingContext"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define AD_AT_GPLINK "gPLink"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define AD_AT_GPOPTIONS "gpOptions"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define AD_AT_NT_SEC_DESC "nTSecurityDescriptor"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define AD_AT_CN "cn"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define AD_AT_FILE_SYS_PATH "gPCFileSysPath"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define AD_AT_MACHINE_EXT_NAMES "gPCMachineExtensionNames"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define AD_AT_FUNC_VERSION "gPCFunctionalityVersion"
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov#define AD_AT_FLAGS "flags"
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define UAC_WORKSTATION_TRUST_ACCOUNT 0x00001000
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov#define UAC_SERVER_TRUST_ACCOUNT 0x00002000
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define AD_AGP_GUID "edacfd8f-ffb3-11d1-b41d-00a0c968f939"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define AD_AUTHENTICATED_USERS_SID "S-1-5-11"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov/* == gpo-smb constants ==================================================== */
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define SMB_STANDARD_URI "smb://"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define BUFSIZE 65536
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define RIGHTS_SECTION "Privilege Rights"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define ALLOW_LOGON_INTERACTIVE "SeInteractiveLogonRight"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define DENY_LOGON_INTERACTIVE "SeDenyInteractiveLogonRight"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define ALLOW_LOGON_REMOTE_INTERACTIVE "SeRemoteInteractiveLogonRight"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define DENY_LOGON_REMOTE_INTERACTIVE "SeDenyRemoteInteractiveLogonRight"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define ALLOW_LOGON_NETWORK "SeNetworkLogonRight"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define DENY_LOGON_NETWORK "SeDenyNetworkLogonRight"
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov#define ALLOW_LOGON_BATCH "SeBatchLogonRight"
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov#define DENY_LOGON_BATCH "SeDenyBatchLogonRight"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#define ALLOW_LOGON_SERVICE "SeServiceLogonRight"
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov#define DENY_LOGON_SERVICE "SeDenyServiceLogonRight"
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov#define GP_EXT_GUID_SECURITY "{827D319E-6EAC-11D2-A4EA-00C04F79F83A}"
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov#define GP_EXT_GUID_SECURITY_SUFFIX "/Machine/Microsoft/Windows NT/SecEdit/GptTmpl.inf"
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#ifndef SSSD_LIBEXEC_PATH
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#error "SSSD_LIBEXEC_PATH not defined"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#else
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov#define GPO_CHILD SSSD_LIBEXEC_PATH"/gpo_child"
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#endif
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov/* If INI_PARSE_IGNORE_NON_KVP is not defined, use 0 (no effect) */
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#ifndef INI_PARSE_IGNORE_NON_KVP
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov#define INI_PARSE_IGNORE_NON_KVP 0
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#warning INI_PARSE_IGNORE_NON_KVP not defined.
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov#endif
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov/* fd used by the gpo_child process for logging */
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashovint gpo_child_debug_fd = -1;
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov/* == common data structures and declarations ============================= */
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashovstruct gp_som {
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov const char *som_dn;
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov struct gp_gplink **gplink_list;
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov int num_gplinks;
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov};
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashovstruct gp_gplink {
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov const char *gpo_dn;
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov bool enforced;
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov};
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov
96544fca522e66b4f69b4252854a5f672c96f9c4Nikolai Kondrashovstruct gp_gpo {
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov struct security_descriptor *gpo_sd;
96544fca522e66b4f69b4252854a5f672c96f9c4Nikolai Kondrashov const char *gpo_dn;
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov const char *gpo_guid;
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov const char *smb_server;
96544fca522e66b4f69b4252854a5f672c96f9c4Nikolai Kondrashov const char *smb_share;
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov const char *smb_path;
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov const char **gpo_cse_guids;
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov int num_gpo_cse_guids;
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov int gpo_func_version;
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov int gpo_flags;
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov bool send_to_child;
5f4f0428c182a9e77d29b39f3749fce03643ac8dNikolai Kondrashov const char *policy_filename;
3ce85a5f5264e7118beb6524e120fd8b53a13da4Nikolai Kondrashov};
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,
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"
#define GPO_CROND "crond"
#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},
};
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 NULL;
}
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,
"PAM service %s maps to both %s and %s\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 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));
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);
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) {
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);
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);
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 **group_sids,
int 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 *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, "Failed to initialize idmap context.\n");
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;
}
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, "Failed to initialize idmap context.\n");
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, 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,
struct sss_idmap_ctx *idmap_ctx,
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->trustee, idmap_ctx, &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,
struct sss_idmap_ctx *idmap_ctx,
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, idmap_ctx, 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 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;
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;
}
ret = ad_gpo_evaluate_dacl(dacl, idmap_ctx, user_sid, group_sids,
group_size, &access_allowed);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE, "Could not determine if GPO is applicable\n");
continue;
}
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;
}
/*
* 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 *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++) {
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) {
ret = sysdb_gpo_store_gpo_result_setting(domain,
allow_key,
allow_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) {
ret = sysdb_gpo_store_gpo_result_setting(domain,
deny_key,
deny_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,
struct sss_domain_info *domain,
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, &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) {
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;
}
#define GPO_CHILD_LOG_FILE "gpo_child"
static errno_t gpo_child_init(void)
{
return child_debug_init(GPO_CHILD_LOG_FILE, &gpo_child_debug_fd);
}
/*
* 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,
struct sss_domain_info *user_domain,
struct sss_domain_info *host_domain)
{
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,
user_domain, 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;
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 *user;
int gpo_timeout_option;
const char *ad_hostname;
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;
};
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;
/* setup logging for gpo child */
gpo_child_init();
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, "using default right\n");
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->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->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,
enum gpo_access_control_mode gpo_mode,
struct sss_domain_info *user_domain,
struct sss_domain_info *host_domain,
enum gpo_map_type gpo_map_type)
{
errno_t ret;
ret = ad_gpo_perform_hbac_processing(mem_ctx,
gpo_mode,
gpo_map_type,
user,
user_domain,
host_domain);
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;
char *filter;
const char *sam_account_name;
char *domain_dn;
int dp_error;
errno_t ret;
char *server_uri;
LDAPURLDesc *lud;
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) {
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_mode,
state->user_domain,
state->host_domain,
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. */
sam_account_name = dp_opt_get_string(state->opts->basic, SDAP_SASL_AUTHID);
if (sam_account_name == NULL) {
ret = ENOMEM;
goto done;
}
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->host_domain->name, &domain_dn);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE,
"Cannot convert domain name [%s] to base DN [%d]: %s\n",
state->host_domain->name, ret, sss_strerror(ret));
goto done;
}
/* 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) {
ret = ENOMEM;
goto done;
}
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");
ret = EIO;
goto done;
}
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;
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);
if (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_mode,
state->user_domain,
state->host_domain,
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;
}
/* 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 ||
uac & UAC_SERVER_TRUST_ACCOUNT)) {
DEBUG(SSSDBG_OP_FAILURE,
"Invalid userAccountControl (%x) value for machine account.\n",
uac);
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->host_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->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: [%d](%s)\n",
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;
}
}
ret = EOK;
goto done;
}
ret = ad_gpo_filter_gpos_by_dacl(state, state->user, state->user_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 DACKL: [%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;
}
}
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");
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) {
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->user_domain,
state->host_domain);
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;
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 = 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_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 || site == NULL) {
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);
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_OP_FAILURE, "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_OP_FAILURE, "no attrs found for SOM; try next SOM\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 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 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 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->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_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 != 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 != 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 != 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 != 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 = 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;
}
/* 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) {
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;
struct ad_gpo_process_cse_state *state;
state = tevent_req_data(req, struct ad_gpo_process_cse_state);
ret = pipe(pipefd_from_child);
if (ret == -1) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
"pipe failed [%d][%s].\n", errno, strerror(errno));
goto fail;
}
ret = pipe(pipefd_to_child);
if (ret == -1) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
"pipe 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_debug_fd, NULL, 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;
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);
ldap_free_urldesc(lud);
if (!state->ref_domain) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Could not find domain matching [%s]\n",
lud->lud_host);
ret = EIO;
goto done;
}
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;
}