diff options
Diffstat (limited to 'src/lib/idmap/sss_idmap.c')
-rw-r--r-- | src/lib/idmap/sss_idmap.c | 1613 |
1 files changed, 1613 insertions, 0 deletions
diff --git a/src/lib/idmap/sss_idmap.c b/src/lib/idmap/sss_idmap.c new file mode 100644 index 0000000..7ad0565 --- /dev/null +++ b/src/lib/idmap/sss_idmap.c @@ -0,0 +1,1613 @@ +/* + SSSD + + ID-mapping library + + Authors: + Sumit Bose <sbose@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 <string.h> +#include <stdio.h> +#include <errno.h> +#include <inttypes.h> + +#include "lib/idmap/sss_idmap.h" +#include "lib/idmap/sss_idmap_private.h" +#include "shared/murmurhash3.h" + +#define SID_FMT "%s-%"PRIu32 +#define SID_STR_MAX_LEN 1024 + +/* Hold all parameters for unix<->sid mapping relevant for + * given slice. */ +struct idmap_range_params { + uint32_t min_id; + uint32_t max_id; + char *range_id; + + uint32_t first_rid; + struct idmap_range_params *next; +}; + +struct idmap_domain_info { + char *name; + char *sid; + struct idmap_range_params range_params; + struct idmap_domain_info *next; + bool external_mapping; + + struct idmap_range_params *helpers; + bool auto_add_ranges; + bool helpers_owner; + + idmap_store_cb cb; + void *pvt; +}; + +static void *default_alloc(size_t size, void *pvt) +{ + return malloc(size); +} + +static void default_free(void *ptr, void *pvt) +{ + free(ptr); +} + +static char *idmap_strdup(struct sss_idmap_ctx *ctx, const char *str) +{ + char *new = NULL; + size_t len; + + CHECK_IDMAP_CTX(ctx, NULL); + + len = strlen(str) + 1; + + new = ctx->alloc_func(len, ctx->alloc_pvt); + if (new == NULL) { + return NULL; + } + + memcpy(new, str, len); + + return new; +} + +static bool ranges_eq(const struct idmap_range_params *a, + const struct idmap_range_params *b) +{ + if (a == NULL || b == NULL) { + return false; + } + + if (a->first_rid == b->first_rid + && a->min_id == b->min_id + && a->max_id == b->max_id) { + return true; + } + + return false; +} + +static enum idmap_error_code +construct_range(struct sss_idmap_ctx *ctx, + const struct idmap_range_params *src, + char *id, + struct idmap_range_params **_dst) +{ + struct idmap_range_params *dst; + + if (src == NULL || id == NULL || _dst == NULL) { + return IDMAP_ERROR; + } + + dst = ctx->alloc_func(sizeof(struct idmap_range_params), ctx->alloc_pvt); + if (dst == NULL) { + return IDMAP_OUT_OF_MEMORY; + } + + dst->min_id = src->min_id; + dst->max_id = src->max_id; + dst->first_rid = src->first_rid; + dst->next = NULL; + dst->range_id = id; + + *_dst = dst; + return IDMAP_SUCCESS; +} + +static bool id_is_in_range(uint32_t id, + struct idmap_range_params *rp, + uint32_t *rid) +{ + if (id == 0 || rp == NULL) { + return false; + } + + if (id >= rp->min_id && id <= rp->max_id) { + if (rid != NULL) { + *rid = rp->first_rid + (id - rp->min_id); + } + + return true; + } + + return false; +} + +const char *idmap_error_string(enum idmap_error_code err) +{ + switch (err) { + case IDMAP_SUCCESS: + return "IDMAP operation successful"; + break; + case IDMAP_NOT_IMPLEMENTED: + return "IDMAP Function is not yet implemented"; + break; + case IDMAP_ERROR: + return "IDMAP general error"; + break; + case IDMAP_OUT_OF_MEMORY: + return "IDMAP operation ran out of memory"; + break; + case IDMAP_NO_DOMAIN: + return "IDMAP domain not found"; + break; + case IDMAP_CONTEXT_INVALID: + return "IDMAP context is invalid"; + break; + case IDMAP_SID_INVALID: + return "IDMAP SID is invalid"; + break; + case IDMAP_SID_UNKNOWN: + return "IDMAP SID not found"; + break; + case IDMAP_NO_RANGE: + return "IDMAP range not found"; + break; + case IDMAP_BUILTIN_SID: + return "IDMAP SID from BUILTIN domain"; + break; + case IDMAP_OUT_OF_SLICES: + return "IDMAP not more free slices"; + break; + case IDMAP_COLLISION: + return "IDMAP new range collides with existing one"; + break; + case IDMAP_EXTERNAL: + return "IDMAP ID managed externally"; + break; + case IDMAP_NAME_UNKNOWN: + return "IDMAP domain with the given name not found"; + break; + default: + return "IDMAP unknown error code"; + } +} + +bool is_domain_sid(const char *sid) +{ + const char *p; + long long a; + char *endptr; + size_t c; + + if (sid == NULL || strncmp(sid, DOM_SID_PREFIX, DOM_SID_PREFIX_LEN) != 0) { + return false; + } + + p = sid + DOM_SID_PREFIX_LEN; + c = 0; + + do { + errno = 0; + a = strtoull(p, &endptr, 10); + if (errno != 0 || a > UINT32_MAX) { + return false; + } + + if (*endptr == '-') { + p = endptr + 1; + } else if (*endptr != '\0') { + return false; + } + c++; + } while(c < 3 && *endptr != '\0'); + + if (c != 3 || *endptr != '\0') { + return false; + } + + return true; +} + +enum idmap_error_code sss_idmap_init(idmap_alloc_func *alloc_func, + void *alloc_pvt, + idmap_free_func *free_func, + struct sss_idmap_ctx **_ctx) +{ + struct sss_idmap_ctx *ctx; + + if (alloc_func == NULL) { + alloc_func = default_alloc; + } + + ctx = alloc_func(sizeof(struct sss_idmap_ctx), alloc_pvt); + if (ctx == NULL) { + return IDMAP_OUT_OF_MEMORY; + } + memset(ctx, 0, sizeof(struct sss_idmap_ctx)); + + ctx->alloc_func = alloc_func; + ctx->alloc_pvt = alloc_pvt; + ctx->free_func = (free_func == NULL) ? default_free : free_func; + + /* Set default values. */ + ctx->idmap_opts.autorid_mode = SSS_IDMAP_DEFAULT_AUTORID; + ctx->idmap_opts.idmap_lower = SSS_IDMAP_DEFAULT_LOWER; + ctx->idmap_opts.idmap_upper = SSS_IDMAP_DEFAULT_UPPER; + ctx->idmap_opts.rangesize = SSS_IDMAP_DEFAULT_RANGESIZE; + ctx->idmap_opts.extra_slice_init = SSS_IDMAP_DEFAULT_EXTRA_SLICE_INIT; + + *_ctx = ctx; + + return IDMAP_SUCCESS; +} + +static void free_helpers(struct sss_idmap_ctx *ctx, + struct idmap_range_params *helpers, + bool helpers_owner) +{ + struct idmap_range_params *it = helpers; + struct idmap_range_params *tmp; + + if (helpers_owner == false) { + return; + } + + while (it != NULL) { + tmp = it->next; + + ctx->free_func(it->range_id, ctx->alloc_pvt); + ctx->free_func(it, ctx->alloc_pvt); + + it = tmp; + } +} + +static struct idmap_range_params* +get_helper_by_id(struct idmap_range_params *helpers, const char *id) +{ + struct idmap_range_params *it; + + for (it = helpers; it != NULL; it = it->next) { + if (strcmp(it->range_id, id) == 0) { + return it; + } + } + + return NULL; +} + +static void sss_idmap_free_domain(struct sss_idmap_ctx *ctx, + struct idmap_domain_info *dom) +{ + if (ctx == NULL || dom == NULL) { + return; + } + + ctx->free_func(dom->range_params.range_id, ctx->alloc_pvt); + + free_helpers(ctx, dom->helpers, dom->helpers_owner); + + ctx->free_func(dom->name, ctx->alloc_pvt); + ctx->free_func(dom->sid, ctx->alloc_pvt); + ctx->free_func(dom, ctx->alloc_pvt); +} + +enum idmap_error_code sss_idmap_free(struct sss_idmap_ctx *ctx) +{ + struct idmap_domain_info *dom; + struct idmap_domain_info *next; + + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + + next = ctx->idmap_domain_info; + while (next) { + dom = next; + next = dom->next; + sss_idmap_free_domain(ctx, dom); + } + + ctx->free_func(ctx, ctx->alloc_pvt); + + return IDMAP_SUCCESS; +} + +static enum idmap_error_code sss_idmap_free_ptr(struct sss_idmap_ctx *ctx, + void *ptr) +{ + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + + if (ptr != NULL) { + ctx->free_func(ptr, ctx->alloc_pvt); + } + + return IDMAP_SUCCESS; +} + +enum idmap_error_code sss_idmap_free_sid(struct sss_idmap_ctx *ctx, + char *sid) +{ + return sss_idmap_free_ptr(ctx, sid); +} + +enum idmap_error_code sss_idmap_free_dom_sid(struct sss_idmap_ctx *ctx, + struct sss_dom_sid *dom_sid) +{ + return sss_idmap_free_ptr(ctx, dom_sid); +} + +enum idmap_error_code sss_idmap_free_smb_sid(struct sss_idmap_ctx *ctx, + struct dom_sid *smb_sid) +{ + return sss_idmap_free_ptr(ctx, smb_sid); +} + +enum idmap_error_code sss_idmap_free_bin_sid(struct sss_idmap_ctx *ctx, + uint8_t *bin_sid) +{ + return sss_idmap_free_ptr(ctx, bin_sid); +} + +static bool check_overlap(struct idmap_range_params *range, + id_t min, id_t max) +{ + return ((range->min_id <= min && range->max_id >= max) + || (range->min_id >= min && range->min_id <= max) + || (range->max_id >= min && range->max_id <= max)); +} + +static bool check_dom_overlap(struct idmap_range_params *prim_range, + /* struct idmap_range_params *sec_ranges, */ + id_t min, + id_t max) +{ + return check_overlap(prim_range, min, max); +} + +enum idmap_error_code sss_idmap_calculate_range(struct sss_idmap_ctx *ctx, + const char *range_id, + id_t *slice_num, + struct sss_idmap_range *_range) +{ + id_t max_slices; + id_t orig_slice; + id_t new_slice = 0; + id_t min; + id_t max; + id_t idmap_lower; + id_t idmap_upper; + id_t rangesize; + bool autorid_mode; + uint32_t hash_val; + struct idmap_domain_info *dom; + + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + + idmap_lower = ctx->idmap_opts.idmap_lower; + idmap_upper = ctx->idmap_opts.idmap_upper; + rangesize = ctx->idmap_opts.rangesize; + autorid_mode = ctx->idmap_opts.autorid_mode; + + max_slices = (idmap_upper - idmap_lower) / rangesize; + + if (slice_num && *slice_num != -1) { + /* The slice is being set explicitly. + * This may happen at system startup when we're loading + * previously-determined slices. In the future, we may also + * permit configuration to select the slice for a domain + * explicitly. + */ + new_slice = *slice_num; + min = (rangesize * new_slice) + idmap_lower; + max = min + rangesize - 1; + for (dom = ctx->idmap_domain_info; dom != NULL; dom = dom->next) { + if (check_dom_overlap(&dom->range_params,min, max)) { + /* This range overlaps one already registered + * Fail, because the slice was manually configured + */ + return IDMAP_COLLISION; + } + } + } else { + /* If slice is -1, we're being asked to pick a new slice */ + + if (autorid_mode) { + /* In autorid compatibility mode, always start at 0 and find the + * first free value. + */ + orig_slice = 0; + } else { + /* Hash the range identifier string */ + hash_val = murmurhash3(range_id, strlen(range_id), 0xdeadbeef); + + /* Now get take the modulus of the hash val and the max_slices + * to determine its optimal position in the range. + */ + new_slice = hash_val % max_slices; + orig_slice = new_slice; + } + + min = (rangesize * new_slice) + idmap_lower; + max = min + rangesize - 1; + /* Verify that this slice is not already in use */ + do { + for (dom = ctx->idmap_domain_info; dom != NULL; dom = dom->next) { + + if (check_dom_overlap(&dom->range_params, + min, max)) { + /* This range overlaps one already registered + * We'll try the next available slot + */ + new_slice++; + if (new_slice >= max_slices) { + /* loop around to the beginning if necessary */ + new_slice = 0; + } + + min = (rangesize * new_slice) + idmap_lower; + max = min + rangesize - 1; + break; + } + } + + /* Keep trying until dom is NULL (meaning we got to the end + * without matching) or we have run out of slices and gotten + * back to the first one we tried. + */ + } while (dom && new_slice != orig_slice); + + if (dom) { + /* We looped all the way through and found no empty slots */ + return IDMAP_OUT_OF_SLICES; + } + } + + _range->min = (rangesize * new_slice) + idmap_lower; + _range->max = _range->min + rangesize - 1; + + if (slice_num) { + *slice_num = new_slice; + } + + return IDMAP_SUCCESS; +} + +enum idmap_error_code sss_idmap_check_collision_ex(const char *o_name, + const char *o_sid, + struct sss_idmap_range *o_range, + uint32_t o_first_rid, + const char *o_range_id, + bool o_external_mapping, + const char *n_name, + const char *n_sid, + struct sss_idmap_range *n_range, + uint32_t n_first_rid, + const char *n_range_id, + bool n_external_mapping) +{ + bool names_equal; + bool sids_equal; + + /* TODO: if both ranges have the same ID check if an update is + * needed. */ + + /* Check if ID ranges overlap. + * ID ranges with external mapping may overlap. */ + if ((!n_external_mapping && !o_external_mapping) + && ((n_range->min >= o_range->min + && n_range->min <= o_range->max) + || (n_range->max >= o_range->min + && n_range->max <= o_range->max))) { + return IDMAP_COLLISION; + } + + names_equal = (strcasecmp(n_name, o_name) == 0); + sids_equal = ((n_sid == NULL && o_sid == NULL) + || (n_sid != NULL && o_sid != NULL + && strcasecmp(n_sid, o_sid) == 0)); + + /* check if domain name and SID are consistent */ + if ((names_equal && !sids_equal) || (!names_equal && sids_equal)) { + return IDMAP_COLLISION; + } + + /* check if external_mapping is consistent */ + if (names_equal && sids_equal + && n_external_mapping != o_external_mapping) { + return IDMAP_COLLISION; + } + + /* check if RID ranges overlap */ + if (names_equal && sids_equal + && n_external_mapping == false + && n_first_rid >= o_first_rid + && n_first_rid <= o_first_rid + (o_range->max - o_range->min)) { + return IDMAP_COLLISION; + } + + return IDMAP_SUCCESS; +} + +enum idmap_error_code sss_idmap_check_collision(struct sss_idmap_ctx *ctx, + char *n_name, char *n_sid, + struct sss_idmap_range *n_range, + uint32_t n_first_rid, + char *n_range_id, + bool n_external_mapping) +{ + struct idmap_domain_info *dom; + enum idmap_error_code err; + struct sss_idmap_range range; + + for (dom = ctx->idmap_domain_info; dom != NULL; dom = dom->next) { + + range.min = dom->range_params.min_id; + range.max = dom->range_params.max_id; + + err = sss_idmap_check_collision_ex(dom->name, dom->sid, + &range, + dom->range_params.first_rid, + dom->range_params.range_id, + dom->external_mapping, + n_name, n_sid, n_range, n_first_rid, + n_range_id, n_external_mapping); + if (err != IDMAP_SUCCESS) { + return err; + } + } + return IDMAP_SUCCESS; +} + +static enum +idmap_error_code dom_check_collision(struct idmap_domain_info *dom_list, + struct idmap_domain_info *new_dom) +{ + struct idmap_domain_info *dom; + enum idmap_error_code err; + struct sss_idmap_range range; + struct sss_idmap_range new_dom_range = { new_dom->range_params.min_id, + new_dom->range_params.max_id }; + + for (dom = dom_list; dom != NULL; dom = dom->next) { + range.min = dom->range_params.min_id; + range.max = dom->range_params.max_id; + + err = sss_idmap_check_collision_ex(dom->name, dom->sid, + &range, + dom->range_params.first_rid, + dom->range_params.range_id, + dom->external_mapping, + new_dom->name, new_dom->sid, + &new_dom_range, + new_dom->range_params.first_rid, + new_dom->range_params.range_id, + new_dom->external_mapping); + if (err != IDMAP_SUCCESS) { + return err; + } + } + return IDMAP_SUCCESS; +} + +static char* +generate_sec_slice_name(struct sss_idmap_ctx *ctx, + const char *domain_sid, uint32_t rid) +{ + const char *SEC_SLICE_NAME_FMT = "%s-%"PRIu32; + char *slice_name; + int len, len2; + + len = snprintf(NULL, 0, SEC_SLICE_NAME_FMT, domain_sid, rid); + if (len <= 0) { + return NULL; + } + + slice_name = ctx->alloc_func(len + 1, ctx->alloc_pvt); + if (slice_name == NULL) { + return NULL; + } + + len2 = snprintf(slice_name, len + 1, SEC_SLICE_NAME_FMT, domain_sid, + rid); + if (len != len2) { + ctx->free_func(slice_name, ctx->alloc_pvt); + return NULL; + } + + return slice_name; +} + +static enum idmap_error_code +generate_slice(struct sss_idmap_ctx *ctx, char *slice_name, uint32_t first_rid, + struct idmap_range_params **_slice) +{ + struct idmap_range_params *slice; + struct sss_idmap_range tmp_range; + enum idmap_error_code err; + + slice = ctx->alloc_func(sizeof(struct idmap_range_params), ctx->alloc_pvt); + if (slice == NULL) { + return IDMAP_OUT_OF_MEMORY; + } + + slice->next = NULL; + + err = sss_idmap_calculate_range(ctx, slice_name, NULL, &tmp_range); + if (err != IDMAP_SUCCESS) { + ctx->free_func(slice, ctx->alloc_pvt); + return err; + } + + slice->min_id = tmp_range.min; + slice->max_id = tmp_range.max; + slice->range_id = slice_name; + slice->first_rid = first_rid; + + *_slice = slice; + return IDMAP_SUCCESS; +} + +static enum idmap_error_code +get_helpers(struct sss_idmap_ctx *ctx, + const char *domain_sid, + uint32_t first_rid, + struct idmap_range_params **_sec_slices) +{ + struct idmap_range_params *prev = NULL; + struct idmap_range_params *sec_slices = NULL; + static enum idmap_error_code err; + struct idmap_range_params *slice; + char *secondary_name; + + for (int i = 0; i < ctx->idmap_opts.extra_slice_init; i++) { + secondary_name = generate_sec_slice_name(ctx, domain_sid, first_rid); + if (secondary_name == NULL) { + err = IDMAP_OUT_OF_MEMORY; + goto fail; + } + + err = generate_slice(ctx, secondary_name, first_rid, &slice); + if (err != IDMAP_SUCCESS) { + goto fail; + } + + first_rid += ctx->idmap_opts.rangesize; + + if (prev != NULL) { + prev->next = slice; + } + + if (sec_slices == NULL) { + sec_slices = slice; + } + + prev = slice; + } + + *_sec_slices = sec_slices; + return IDMAP_SUCCESS; + +fail: + ctx->free_func(secondary_name, ctx->alloc_pvt); + + /* Free already generated helpers. */ + free_helpers(ctx, sec_slices, true); + + return err; +} + +enum idmap_error_code sss_idmap_add_domain_ex(struct sss_idmap_ctx *ctx, + const char *domain_name, + const char *domain_sid, + struct sss_idmap_range *range, + const char *range_id, + uint32_t rid, + bool external_mapping) +{ + struct idmap_domain_info *dom = NULL; + enum idmap_error_code err; + + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + + if (domain_name == NULL) { + return IDMAP_NO_DOMAIN; + } + + if (range == NULL) { + return IDMAP_NO_RANGE; + } + + /* For algorithmic mapping a valid domain SID is required, for external + * mapping it may be NULL, but if set it should be valid. */ + if ((!external_mapping && !is_domain_sid(domain_sid)) + || (external_mapping + && domain_sid != NULL + && !is_domain_sid(domain_sid))) { + return IDMAP_SID_INVALID; + } + + dom = ctx->alloc_func(sizeof(struct idmap_domain_info), ctx->alloc_pvt); + if (dom == NULL) { + return IDMAP_OUT_OF_MEMORY; + } + memset(dom, 0, sizeof(struct idmap_domain_info)); + + dom->name = idmap_strdup(ctx, domain_name); + if (dom->name == NULL) { + err = IDMAP_OUT_OF_MEMORY; + goto fail; + } + + if (domain_sid != NULL) { + dom->sid = idmap_strdup(ctx, domain_sid); + if (dom->sid == NULL) { + err = IDMAP_OUT_OF_MEMORY; + goto fail; + } + } + + dom->range_params.min_id = range->min; + dom->range_params.max_id = range->max; + + if (range_id != NULL) { + dom->range_params.range_id = idmap_strdup(ctx, range_id); + if (dom->range_params.range_id == NULL) { + err = IDMAP_OUT_OF_MEMORY; + goto fail; + } + } + + dom->range_params.first_rid = rid; + dom->external_mapping = external_mapping; + + err = dom_check_collision(ctx->idmap_domain_info, dom); + if (err != IDMAP_SUCCESS) { + goto fail; + } + + dom->next = ctx->idmap_domain_info; + ctx->idmap_domain_info = dom; + + return IDMAP_SUCCESS; + +fail: + sss_idmap_free_domain(ctx, dom); + + return err; +} + +enum idmap_error_code +sss_idmap_add_auto_domain_ex(struct sss_idmap_ctx *ctx, + const char *domain_name, + const char *domain_sid, + struct sss_idmap_range *range, + const char *range_id, + uint32_t rid, + bool external_mapping, + idmap_store_cb cb, + void *pvt) +{ + enum idmap_error_code err; + + err = sss_idmap_add_domain_ex(ctx, domain_name, domain_sid, range, + range_id, rid, external_mapping); + if (err != IDMAP_SUCCESS) { + return err; + } + + if (external_mapping) { + /* There's no point in generating secondary ranges if external_mapping + is enabled. */ + ctx->idmap_domain_info->auto_add_ranges = false; + return IDMAP_SUCCESS; + } + + if ((range->max - range->min + 1) != ctx->idmap_opts.rangesize) { + /* Range of primary slice is not equal to the value of + ldap_idmap_range_size option. */ + return IDMAP_ERROR; + } + + /* No additional secondary ranges should be added if no sec ranges are + predeclared. */ + if (ctx->idmap_opts.extra_slice_init == 0) { + ctx->idmap_domain_info->auto_add_ranges = false; + return IDMAP_SUCCESS; + } + + /* Add size of primary slice for first_rid of secondary slices. */ + rid += ctx->idmap_opts.rangesize; + err = get_helpers(ctx, domain_sid, rid, + &ctx->idmap_domain_info->helpers); + if (err == IDMAP_SUCCESS) { + ctx->idmap_domain_info->auto_add_ranges = true; + ctx->idmap_domain_info->helpers_owner = true; + } else { + /* Running out of slices for secondary mapping is a non-fatal + * problem. */ + if (err == IDMAP_OUT_OF_SLICES) { + err = IDMAP_SUCCESS; + } + ctx->idmap_domain_info->auto_add_ranges = false; + } + + ctx->idmap_domain_info->cb = cb; + ctx->idmap_domain_info->pvt = pvt; + + return err; +} + +enum idmap_error_code sss_idmap_add_domain(struct sss_idmap_ctx *ctx, + const char *domain_name, + const char *domain_sid, + struct sss_idmap_range *range) +{ + return sss_idmap_add_domain_ex(ctx, domain_name, domain_sid, range, NULL, + 0, false); +} + +static bool sss_idmap_sid_is_builtin(const char *sid) +{ + if (strncmp(sid, "S-1-5-32-", 9) == 0) { + return true; + } + + return false; +} + +static bool parse_rid(const char *sid, size_t dom_prefix_len, long long *_rid) +{ + long long rid; + char *endptr; + + errno = 0; + /* Use suffix of sid - part after domain and following '-' */ + rid = strtoull(sid + dom_prefix_len + 1, &endptr, 10); + if (errno != 0 || rid > UINT32_MAX || *endptr != '\0') { + return false; + } + + *_rid = rid; + return true; +} + +static bool is_sid_from_dom(const char *dom_sid, const char *sid, + size_t *_dom_sid_len) +{ + size_t dom_sid_len; + + if (dom_sid == NULL) { + return false; + } + + dom_sid_len = strlen(dom_sid); + *_dom_sid_len = dom_sid_len; + + if (strlen(sid) < dom_sid_len || sid[dom_sid_len] != '-') { + return false; + } + + return strncmp(sid, dom_sid, dom_sid_len) == 0; +} + +static bool comp_id(struct idmap_range_params *range_params, long long rid, + uint32_t *_id) +{ + uint32_t id; + + if (rid >= range_params->first_rid + && ((UINT32_MAX - range_params->min_id) > + (rid - range_params->first_rid))) { + id = range_params->min_id + (rid - range_params->first_rid); + if (id <= range_params->max_id) { + *_id = id; + return true; + } + } + return false; +} + +static enum idmap_error_code +get_range(struct sss_idmap_ctx *ctx, + struct idmap_range_params *helpers, + const char *dom_sid, + long long rid, + struct idmap_range_params **_range) +{ + char *secondary_name = NULL; + enum idmap_error_code err; + int first_rid; + struct idmap_range_params *range; + struct idmap_range_params *helper; + + first_rid = (rid / ctx->idmap_opts.rangesize) * ctx->idmap_opts.rangesize; + + secondary_name = generate_sec_slice_name(ctx, dom_sid, first_rid); + if (secondary_name == NULL) { + err = IDMAP_OUT_OF_MEMORY; + goto error; + } + + helper = get_helper_by_id(helpers, secondary_name); + if (helper != NULL) { + /* Utilize helper's range. */ + err = construct_range(ctx, helper, secondary_name, &range); + } else { + /* Have to generate a whole new range. */ + err = generate_slice(ctx, secondary_name, first_rid, &range); + } + + if (err != IDMAP_SUCCESS) { + goto error; + } + + *_range = range; + return IDMAP_SUCCESS; + +error: + ctx->free_func(secondary_name, ctx->alloc_pvt); + return err; +} + +static enum idmap_error_code +spawn_dom(struct sss_idmap_ctx *ctx, + struct idmap_domain_info *parent, + struct idmap_range_params *range) +{ + struct sss_idmap_range tmp; + static enum idmap_error_code err; + struct idmap_domain_info *it; + + tmp.min = range->min_id; + tmp.max = range->max_id; + + err = sss_idmap_add_domain_ex(ctx, + parent->name, + parent->sid, + &tmp, range->range_id, + range->first_rid, false); + if (err != IDMAP_SUCCESS) { + return err; + } + + it = ctx->idmap_domain_info; + while (it != NULL) { + /* Find the newly added domain. */ + if (ranges_eq(&it->range_params, range)) { + + /* Share helpers. */ + it->helpers = parent->helpers; + it->auto_add_ranges = parent->auto_add_ranges; + + /* Share call back for storing domains */ + it->cb = parent->cb; + it->pvt = parent->pvt; + break; + } + + it = it->next; + } + + if (it == NULL) { + /* Failed to find just added domain. */ + return IDMAP_ERROR; + } + + /* Store mapping for newly created domain. */ + if (it->cb != NULL) { + err = it->cb(it->name, + it->sid, + it->range_params.range_id, + it->range_params.min_id, + it->range_params.max_id, + it->range_params.first_rid, + it->pvt); + if (err != IDMAP_SUCCESS) { + return err; + } + } + + return IDMAP_SUCCESS; +} + +static enum idmap_error_code +add_dom_for_sid(struct sss_idmap_ctx *ctx, + struct idmap_domain_info *matched_dom, + const char *sid, + uint32_t *_id) +{ + enum idmap_error_code err; + long long rid; + struct idmap_range_params *range = NULL; + + if (parse_rid(sid, strlen(matched_dom->sid), &rid) == false) { + err = IDMAP_SID_INVALID; + goto done; + } + + err = get_range(ctx, matched_dom->helpers, matched_dom->sid, rid, &range); + if (err != IDMAP_SUCCESS) { + goto done; + } + + err = spawn_dom(ctx, matched_dom, range); + if (err != IDMAP_SUCCESS) { + goto done; + } + + if (!comp_id(range, rid, _id)) { + err = IDMAP_ERROR; + goto done; + } + + err = IDMAP_SUCCESS; + +done: + if (range != NULL) { + ctx->free_func(range->range_id, ctx->alloc_pvt); + } + ctx->free_func(range, ctx->alloc_pvt); + return err; +} + +enum idmap_error_code sss_idmap_sid_to_unix(struct sss_idmap_ctx *ctx, + const char *sid, + uint32_t *_id) +{ + struct idmap_domain_info *idmap_domain_info; + struct idmap_domain_info *matched_dom = NULL; + size_t dom_len; + long long rid; + + if (sid == NULL || _id == NULL) { + return IDMAP_ERROR; + } + + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + + idmap_domain_info = ctx->idmap_domain_info; + + if (sss_idmap_sid_is_builtin(sid)) { + return IDMAP_BUILTIN_SID; + } + + /* Try primary slices */ + while (idmap_domain_info != NULL) { + + if (is_sid_from_dom(idmap_domain_info->sid, sid, &dom_len)) { + + if (idmap_domain_info->external_mapping == true) { + return IDMAP_EXTERNAL; + } + + if (parse_rid(sid, dom_len, &rid) == false) { + return IDMAP_SID_INVALID; + } + + if (comp_id(&idmap_domain_info->range_params, rid, _id)) { + return IDMAP_SUCCESS; + } + + matched_dom = idmap_domain_info; + } + + idmap_domain_info = idmap_domain_info->next; + } + + if (matched_dom != NULL && matched_dom->auto_add_ranges) { + return add_dom_for_sid(ctx, matched_dom, sid, _id); + } + + return matched_dom ? IDMAP_NO_RANGE : IDMAP_NO_DOMAIN; +} + +enum idmap_error_code sss_idmap_check_sid_unix(struct sss_idmap_ctx *ctx, + const char *sid, + uint32_t id) +{ + struct idmap_domain_info *idmap_domain_info; + size_t dom_len; + bool no_range = false; + + if (sid == NULL) { + return IDMAP_ERROR; + } + + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + + if (ctx->idmap_domain_info == NULL) { + return IDMAP_NO_DOMAIN; + } + + idmap_domain_info = ctx->idmap_domain_info; + + if (sss_idmap_sid_is_builtin(sid)) { + return IDMAP_BUILTIN_SID; + } + + while (idmap_domain_info != NULL) { + if (idmap_domain_info->sid != NULL) { + dom_len = strlen(idmap_domain_info->sid); + if (strlen(sid) > dom_len && sid[dom_len] == '-' + && strncmp(sid, idmap_domain_info->sid, dom_len) == 0) { + + if (id >= idmap_domain_info->range_params.min_id + && id <= idmap_domain_info->range_params.max_id) { + return IDMAP_SUCCESS; + } + + no_range = true; + } + } + + idmap_domain_info = idmap_domain_info->next; + } + + return no_range ? IDMAP_NO_RANGE : IDMAP_SID_UNKNOWN; +} + +static enum idmap_error_code generate_sid(struct sss_idmap_ctx *ctx, + const char *dom_sid, + uint32_t rid, + char **_sid) +{ + char *sid; + int len; + int ret; + + len = snprintf(NULL, 0, SID_FMT, dom_sid, rid); + if (len <= 0 || len > SID_STR_MAX_LEN) { + return IDMAP_ERROR; + } + + sid = ctx->alloc_func(len + 1, ctx->alloc_pvt); + if (sid == NULL) { + return IDMAP_OUT_OF_MEMORY; + } + + ret = snprintf(sid, len + 1, SID_FMT, dom_sid, rid); + if (ret != len) { + ctx->free_func(sid, ctx->alloc_pvt); + return IDMAP_ERROR; + } + + *_sid = sid; + return IDMAP_SUCCESS; +} + +enum idmap_error_code sss_idmap_unix_to_sid(struct sss_idmap_ctx *ctx, + uint32_t id, + char **_sid) +{ + struct idmap_domain_info *idmap_domain_info; + uint32_t rid; + enum idmap_error_code err; + + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + + idmap_domain_info = ctx->idmap_domain_info; + + while (idmap_domain_info != NULL) { + if (id_is_in_range(id, &idmap_domain_info->range_params, &rid)) { + + if (idmap_domain_info->external_mapping == true + || idmap_domain_info->sid == NULL) { + return IDMAP_EXTERNAL; + } + + return generate_sid(ctx, idmap_domain_info->sid, rid, _sid); + } + + idmap_domain_info = idmap_domain_info->next; + } + + /* Check secondary ranges. */ + idmap_domain_info = ctx->idmap_domain_info; + while (idmap_domain_info != NULL) { + + for (struct idmap_range_params *it = idmap_domain_info->helpers; + it != NULL; + it = it->next) { + + if (idmap_domain_info->helpers_owner == false) { + /* Checking helpers on owner is sufficient. */ + continue; + } + + if (id_is_in_range(id, it, &rid)) { + + if (idmap_domain_info->external_mapping == true + || idmap_domain_info->sid == NULL) { + return IDMAP_EXTERNAL; + } + + err = spawn_dom(ctx, idmap_domain_info, it); + if (err != IDMAP_SUCCESS) { + return err; + } + + return generate_sid(ctx, idmap_domain_info->sid, rid, _sid); + } + } + + idmap_domain_info = idmap_domain_info->next; + } + + return IDMAP_NO_DOMAIN; +} + +enum idmap_error_code sss_idmap_dom_sid_to_unix(struct sss_idmap_ctx *ctx, + struct sss_dom_sid *dom_sid, + uint32_t *id) +{ + enum idmap_error_code err; + char *sid; + + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + + err = sss_idmap_dom_sid_to_sid(ctx, dom_sid, &sid); + if (err != IDMAP_SUCCESS) { + goto done; + } + + err = sss_idmap_sid_to_unix(ctx, sid, id); + +done: + ctx->free_func(sid, ctx->alloc_pvt); + + return err; +} + +enum idmap_error_code sss_idmap_bin_sid_to_unix(struct sss_idmap_ctx *ctx, + uint8_t *bin_sid, + size_t length, + uint32_t *id) +{ + enum idmap_error_code err; + char *sid; + + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + + err = sss_idmap_bin_sid_to_sid(ctx, bin_sid, length, &sid); + if (err != IDMAP_SUCCESS) { + goto done; + } + + err = sss_idmap_sid_to_unix(ctx, sid, id); + +done: + ctx->free_func(sid, ctx->alloc_pvt); + + return err; +} + +enum idmap_error_code sss_idmap_smb_sid_to_unix(struct sss_idmap_ctx *ctx, + struct dom_sid *smb_sid, + uint32_t *id) +{ + enum idmap_error_code err; + char *sid; + + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + + err = sss_idmap_smb_sid_to_sid(ctx, smb_sid, &sid); + if (err != IDMAP_SUCCESS) { + goto done; + } + + err = sss_idmap_sid_to_unix(ctx, sid, id); + +done: + ctx->free_func(sid, ctx->alloc_pvt); + + return err; +} + +enum idmap_error_code sss_idmap_check_dom_sid_to_unix(struct sss_idmap_ctx *ctx, + struct sss_dom_sid *dom_sid, + uint32_t id) +{ + enum idmap_error_code err; + char *sid; + + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + + err = sss_idmap_dom_sid_to_sid(ctx, dom_sid, &sid); + if (err != IDMAP_SUCCESS) { + goto done; + } + + err = sss_idmap_check_sid_unix(ctx, sid, id); + +done: + ctx->free_func(sid, ctx->alloc_pvt); + + return err; +} + +enum idmap_error_code sss_idmap_check_bin_sid_unix(struct sss_idmap_ctx *ctx, + uint8_t *bin_sid, + size_t length, + uint32_t id) +{ + enum idmap_error_code err; + char *sid; + + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + + err = sss_idmap_bin_sid_to_sid(ctx, bin_sid, length, &sid); + if (err != IDMAP_SUCCESS) { + goto done; + } + + err = sss_idmap_check_sid_unix(ctx, sid, id); + +done: + ctx->free_func(sid, ctx->alloc_pvt); + + return err; +} + +enum idmap_error_code sss_idmap_check_smb_sid_unix(struct sss_idmap_ctx *ctx, + struct dom_sid *smb_sid, + uint32_t id) +{ + enum idmap_error_code err; + char *sid; + + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + + err = sss_idmap_smb_sid_to_sid(ctx, smb_sid, &sid); + if (err != IDMAP_SUCCESS) { + goto done; + } + + err = sss_idmap_check_sid_unix(ctx, sid, id); + +done: + ctx->free_func(sid, ctx->alloc_pvt); + + return err; +} +enum idmap_error_code sss_idmap_unix_to_dom_sid(struct sss_idmap_ctx *ctx, + uint32_t id, + struct sss_dom_sid **_dom_sid) +{ + enum idmap_error_code err; + char *sid = NULL; + struct sss_dom_sid *dom_sid = NULL; + + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + + err = sss_idmap_unix_to_sid(ctx, id, &sid); + if (err != IDMAP_SUCCESS) { + goto done; + } + + err = sss_idmap_sid_to_dom_sid(ctx, sid, &dom_sid); + if (err != IDMAP_SUCCESS) { + goto done; + } + + *_dom_sid = dom_sid; + err = IDMAP_SUCCESS; + +done: + ctx->free_func(sid, ctx->alloc_pvt); + if (err != IDMAP_SUCCESS) { + ctx->free_func(dom_sid, ctx->alloc_pvt); + } + + return err; +} + +enum idmap_error_code sss_idmap_unix_to_bin_sid(struct sss_idmap_ctx *ctx, + uint32_t id, + uint8_t **_bin_sid, + size_t *_length) +{ + enum idmap_error_code err; + char *sid = NULL; + uint8_t *bin_sid = NULL; + size_t length; + + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + + err = sss_idmap_unix_to_sid(ctx, id, &sid); + if (err != IDMAP_SUCCESS) { + goto done; + } + + err = sss_idmap_sid_to_bin_sid(ctx, sid, &bin_sid, &length); + if (err != IDMAP_SUCCESS) { + goto done; + } + + *_bin_sid = bin_sid; + *_length = length; + err = IDMAP_SUCCESS; + +done: + ctx->free_func(sid, ctx->alloc_pvt); + if (err != IDMAP_SUCCESS) { + ctx->free_func(bin_sid, ctx->alloc_pvt); + } + + return err; + +} + +enum idmap_error_code +sss_idmap_ctx_set_autorid(struct sss_idmap_ctx *ctx, bool use_autorid) +{ + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + ctx->idmap_opts.autorid_mode = use_autorid; + return IDMAP_SUCCESS; +} + +enum idmap_error_code +sss_idmap_ctx_set_lower(struct sss_idmap_ctx *ctx, id_t lower) +{ + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + ctx->idmap_opts.idmap_lower = lower; + return IDMAP_SUCCESS; +} + +enum idmap_error_code +sss_idmap_ctx_set_upper(struct sss_idmap_ctx *ctx, id_t upper) +{ + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + ctx->idmap_opts.idmap_upper = upper; + return IDMAP_SUCCESS; +} + +enum idmap_error_code +sss_idmap_ctx_set_rangesize(struct sss_idmap_ctx *ctx, id_t rangesize) +{ + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + ctx->idmap_opts.rangesize = rangesize; + return IDMAP_SUCCESS; +} + +enum idmap_error_code +sss_idmap_ctx_set_extra_slice_init(struct sss_idmap_ctx *ctx, + int extra_slice_init) +{ + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + ctx->idmap_opts.extra_slice_init = extra_slice_init; + return IDMAP_SUCCESS; +} + +enum idmap_error_code +sss_idmap_ctx_get_autorid(struct sss_idmap_ctx *ctx, bool *_autorid) +{ + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + *_autorid = ctx->idmap_opts.autorid_mode; + return IDMAP_SUCCESS; +} + +enum idmap_error_code +sss_idmap_ctx_get_lower(struct sss_idmap_ctx *ctx, id_t *_lower) +{ + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + *_lower = ctx->idmap_opts.idmap_lower; + return IDMAP_SUCCESS; +} + +enum idmap_error_code +sss_idmap_ctx_get_upper(struct sss_idmap_ctx *ctx, id_t *_upper) +{ + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + *_upper = ctx->idmap_opts.idmap_upper; + return IDMAP_SUCCESS; +} + +enum idmap_error_code +sss_idmap_ctx_get_rangesize(struct sss_idmap_ctx *ctx, id_t *_rangesize) +{ + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + *_rangesize = ctx->idmap_opts.rangesize; + return IDMAP_SUCCESS; +} + +enum idmap_error_code +sss_idmap_domain_has_algorithmic_mapping(struct sss_idmap_ctx *ctx, + const char *dom_sid, + bool *has_algorithmic_mapping) +{ + struct idmap_domain_info *idmap_domain_info; + size_t len; + size_t dom_sid_len; + + if (dom_sid == NULL) { + return IDMAP_SID_INVALID; + } + + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + + if (ctx->idmap_domain_info == NULL) { + return IDMAP_NO_DOMAIN; + } + + idmap_domain_info = ctx->idmap_domain_info; + + while (idmap_domain_info != NULL) { + if (idmap_domain_info->sid != NULL) { + len = strlen(idmap_domain_info->sid); + dom_sid_len = strlen(dom_sid); + if (((dom_sid_len > len && dom_sid[len] == '-') + || dom_sid_len == len) + && strncmp(dom_sid, idmap_domain_info->sid, len) == 0) { + + *has_algorithmic_mapping = !idmap_domain_info->external_mapping; + return IDMAP_SUCCESS; + + } + } + + idmap_domain_info = idmap_domain_info->next; + } + + return IDMAP_SID_UNKNOWN; +} + +enum idmap_error_code +sss_idmap_domain_by_name_has_algorithmic_mapping(struct sss_idmap_ctx *ctx, + const char *dom_name, + bool *has_algorithmic_mapping) +{ + struct idmap_domain_info *idmap_domain_info; + + if (dom_name == NULL) { + return IDMAP_ERROR; + } + + CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID); + + if (ctx->idmap_domain_info == NULL) { + return IDMAP_NO_DOMAIN; + } + + idmap_domain_info = ctx->idmap_domain_info; + + while (idmap_domain_info != NULL) { + if (idmap_domain_info->name != NULL + && strcmp(dom_name, idmap_domain_info->name) == 0) { + + *has_algorithmic_mapping = !idmap_domain_info->external_mapping; + return IDMAP_SUCCESS; + } + + idmap_domain_info = idmap_domain_info->next; + } + + return IDMAP_NAME_UNKNOWN; +} |