summaryrefslogtreecommitdiffstats
path: root/src/providers/ldap/sdap_idmap.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/providers/ldap/sdap_idmap.c')
-rw-r--r--src/providers/ldap/sdap_idmap.c629
1 files changed, 629 insertions, 0 deletions
diff --git a/src/providers/ldap/sdap_idmap.c b/src/providers/ldap/sdap_idmap.c
new file mode 100644
index 0000000..3795ed6
--- /dev/null
+++ b/src/providers/ldap/sdap_idmap.c
@@ -0,0 +1,629 @@
+/*
+ SSSD
+
+ Authors:
+ Stephen Gallagher <sgallagh@redhat.com>
+
+ Copyright (C) 2012 Red Hat
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "util/util.h"
+#include "util/dlinklist.h"
+#include "providers/ldap/sdap_idmap.h"
+#include "util/util_sss_idmap.h"
+
+static errno_t
+sdap_idmap_get_configured_external_range(struct sdap_idmap_ctx *idmap_ctx,
+ struct sss_idmap_range *range)
+{
+ int int_id;
+ struct sdap_id_ctx *id_ctx;
+ uint32_t min;
+ uint32_t max;
+
+ if (idmap_ctx == NULL) {
+ return EINVAL;
+ }
+
+ id_ctx = idmap_ctx->id_ctx;
+
+ int_id = dp_opt_get_int(id_ctx->opts->basic, SDAP_MIN_ID);
+ if (int_id < 0) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "ldap_min_id must be greater than 0.\n");
+ return EINVAL;
+ }
+ min = int_id;
+
+ int_id = dp_opt_get_int(id_ctx->opts->basic, SDAP_MAX_ID);
+ if (int_id < 0) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "ldap_max_id must be greater than 0.\n");
+ return EINVAL;
+ }
+ max = int_id;
+
+ if ((min == 0 && max != 0) || (min != 0 && max == 0)) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "Both ldap_min_id and ldap_max_id " \
+ "either must be 0 (not set) " \
+ "or positive integers.\n");
+ return EINVAL;
+ }
+
+ if (min == 0 && max == 0) {
+ /* ldap_min_id and ldap_max_id not set, using min_id and max_id */
+ min = id_ctx->be->domain->id_min;
+ max = id_ctx->be->domain->id_max;
+ if (max == 0) {
+ max = UINT32_MAX;
+ }
+ }
+
+ range->min = min;
+ range->max =max;
+
+ return EOK;
+}
+
+static errno_t
+sdap_idmap_add_configured_external_range(struct sdap_idmap_ctx *idmap_ctx)
+{
+ int ret;
+ struct sss_idmap_range range;
+ struct sdap_id_ctx *id_ctx;
+ enum idmap_error_code err;
+
+ ret = sdap_idmap_get_configured_external_range(idmap_ctx, &range);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sdap_idmap_get_configured_external_range failed.\n");
+ return ret;
+ }
+
+ id_ctx = idmap_ctx->id_ctx;
+
+ err = sss_idmap_add_auto_domain_ex(idmap_ctx->map,
+ id_ctx->be->domain->name,
+ id_ctx->be->domain->domain_id, &range,
+ NULL, 0, true, NULL, NULL);
+ if (err != IDMAP_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Could not add domain [%s] to the map: [%d]\n",
+ id_ctx->be->domain->name, err);
+ return EIO;
+ }
+
+ return EOK;
+}
+
+errno_t sdap_idmap_find_new_domain(struct sdap_idmap_ctx *idmap_ctx,
+ const char *dom_name,
+ const char *dom_sid_str)
+{
+ int ret;
+
+ ret = sdap_idmap_add_domain(idmap_ctx,
+ dom_name, dom_sid_str,
+ -1);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Could not add new domain [%s]\n", dom_name);
+ return ret;
+ }
+
+ return EOK;
+}
+
+errno_t
+sdap_idmap_init(TALLOC_CTX *mem_ctx,
+ struct sdap_id_ctx *id_ctx,
+ struct sdap_idmap_ctx **_idmap_ctx)
+{
+ errno_t ret;
+ TALLOC_CTX *tmp_ctx;
+ enum idmap_error_code err;
+ size_t i;
+ struct ldb_result *res;
+ const char *dom_name;
+ const char *sid_str;
+ id_t slice_num;
+ id_t idmap_lower;
+ id_t idmap_upper;
+ id_t rangesize;
+ bool autorid_mode;
+ int extra_slice_init;
+ struct sdap_idmap_ctx *idmap_ctx = NULL;
+
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) return ENOMEM;
+
+ idmap_ctx = talloc_zero(tmp_ctx, struct sdap_idmap_ctx);
+ if (!idmap_ctx) {
+ ret = ENOMEM;
+ goto done;
+ }
+ idmap_ctx->id_ctx = id_ctx;
+ idmap_ctx->find_new_domain = sdap_idmap_find_new_domain;
+
+ idmap_lower = dp_opt_get_int(idmap_ctx->id_ctx->opts->basic,
+ SDAP_IDMAP_LOWER);
+ idmap_upper = dp_opt_get_int(idmap_ctx->id_ctx->opts->basic,
+ SDAP_IDMAP_UPPER);
+ rangesize = dp_opt_get_int(idmap_ctx->id_ctx->opts->basic,
+ SDAP_IDMAP_RANGESIZE);
+ autorid_mode = dp_opt_get_bool(idmap_ctx->id_ctx->opts->basic,
+ SDAP_IDMAP_AUTORID_COMPAT);
+ extra_slice_init = dp_opt_get_int(idmap_ctx->id_ctx->opts->basic,
+ SDAP_IDMAP_EXTRA_SLICE_INIT);
+
+ /* Validate that the values make sense */
+ if (rangesize <= 0
+ || idmap_upper <= idmap_lower
+ || (idmap_upper-idmap_lower) < rangesize)
+ {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Invalid settings for range selection: "
+ "[%"SPRIid"][%"SPRIid"][%"SPRIid"]\n",
+ idmap_lower, idmap_upper, rangesize);
+ ret = EINVAL;
+ goto done;
+ }
+
+ if (((idmap_upper - idmap_lower) % rangesize) != 0) {
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Range size does not divide evenly. Uppermost range will "
+ "not be used\n");
+ }
+
+ /* Initialize the map */
+ err = sss_idmap_init(sss_idmap_talloc, idmap_ctx,
+ sss_idmap_talloc_free,
+ &idmap_ctx->map);
+ if (err != IDMAP_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Could not initialize the ID map: [%s]\n",
+ idmap_error_string(err));
+ if (err == IDMAP_OUT_OF_MEMORY) {
+ ret = ENOMEM;
+ } else {
+ ret = EINVAL;
+ }
+ goto done;
+ }
+
+ err = sss_idmap_ctx_set_autorid(idmap_ctx->map, autorid_mode);
+ err |= sss_idmap_ctx_set_lower(idmap_ctx->map, idmap_lower);
+ err |= sss_idmap_ctx_set_upper(idmap_ctx->map, idmap_upper);
+ err |= sss_idmap_ctx_set_rangesize(idmap_ctx->map, rangesize);
+ err |= sss_idmap_ctx_set_extra_slice_init(idmap_ctx->map, extra_slice_init);
+ if (err != IDMAP_SUCCESS) {
+ /* This should never happen */
+ DEBUG(SSSDBG_CRIT_FAILURE, "sss_idmap_ctx corrupted\n");
+ ret = EIO;
+ goto done;
+ }
+
+
+ /* Setup range for externally managed IDs, i.e. IDs are read from the
+ * ldap_user_uid_number and ldap_group_gid_number attributes. */
+ if (!dp_opt_get_bool(idmap_ctx->id_ctx->opts->basic, SDAP_ID_MAPPING)) {
+ ret = sdap_idmap_add_configured_external_range(idmap_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sdap_idmap_add_configured_external_range failed.\n");
+ goto done;
+ }
+ }
+
+ /* Read in any existing mappings from the cache */
+ ret = sysdb_idmap_get_mappings(tmp_ctx, id_ctx->be->domain, &res);
+ if (ret != EOK && ret != ENOENT) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Could not read ID mappings from the cache: [%s]\n",
+ strerror(ret));
+ goto done;
+ }
+
+ if (ret == EOK) {
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Initializing [%d] domains for ID-mapping\n", res->count);
+
+ for (i = 0; i < res->count; i++) {
+ dom_name = ldb_msg_find_attr_as_string(res->msgs[i],
+ SYSDB_NAME,
+ NULL);
+ if (!dom_name) {
+ /* This should never happen */
+ ret = EINVAL;
+ goto done;
+ }
+
+ sid_str = ldb_msg_find_attr_as_string(res->msgs[i],
+ SYSDB_IDMAP_SID_ATTR,
+ NULL);
+ if (!sid_str) {
+ /* This should never happen */
+ ret = EINVAL;
+ goto done;
+ }
+
+ slice_num = ldb_msg_find_attr_as_int(res->msgs[i],
+ SYSDB_IDMAP_SLICE_ATTR,
+ -1);
+ if (slice_num == -1) {
+ /* This should never happen */
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = sdap_idmap_add_domain(idmap_ctx, dom_name,
+ sid_str, slice_num);
+ if (ret != EOK) {
+ if (ret == EINVAL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Could not add domain [%s][%s][%"SPRIid"] "
+ "to ID map: [%s] "
+ "Unexpected ID map configuration. Check ID map related "
+ "parameters in sssd.conf and remove the sssd cache if "
+ "some of these parameters were changed recently.\n",
+ dom_name, sid_str, slice_num, strerror(ret));
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Could not add domain [%s][%s][%"SPRIid"] "
+ "to ID map: [%s]\n",
+ dom_name, sid_str, slice_num, strerror(ret));
+ }
+
+ goto done;
+ }
+ }
+ } else {
+ /* This is the first time we're setting up id-mapping
+ * Store the default domain as slice 0
+ */
+ dom_name = dp_opt_get_string(idmap_ctx->id_ctx->opts->basic, SDAP_IDMAP_DEFAULT_DOMAIN);
+ if (!dom_name) {
+ /* If it's not explicitly specified, use the SSSD domain name */
+ dom_name = idmap_ctx->id_ctx->be->domain->name;
+ ret = dp_opt_set_string(idmap_ctx->id_ctx->opts->basic,
+ SDAP_IDMAP_DEFAULT_DOMAIN,
+ dom_name);
+ if (ret != EOK) goto done;
+ }
+
+ sid_str = dp_opt_get_string(idmap_ctx->id_ctx->opts->basic, SDAP_IDMAP_DEFAULT_DOMAIN_SID);
+ if (sid_str) {
+ struct sss_domain_info *domain = idmap_ctx->id_ctx->be->domain;
+ domain->domain_id = talloc_strdup(domain, sid_str);
+ if (domain->domain_id == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* Set the default domain as slice 0 */
+ ret = sdap_idmap_add_domain(idmap_ctx, dom_name,
+ sid_str, 0);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Could not add domain [%s][%s][%u] to ID map: [%s]\n",
+ dom_name, sid_str, 0, strerror(ret));
+ goto done;
+ }
+ } else {
+ if (dp_opt_get_bool(idmap_ctx->id_ctx->opts->basic, SDAP_IDMAP_AUTORID_COMPAT)) {
+ /* In autorid compatibility mode, we MUST have a slice 0 */
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "WARNING: Autorid compatibility mode selected, "
+ "but %s is not set. UID/GID values may differ "
+ "between clients.\n",
+ idmap_ctx->id_ctx->opts->basic[SDAP_IDMAP_DEFAULT_DOMAIN_SID].opt_name);
+ }
+ /* Otherwise, we'll just fall back to hash values as they are seen */
+ }
+ }
+
+ *_idmap_ctx = talloc_steal(mem_ctx, idmap_ctx);
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+errno_t
+sdap_idmap_add_domain(struct sdap_idmap_ctx *idmap_ctx,
+ const char *dom_name,
+ const char *dom_sid,
+ id_t slice)
+{
+ errno_t ret;
+ struct sss_idmap_range range;
+ enum idmap_error_code err;
+ id_t idmap_upper;
+ bool external_mapping = true;
+
+ ret = sss_idmap_ctx_get_upper(idmap_ctx->map, &idmap_upper);
+ if (ret != IDMAP_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to get upper bound of available ID range.\n");
+ ret = EIO;
+ goto done;
+ }
+
+ if (dp_opt_get_bool(idmap_ctx->id_ctx->opts->basic, SDAP_ID_MAPPING)) {
+ external_mapping = false;
+ ret = sss_idmap_calculate_range(idmap_ctx->map, dom_sid, &slice, &range);
+ if (ret != IDMAP_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to calculate range for domain [%s]: [%d]\n", dom_name,
+ ret);
+ ret = EIO;
+ goto done;
+ }
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Adding domain [%s] as slice [%"SPRIid"]\n", dom_sid, slice);
+
+ if (range.max > idmap_upper) {
+ /* This should never happen */
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "BUG: Range maximum exceeds the global maximum: "
+ "%u > %"SPRIid"\n", range.max, idmap_upper);
+ ret = EINVAL;
+ goto done;
+ }
+ } else {
+ ret = sdap_idmap_get_configured_external_range(idmap_ctx, &range);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sdap_idmap_get_configured_external_range failed.\n");
+ return ret;
+ }
+ }
+
+ /* Add this domain to the map */
+ err = sss_idmap_add_auto_domain_ex(idmap_ctx->map, dom_name, dom_sid,
+ &range, NULL, 0, external_mapping,
+ NULL, NULL);
+ if (err != IDMAP_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Could not add domain [%s] to the map: [%d]\n",
+ dom_name, err);
+ ret = EIO;
+ goto done;
+ }
+
+ /* If algorithmic mapping is used add this domain to the SYSDB cache so it
+ * will survive reboot */
+ if (!external_mapping) {
+ ret = sysdb_idmap_store_mapping(idmap_ctx->id_ctx->be->domain,
+ dom_name, dom_sid,
+ slice);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sysdb_idmap_store_mapping failed.\n");
+ goto done;
+ }
+ }
+
+done:
+ return ret;
+}
+
+errno_t
+sdap_idmap_get_dom_sid_from_object(TALLOC_CTX *mem_ctx,
+ const char *object_sid,
+ char **dom_sid_str)
+{
+ const char *p;
+ long long a;
+ size_t c;
+ char *endptr;
+
+ if (object_sid == NULL
+ || strncmp(object_sid, DOM_SID_PREFIX, DOM_SID_PREFIX_LEN) != 0) {
+ return EINVAL;
+ }
+
+ p = object_sid + DOM_SID_PREFIX_LEN;
+ c = 0;
+
+ do {
+ errno = 0;
+ a = strtoull(p, &endptr, 10);
+ if (errno != 0 || a > UINT32_MAX) {
+ return EINVAL;
+ }
+
+ if (*endptr == '-') {
+ p = endptr + 1;
+ } else {
+ return EINVAL;
+ }
+ c++;
+ } while(c < 3);
+
+ /* If we made it here, we are now one character past
+ * the last hyphen in the object-sid.
+ * Copy the dom-sid substring.
+ */
+ *dom_sid_str = talloc_strndup(mem_ctx, object_sid,
+ (endptr-object_sid));
+ if (!*dom_sid_str) return ENOMEM;
+
+ return EOK;
+}
+
+errno_t
+sdap_idmap_sid_to_unix(struct sdap_idmap_ctx *idmap_ctx,
+ const char *sid_str,
+ id_t *id)
+{
+ errno_t ret;
+ enum idmap_error_code err;
+ char *dom_sid_str = NULL;
+
+ /* Convert the SID into a UNIX ID */
+ err = sss_idmap_sid_to_unix(idmap_ctx->map,
+ sid_str,
+ (uint32_t *)id);
+ switch (err) {
+ case IDMAP_SUCCESS:
+ break;
+ case IDMAP_NO_DOMAIN:
+ /* This is the first time we've seen this domain
+ * Create a new domain for it. We'll use the dom-sid
+ * as the domain name for now, since we don't have
+ * any way to get the real name.
+ */
+ ret = sdap_idmap_get_dom_sid_from_object(NULL, sid_str,
+ &dom_sid_str);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Could not parse domain SID from [%s]\n", sid_str);
+ goto done;
+ }
+
+ ret = idmap_ctx->find_new_domain(idmap_ctx, dom_sid_str, dom_sid_str);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Could not add new domain for sid [%s]\n", sid_str);
+ goto done;
+ }
+
+ /* Now try converting to a UNIX ID again */
+ err = sss_idmap_sid_to_unix(idmap_ctx->map,
+ sid_str,
+ (uint32_t *)id);
+ if (err != IDMAP_SUCCESS) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Could not convert objectSID [%s] to a UNIX ID\n",
+ sid_str);
+ ret = EIO;
+ goto done;
+ }
+ break;
+ case IDMAP_BUILTIN_SID:
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Object SID [%s] is a built-in one.\n", sid_str);
+ /* ENOTSUP indicates built-in SID */
+ ret = ENOTSUP;
+ goto done;
+ break;
+ case IDMAP_NO_RANGE:
+ DEBUG(SSSDBG_IMPORTANT_INFO,
+ "Object SID [%s] has a RID that is larger than the "
+ "ldap_idmap_range_size. See the \"ID MAPPING\" section of "
+ "sssd-ad(5) for an explanation of how to resolve this issue.\n",
+ sid_str);
+ /* Fall through intentionally */
+ SSS_ATTRIBUTE_FALLTHROUGH;
+ default:
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Could not convert objectSID [%s] to a UNIX ID\n",
+ sid_str);
+ ret = EIO;
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ talloc_free(dom_sid_str);
+ return ret;
+}
+
+bool sdap_idmap_domain_has_algorithmic_mapping(struct sdap_idmap_ctx *ctx,
+ const char *dom_name,
+ const char *dom_sid)
+{
+ enum idmap_error_code err;
+ bool has_algorithmic_mapping;
+ char *new_dom_sid;
+ int ret;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ if (dp_opt_get_bool(ctx->id_ctx->opts->basic, SDAP_ID_MAPPING)
+ && dp_target_enabled(ctx->id_ctx->be->provider, "ldap", DPT_ID)) {
+ return true;
+ }
+
+ err = sss_idmap_domain_has_algorithmic_mapping(ctx->map, dom_sid,
+ &has_algorithmic_mapping);
+ switch (err){
+ case IDMAP_SUCCESS:
+ return has_algorithmic_mapping;
+ case IDMAP_SID_INVALID: /* FALLTHROUGH */
+ case IDMAP_SID_UNKNOWN: /* FALLTHROUGH */
+ case IDMAP_NO_DOMAIN: /* FALLTHROUGH */
+ /* continue with idmap_domain_by_name */
+ break;
+ default:
+ return false;
+ }
+
+ err = sss_idmap_domain_by_name_has_algorithmic_mapping(ctx->map,
+ dom_name,
+ &has_algorithmic_mapping);
+ if (err == IDMAP_SUCCESS) {
+ return has_algorithmic_mapping;
+ } else if (err != IDMAP_NAME_UNKNOWN && err != IDMAP_NO_DOMAIN) {
+ return false;
+ }
+
+ /* If there is no SID, e.g. IPA without enabled trust support, we cannot
+ * have algorithmic mapping */
+ if (dom_sid == NULL) {
+ return false;
+ }
+
+ /* This is the first time we've seen this domain
+ * Create a new domain for it. We'll use the dom-sid
+ * as the domain name for now, since we don't have
+ * any way to get the real name.
+ */
+
+ if (is_domain_sid(dom_sid)) {
+ new_dom_sid = discard_const(dom_sid);
+ } else {
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
+ return false;
+ }
+
+ ret = sdap_idmap_get_dom_sid_from_object(tmp_ctx, dom_sid,
+ &new_dom_sid);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Could not parse domain SID from [%s]\n", dom_sid);
+ talloc_free(tmp_ctx);
+ return false;
+ }
+ }
+
+ ret = ctx->find_new_domain(ctx, dom_name, new_dom_sid);
+ talloc_free(tmp_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Could not add new domain for sid [%s]\n", dom_sid);
+ return false;
+ }
+
+ err = sss_idmap_domain_has_algorithmic_mapping(ctx->map, dom_sid,
+ &has_algorithmic_mapping);
+ if (err == IDMAP_SUCCESS) {
+ return has_algorithmic_mapping;
+ }
+
+ return false;
+}