/* SSSD System Database Copyright (C) 2008-2011 Simo Sorce Copyright (C) 2008-2011 Stephen Gallagher 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 . */ #include "util/util.h" #include "util/strtonum.h" #include "util/sss_utf8.h" #include "util/crypto/sss_crypto.h" #include "db/sysdb_private.h" #include "confdb/confdb.h" #include "util/probes.h" #include errno_t sysdb_dn_sanitize(TALLOC_CTX *mem_ctx, const char *input, char **sanitized) { struct ldb_val val; errno_t ret = EOK; val.data = (uint8_t *)talloc_strdup(mem_ctx, input); if (!val.data) { return ENOMEM; } /* We can't include the trailing NULL because it would * be escaped and result in an unterminated string */ val.length = strlen(input); *sanitized = ldb_dn_escape_value(mem_ctx, val); if (!*sanitized) { ret = ENOMEM; } talloc_free(val.data); return ret; } struct ldb_dn *sysdb_custom_subtree_dn(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, const char *subtree_name) { errno_t ret; char *clean_subtree; struct ldb_dn *dn = NULL; TALLOC_CTX *tmp_ctx; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) return NULL; ret = sysdb_dn_sanitize(tmp_ctx, subtree_name, &clean_subtree); if (ret != EOK) { talloc_free(tmp_ctx); return NULL; } dn = ldb_dn_new_fmt(tmp_ctx, dom->sysdb->ldb, SYSDB_TMPL_CUSTOM_SUBTREE, clean_subtree, dom->name); if (dn) { talloc_steal(mem_ctx, dn); } talloc_free(tmp_ctx); return dn; } struct ldb_dn *sysdb_custom_dn(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, const char *object_name, const char *subtree_name) { errno_t ret; TALLOC_CTX *tmp_ctx; char *clean_name; char *clean_subtree; struct ldb_dn *dn = NULL; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return NULL; } ret = sysdb_dn_sanitize(tmp_ctx, object_name, &clean_name); if (ret != EOK) { goto done; } ret = sysdb_dn_sanitize(tmp_ctx, subtree_name, &clean_subtree); if (ret != EOK) { goto done; } dn = ldb_dn_new_fmt(mem_ctx, dom->sysdb->ldb, SYSDB_TMPL_CUSTOM, clean_name, clean_subtree, dom->name); done: talloc_free(tmp_ctx); return dn; } struct ldb_dn *sysdb_user_dn(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, const char *name) { errno_t ret; char *clean_name; struct ldb_dn *dn; ret = sysdb_dn_sanitize(NULL, name, &clean_name); if (ret != EOK) { return NULL; } dn = ldb_dn_new_fmt(mem_ctx, dom->sysdb->ldb, SYSDB_TMPL_USER, clean_name, dom->name); talloc_free(clean_name); return dn; } struct ldb_dn *sysdb_user_base_dn(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom) { return ldb_dn_new_fmt(mem_ctx, dom->sysdb->ldb, SYSDB_TMPL_USER_BASE, dom->name); } struct ldb_dn *sysdb_group_dn(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, const char *name) { errno_t ret; char *clean_name; struct ldb_dn *dn; ret = sysdb_dn_sanitize(NULL, name, &clean_name); if (ret != EOK) { return NULL; } dn = ldb_dn_new_fmt(mem_ctx, dom->sysdb->ldb, SYSDB_TMPL_GROUP, clean_name, dom->name); talloc_free(clean_name); return dn; } struct ldb_dn *sysdb_group_base_dn(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom) { return ldb_dn_new_fmt(mem_ctx, dom->sysdb->ldb, SYSDB_TMPL_GROUP_BASE, dom->name); } struct ldb_dn *sysdb_netgroup_dn(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, const char *name) { errno_t ret; char *clean_name; struct ldb_dn *dn; ret = sysdb_dn_sanitize(NULL, name, &clean_name); if (ret != EOK) { return NULL; } dn = ldb_dn_new_fmt(mem_ctx, dom->sysdb->ldb, SYSDB_TMPL_NETGROUP, clean_name, dom->name); talloc_free(clean_name); return dn; } struct ldb_dn *sysdb_netgroup_base_dn(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom) { return ldb_dn_new_fmt(mem_ctx, dom->sysdb->ldb, SYSDB_TMPL_NETGROUP_BASE, dom->name); } errno_t sysdb_get_rdn(struct sysdb_ctx *sysdb, TALLOC_CTX *mem_ctx, const char *dn, char **_name, char **_val) { errno_t ret; struct ldb_dn *ldb_dn; const char *attr_name = NULL; const struct ldb_val *val; TALLOC_CTX *tmp_ctx; /* We have to create a tmp_ctx here because * ldb_dn_new_fmt() fails if mem_ctx is NULL */ tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } ldb_dn = ldb_dn_new_fmt(tmp_ctx, sysdb->ldb, "%s", dn); if (ldb_dn == NULL) { ret = ENOMEM; goto done; } if (_name) { attr_name = ldb_dn_get_rdn_name(ldb_dn); if (attr_name == NULL) { ret = EINVAL; goto done; } *_name = talloc_strdup(mem_ctx, attr_name); if (!*_name) { ret = ENOMEM; goto done; } } val = ldb_dn_get_rdn_val(ldb_dn); if (val == NULL) { ret = EINVAL; if (_name) talloc_free(*_name); goto done; } *_val = talloc_strndup(mem_ctx, (char *) val->data, val->length); if (!*_val) { ret = ENOMEM; if (_name) talloc_free(*_name); goto done; } ret = EOK; done: talloc_zfree(tmp_ctx); return ret; } errno_t sysdb_group_dn_name(struct sysdb_ctx *sysdb, TALLOC_CTX *mem_ctx, const char *dn, char **_name) { return sysdb_get_rdn(sysdb, mem_ctx, dn, NULL, _name); } struct ldb_dn *sysdb_domain_dn(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom) { return ldb_dn_new_fmt(mem_ctx, dom->sysdb->ldb, SYSDB_DOM_BASE, dom->name); } struct ldb_dn *sysdb_base_dn(struct sysdb_ctx *sysdb, TALLOC_CTX *mem_ctx) { return ldb_dn_new(mem_ctx, sysdb->ldb, SYSDB_BASE); } struct ldb_context *sysdb_ctx_get_ldb(struct sysdb_ctx *sysdb) { return sysdb->ldb; } struct sysdb_attrs *sysdb_new_attrs(TALLOC_CTX *mem_ctx) { return talloc_zero(mem_ctx, struct sysdb_attrs); } int sysdb_attrs_get_el_ext(struct sysdb_attrs *attrs, const char *name, bool alloc, struct ldb_message_element **el) { struct ldb_message_element *e = NULL; int i; for (i = 0; i < attrs->num; i++) { if (strcasecmp(name, attrs->a[i].name) == 0) e = &(attrs->a[i]); } if (!e && alloc) { e = talloc_realloc(attrs, attrs->a, struct ldb_message_element, attrs->num+1); if (!e) return ENOMEM; attrs->a = e; e[attrs->num].name = talloc_strdup(e, name); if (!e[attrs->num].name) return ENOMEM; e[attrs->num].num_values = 0; e[attrs->num].values = NULL; e[attrs->num].flags = 0; e = &(attrs->a[attrs->num]); attrs->num++; } if (!e) { return ENOENT; } *el = e; return EOK; } int sysdb_attrs_get_el(struct sysdb_attrs *attrs, const char *name, struct ldb_message_element **el) { return sysdb_attrs_get_el_ext(attrs, name, true, el); } int sysdb_attrs_get_string(struct sysdb_attrs *attrs, const char *name, const char **string) { struct ldb_message_element *el; int ret; ret = sysdb_attrs_get_el_ext(attrs, name, false, &el); if (ret) { return ret; } if (el->num_values != 1) { return ERANGE; } *string = (const char *)el->values[0].data; return EOK; } int sysdb_attrs_get_int32_t(struct sysdb_attrs *attrs, const char *name, int32_t *value) { struct ldb_message_element *el; int ret; char *endptr; int32_t val; ret = sysdb_attrs_get_el_ext(attrs, name, false, &el); if (ret) { return ret; } if (el->num_values != 1) { return ERANGE; } errno = 0; val = strtoint32((const char *) el->values[0].data, &endptr, 10); if (errno != 0) return errno; if (*endptr) return EINVAL; *value = val; return EOK; } int sysdb_attrs_get_uint32_t(struct sysdb_attrs *attrs, const char *name, uint32_t *value) { struct ldb_message_element *el; int ret; char *endptr; uint32_t val; ret = sysdb_attrs_get_el_ext(attrs, name, false, &el); if (ret) { return ret; } if (el->num_values != 1) { return ERANGE; } errno = 0; val = strtouint32((const char *) el->values[0].data, &endptr, 10); if (errno != 0) return errno; if (*endptr) return EINVAL; *value = val; return EOK; } int sysdb_attrs_get_uint16_t(struct sysdb_attrs *attrs, const char *name, uint16_t *value) { struct ldb_message_element *el; int ret; char *endptr; uint16_t val; ret = sysdb_attrs_get_el_ext(attrs, name, false, &el); if (ret) { return ret; } if (el->num_values != 1) { return ERANGE; } errno = 0; val = strtouint16((const char *) el->values[0].data, &endptr, 10); if (errno != 0) return errno; if (*endptr) return EINVAL; *value = val; return EOK; } errno_t sysdb_attrs_get_bool(struct sysdb_attrs *attrs, const char *name, bool *value) { struct ldb_message_element *el; int ret; ret = sysdb_attrs_get_el_ext(attrs, name, false, &el); if (ret) { return ret; } if (el->num_values != 1) { return ERANGE; } if (strcmp((const char *)el->values[0].data, "TRUE") == 0) *value = true; else *value = false; return EOK; } const char **sss_ldb_el_to_string_list(TALLOC_CTX *mem_ctx, struct ldb_message_element *el) { unsigned int u; const char **a; a = talloc_zero_array(mem_ctx, const char *, el->num_values + 1); if (a == NULL) { return NULL; } for (u = 0; u < el->num_values; u++) { a[u] = talloc_strndup(a, (const char *)el->values[u].data, el->values[u].length); if (a[u] == NULL) { talloc_free(a); return NULL; } } return a; } int sysdb_attrs_get_string_array(struct sysdb_attrs *attrs, const char *name, TALLOC_CTX *mem_ctx, const char ***string) { struct ldb_message_element *el; int ret; const char **a; ret = sysdb_attrs_get_el_ext(attrs, name, false, &el); if (ret) { return ret; } a = sss_ldb_el_to_string_list(mem_ctx, el); if (a == NULL) { return ENOMEM; } *string = a; return EOK; } static int sysdb_attrs_add_val_int(struct sysdb_attrs *attrs, const char *name, bool check_values, const struct ldb_val *val) { struct ldb_message_element *el = NULL; struct ldb_val *vals; int ret; size_t c; ret = sysdb_attrs_get_el(attrs, name, &el); if (ret != EOK) { return ret; } if (check_values) { for (c = 0; c < el->num_values; c++) { if (val->length == el->values[c].length && memcmp(val->data, el->values[c].data, val->length) == 0) { return EOK; } } } vals = talloc_realloc(attrs->a, el->values, struct ldb_val, el->num_values+1); if (!vals) return ENOMEM; vals[el->num_values] = ldb_val_dup(vals, val); if (vals[el->num_values].data == NULL && vals[el->num_values].length != 0) { return ENOMEM; } el->values = vals; el->num_values++; return EOK; } int sysdb_attrs_add_val(struct sysdb_attrs *attrs, const char *name, const struct ldb_val *val) { return sysdb_attrs_add_val_int(attrs, name, false, val); } /* Check if the same value already exists. */ int sysdb_attrs_add_val_safe(struct sysdb_attrs *attrs, const char *name, const struct ldb_val *val) { return sysdb_attrs_add_val_int(attrs, name, true, val); } int sysdb_attrs_add_string_safe(struct sysdb_attrs *attrs, const char *name, const char *str) { struct ldb_val v; v.data = (uint8_t *)discard_const(str); v.length = strlen(str); return sysdb_attrs_add_val_safe(attrs, name, &v); } int sysdb_attrs_add_string(struct sysdb_attrs *attrs, const char *name, const char *str) { struct ldb_val v; v.data = (uint8_t *)discard_const(str); v.length = strlen(str); return sysdb_attrs_add_val(attrs, name, &v); } int sysdb_attrs_add_lower_case_string(struct sysdb_attrs *attrs, bool safe, const char *name, const char *str) { char *lc_str; int ret; if (attrs == NULL || str == NULL) { return EINVAL; } lc_str = sss_tc_utf8_str_tolower(attrs, str); if (lc_str == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Cannot convert name to lowercase.\n"); return ENOMEM; } if (safe) { ret = sysdb_attrs_add_string_safe(attrs, name, lc_str); } else { ret = sysdb_attrs_add_string(attrs, name, lc_str); } talloc_free(lc_str); return ret; } int sysdb_attrs_add_mem(struct sysdb_attrs *attrs, const char *name, const void *mem, size_t size) { struct ldb_val v; v.data = discard_const(mem); v.length = size; return sysdb_attrs_add_val(attrs, name, &v); } int sysdb_attrs_add_base64_blob(struct sysdb_attrs *attrs, const char *name, const char *base64_str) { struct ldb_val v; int ret; if (base64_str == NULL) { return EINVAL; } v.data = sss_base64_decode(attrs, base64_str, &v.length); if (v.data == NULL) { DEBUG(SSSDBG_OP_FAILURE, "sss_base64_decode failed.\n"); return ENOMEM; } ret = sysdb_attrs_add_val(attrs, name, &v); talloc_free(v.data); return ret; } int sysdb_attrs_add_bool(struct sysdb_attrs *attrs, const char *name, bool value) { if(value) { return sysdb_attrs_add_string(attrs, name, "TRUE"); } return sysdb_attrs_add_string(attrs, name, "FALSE"); } int sysdb_attrs_steal_string(struct sysdb_attrs *attrs, const char *name, char *str) { struct ldb_message_element *el = NULL; struct ldb_val *vals; int ret; ret = sysdb_attrs_get_el(attrs, name, &el); if (ret != EOK) { return ret; } vals = talloc_realloc(attrs->a, el->values, struct ldb_val, el->num_values+1); if (!vals) return ENOMEM; el->values = vals; /* now steal and assign the string */ talloc_steal(el->values, str); el->values[el->num_values].data = (uint8_t *)str; el->values[el->num_values].length = strlen(str); el->num_values++; return EOK; } int sysdb_attrs_add_long(struct sysdb_attrs *attrs, const char *name, long value) { struct ldb_val v; char *str; int ret; str = talloc_asprintf(attrs, "%ld", value); if (!str) return ENOMEM; v.data = (uint8_t *)str; v.length = strlen(str); ret = sysdb_attrs_add_val(attrs, name, &v); talloc_free(str); return ret; } int sysdb_attrs_add_uint32(struct sysdb_attrs *attrs, const char *name, uint32_t value) { unsigned long val = value; struct ldb_val v; char *str; int ret; str = talloc_asprintf(attrs, "%lu", val); if (!str) return ENOMEM; v.data = (uint8_t *)str; v.length = strlen(str); ret = sysdb_attrs_add_val(attrs, name, &v); talloc_free(str); return ret; } int sysdb_attrs_add_time_t(struct sysdb_attrs *attrs, const char *name, time_t value) { long long val = value; struct ldb_val v; char *str; int ret; str = talloc_asprintf(attrs, "%lld", val); if (!str) return ENOMEM; v.data = (uint8_t *)str; v.length = strlen(str); ret = sysdb_attrs_add_val(attrs, name, &v); talloc_free(str); return ret; } int sysdb_attrs_add_lc_name_alias(struct sysdb_attrs *attrs, const char *value) { return sysdb_attrs_add_lower_case_string(attrs, false, SYSDB_NAME_ALIAS, value); } int sysdb_attrs_add_lc_name_alias_safe(struct sysdb_attrs *attrs, const char *value) { return sysdb_attrs_add_lower_case_string(attrs, true, SYSDB_NAME_ALIAS, value); } int sysdb_attrs_copy_values(struct sysdb_attrs *src, struct sysdb_attrs *dst, const char *name) { int ret = EOK; int i; struct ldb_message_element *src_el; ret = sysdb_attrs_get_el(src, name, &src_el); if (ret != EOK) { goto done; } for (i = 0; i < src_el->num_values; i++) { ret = sysdb_attrs_add_val(dst, name, &src_el->values[i]); if (ret != EOK) { goto done; } } done: return ret; } int sysdb_attrs_users_from_str_list(struct sysdb_attrs *attrs, const char *attr_name, const char *domain, const char *const *list) { struct ldb_message_element *el = NULL; struct ldb_val *vals; int i, j, num; char *member; int ret; ret = sysdb_attrs_get_el(attrs, attr_name, &el); if (ret) { return ret; } for (num = 0; list[num]; num++) /* count */ ; vals = talloc_realloc(attrs->a, el->values, struct ldb_val, el->num_values + num); if (!vals) { return ENOMEM; } el->values = vals; DEBUG(SSSDBG_TRACE_ALL, "Adding %d members to existing %d ones\n", num, el->num_values); for (i = 0, j = el->num_values; i < num; i++) { member = sysdb_user_strdn(el->values, domain, list[i]); if (!member) { DEBUG(SSSDBG_CONF_SETTINGS, "Failed to get user dn for [%s]\n", list[i]); continue; } el->values[j].data = (uint8_t *)member; el->values[j].length = strlen(member); j++; DEBUG(SSSDBG_TRACE_LIBS, " member #%d: [%s]\n", i, member); } el->num_values = j; return EOK; } static char *build_dom_dn_str_escape(TALLOC_CTX *mem_ctx, const char *template, const char *domain, const char *name) { char *ret; int l; l = strcspn(name, ",=\n+<>#;\\\""); if (name[l] != '\0') { struct ldb_val v; char *tmp; v.data = discard_const_p(uint8_t, name); v.length = strlen(name); tmp = ldb_dn_escape_value(mem_ctx, v); if (!tmp) { return NULL; } ret = talloc_asprintf(mem_ctx, template, tmp, domain); talloc_zfree(tmp); if (!ret) { return NULL; } return ret; } ret = talloc_asprintf(mem_ctx, template, name, domain); if (!ret) { return NULL; } return ret; } char *sysdb_user_strdn(TALLOC_CTX *mem_ctx, const char *domain, const char *name) { return build_dom_dn_str_escape(mem_ctx, SYSDB_TMPL_USER, domain, name); } char *sysdb_group_strdn(TALLOC_CTX *mem_ctx, const char *domain, const char *name) { return build_dom_dn_str_escape(mem_ctx, SYSDB_TMPL_GROUP, domain, name); } /* TODO: make a more complete and precise mapping */ int sysdb_error_to_errno(int ldberr) { switch (ldberr) { case LDB_SUCCESS: return EOK; case LDB_ERR_OPERATIONS_ERROR: return EIO; case LDB_ERR_NO_SUCH_OBJECT: return ENOENT; case LDB_ERR_BUSY: return EBUSY; case LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS: case LDB_ERR_ENTRY_ALREADY_EXISTS: return EEXIST; case LDB_ERR_INVALID_ATTRIBUTE_SYNTAX: return EINVAL; default: DEBUG(SSSDBG_CRIT_FAILURE, "LDB returned unexpected error: [%s]\n", ldb_strerror(ldberr)); return EFAULT; } } /* =Transactions========================================================== */ int sysdb_transaction_start(struct sysdb_ctx *sysdb) { int ret; ret = ldb_transaction_start(sysdb->ldb); if (ret == LDB_SUCCESS) { PROBE(SYSDB_TRANSACTION_START, sysdb->transaction_nesting); sysdb->transaction_nesting++; } else { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start ldb transaction! (%d)\n", ret); } return sysdb_error_to_errno(ret); } int sysdb_transaction_commit(struct sysdb_ctx *sysdb) { int ret; #ifdef HAVE_SYSTEMTAP int commit_nesting = sysdb->transaction_nesting-1; #endif PROBE(SYSDB_TRANSACTION_COMMIT_BEFORE, commit_nesting); ret = ldb_transaction_commit(sysdb->ldb); if (ret == LDB_SUCCESS) { sysdb->transaction_nesting--; PROBE(SYSDB_TRANSACTION_COMMIT_AFTER, sysdb->transaction_nesting); } else { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit ldb transaction! (%d)\n", ret); } return sysdb_error_to_errno(ret); } int sysdb_transaction_cancel(struct sysdb_ctx *sysdb) { int ret; ret = ldb_transaction_cancel(sysdb->ldb); if (ret == LDB_SUCCESS) { sysdb->transaction_nesting--; PROBE(SYSDB_TRANSACTION_CANCEL, sysdb->transaction_nesting); } else { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel ldb transaction! (%d)\n", ret); } return sysdb_error_to_errno(ret); } int compare_ldb_dn_comp_num(const void *m1, const void *m2) { struct ldb_message *msg1 = talloc_get_type(*(void **) discard_const(m1), struct ldb_message); struct ldb_message *msg2 = talloc_get_type(*(void **) discard_const(m2), struct ldb_message); return ldb_dn_get_comp_num(msg2->dn) - ldb_dn_get_comp_num(msg1->dn); } int sysdb_attrs_replace_name(struct sysdb_attrs *attrs, const char *oldname, const char *newname) { struct ldb_message_element *e = NULL; int i; const char *dummy; if (attrs == NULL || oldname == NULL || newname == NULL) return EINVAL; for (i = 0; i < attrs->num; i++) { if (strcasecmp(oldname, attrs->a[i].name) == 0) { e = &(attrs->a[i]); } if (strcasecmp(newname, attrs->a[i].name) == 0) { DEBUG(SSSDBG_MINOR_FAILURE, "New attribute name [%s] already exists.\n", newname); return EEXIST; } } if (e != NULL) { dummy = talloc_strdup(attrs, newname); if (dummy == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); return ENOMEM; } talloc_free(discard_const(e->name)); e->name = dummy; } return EOK; } /* Search for all incidences of attr_name in a list of * sysdb_attrs and add their value to a list * * TODO: Currently only works for single-valued * attributes. Multi-valued attributes will return * only the first entry */ errno_t sysdb_attrs_to_list(TALLOC_CTX *mem_ctx, struct sysdb_attrs **attrs, int attr_count, const char *attr_name, char ***_list) { int attr_idx; int i; char **list; char **tmp_list; int list_idx; *_list = NULL; /* Assume that every attrs entry contains the attr_name * This may waste a little memory if some entries don't * have the attribute, but it will save us the trouble * of continuously resizing the array. */ list = talloc_array(mem_ctx, char *, attr_count+1); if (!list) { return ENOMEM; } list_idx = 0; /* Loop through all entries in attrs */ for (attr_idx = 0; attr_idx < attr_count; attr_idx++) { /* Examine each attribute within the entry */ for (i = 0; i < attrs[attr_idx]->num; i++) { if (strcasecmp(attrs[attr_idx]->a[i].name, attr_name) == 0) { /* Attribute name matches the requested name * Copy it to the output list */ list[list_idx] = talloc_strdup( list, (const char *)attrs[attr_idx]->a[i].values[0].data); if (!list[list_idx]) { talloc_free(list); return ENOMEM; } list_idx++; /* We only support single-valued attributes * Break here and go on to the next entry */ break; } } } list[list_idx] = NULL; /* if list_idx < attr_count, do a realloc to * reclaim unused memory */ if (list_idx < attr_count) { tmp_list = talloc_realloc(mem_ctx, list, char *, list_idx+1); if (!tmp_list) { talloc_zfree(list); return ENOMEM; } list = tmp_list; } *_list = list; return EOK; } errno_t sysdb_get_bool(struct sysdb_ctx *sysdb, struct ldb_dn *dn, const char *attr_name, bool *value) { TALLOC_CTX *tmp_ctx; struct ldb_result *res; errno_t ret; int lret; const char *attrs[2] = {attr_name, NULL}; struct ldb_message_element *el; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } lret = ldb_search(sysdb->ldb, tmp_ctx, &res, dn, LDB_SCOPE_BASE, attrs, NULL); if (lret != LDB_SUCCESS) { ret = sysdb_error_to_errno(lret); goto done; } if (res->count == 0) { /* This entry has not been populated in LDB * This is a common case, as unlike LDAP, * LDB does not need to have all of its parent * objects actually exist. * This object in the sysdb exists mostly just * to contain this attribute. */ *value = false; ret = ENOENT; goto done; } else if (res->count != 1) { DEBUG(SSSDBG_CRIT_FAILURE, "Got more than one reply for base search!\n"); ret = EIO; goto done; } el = ldb_msg_find_element(res->msgs[0], attr_name); if (el == NULL || el->num_values == 0) { ret = ENOENT; goto done; } *value = ldb_msg_find_attr_as_bool(res->msgs[0], attr_name, false); ret = EOK; done: talloc_free(tmp_ctx); return ret; } errno_t sysdb_set_bool(struct sysdb_ctx *sysdb, struct ldb_dn *dn, const char *cn_value, const char *attr_name, bool value) { TALLOC_CTX *tmp_ctx = NULL; struct ldb_message *msg = NULL; struct ldb_result *res = NULL; errno_t ret; int lret; if (dn == NULL || cn_value == NULL || attr_name == NULL) { return EINVAL; } tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } lret = ldb_search(sysdb->ldb, tmp_ctx, &res, dn, LDB_SCOPE_BASE, NULL, NULL); if (lret != LDB_SUCCESS) { ret = sysdb_error_to_errno(lret); goto done; } msg = ldb_msg_new(tmp_ctx); if (msg == NULL) { ret = ENOMEM; goto done; } msg->dn = dn; if (res->count == 0) { lret = ldb_msg_add_string(msg, "cn", cn_value); if (lret != LDB_SUCCESS) { ret = sysdb_error_to_errno(lret); goto done; } } else if (res->count != 1) { DEBUG(SSSDBG_CRIT_FAILURE, "Got more than one reply for base search!\n"); ret = EIO; goto done; } else { lret = ldb_msg_add_empty(msg, attr_name, LDB_FLAG_MOD_REPLACE, NULL); if (lret != LDB_SUCCESS) { ret = sysdb_error_to_errno(lret); goto done; } } lret = ldb_msg_add_string(msg, attr_name, value ? "TRUE" : "FALSE"); if (lret != LDB_SUCCESS) { ret = sysdb_error_to_errno(lret); goto done; } if (res->count) { lret = ldb_modify(sysdb->ldb, msg); } else { lret = ldb_add(sysdb->ldb, msg); } if (lret != LDB_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, "ldb operation failed: [%s](%d)[%s]\n", ldb_strerror(lret), lret, ldb_errstring(sysdb->ldb)); } ret = sysdb_error_to_errno(lret); done: talloc_free(tmp_ctx); return ret; } errno_t sysdb_has_enumerated(struct sss_domain_info *domain, bool *has_enumerated) { errno_t ret; struct ldb_dn *dn; TALLOC_CTX *tmp_ctx; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { ret = ENOMEM; goto done; } dn = ldb_dn_new_fmt(tmp_ctx, domain->sysdb->ldb, SYSDB_DOM_BASE, domain->name); if (!dn) { ret = ENOMEM; goto done; } ret = sysdb_get_bool(domain->sysdb, dn, SYSDB_HAS_ENUMERATED, has_enumerated); done: talloc_free(tmp_ctx); return ret; } errno_t sysdb_set_enumerated(struct sss_domain_info *domain, bool enumerated) { errno_t ret; TALLOC_CTX *tmp_ctx; struct ldb_dn *dn; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { ret = ENOMEM; goto done; } dn = ldb_dn_new_fmt(tmp_ctx, domain->sysdb->ldb, SYSDB_DOM_BASE, domain->name); if (!dn) { ret = ENOMEM; goto done; } ret = sysdb_set_bool(domain->sysdb, dn, domain->name, SYSDB_HAS_ENUMERATED, enumerated); done: talloc_free(tmp_ctx); return ret; } errno_t sysdb_attrs_primary_name(struct sysdb_ctx *sysdb, struct sysdb_attrs *attrs, const char *ldap_attr, const char **_primary) { errno_t ret; char *rdn_attr = NULL; char *rdn_val = NULL; struct ldb_message_element *sysdb_name_el; struct ldb_message_element *orig_dn_el; size_t i; TALLOC_CTX *tmp_ctx = NULL; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } ret = sysdb_attrs_get_el(attrs, SYSDB_NAME, &sysdb_name_el); if (ret != EOK || sysdb_name_el->num_values == 0) { ret = EINVAL; goto done; } if (sysdb_name_el->num_values == 1) { /* Entry contains only one name. Just return that */ *_primary = (const char *)sysdb_name_el->values[0].data; ret = EOK; goto done; } /* Multiple values for name. Check whether one matches the RDN */ ret = sysdb_attrs_get_el(attrs, SYSDB_ORIG_DN, &orig_dn_el); if (ret) { goto done; } if (orig_dn_el->num_values == 0) { DEBUG(SSSDBG_CRIT_FAILURE, "Original DN is not available.\n"); ret = EINVAL; goto done; } else if (orig_dn_el->num_values == 1) { ret = sysdb_get_rdn(sysdb, tmp_ctx, (const char *) orig_dn_el->values[0].data, &rdn_attr, &rdn_val); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not get rdn from [%s]\n", (const char *) orig_dn_el->values[0].data); goto done; } } else { DEBUG(SSSDBG_CRIT_FAILURE, "Should not have more than one origDN\n"); ret = EINVAL; goto done; } /* First check whether the attribute name matches */ DEBUG(SSSDBG_TRACE_INTERNAL, "Comparing attribute names [%s] and [%s]\n", rdn_attr, ldap_attr); if (strcasecmp(rdn_attr, ldap_attr) != 0) { /* Multiple entries, and the RDN attribute doesn't match. * We have no way of resolving this deterministically, * so we'll use the first value as a fallback. */ DEBUG(SSSDBG_MINOR_FAILURE, "The entry has multiple names and the RDN attribute does " "not match. Will use the first value as fallback.\n"); *_primary = (const char *)sysdb_name_el->values[0].data; ret = EOK; goto done; } for (i = 0; i < sysdb_name_el->num_values; i++) { if (strcasecmp(rdn_val, (const char *)sysdb_name_el->values[i].data) == 0) { /* This name matches the RDN. Use it */ break; } } if (i < sysdb_name_el->num_values) { /* Match was found */ *_primary = (const char *)sysdb_name_el->values[i].data; } else { /* If we can't match the name to the RDN, we just have to * throw up our hands. There's no deterministic way to * decide which name is correct. */ DEBUG(SSSDBG_CRIT_FAILURE, "Cannot save entry. Unable to determine groupname\n"); ret = EINVAL; goto done; } ret = EOK; done: if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not determine primary name: [%d][%s]\n", ret, strerror(ret)); } talloc_free(tmp_ctx); return ret; } /* * An entity with multiple names would have multiple SYSDB_NAME attributes * after being translated into sysdb names using a map. * Given a primary name returned by sysdb_attrs_primary_name(), this function * returns the other SYSDB_NAME attribute values so they can be saved as * SYSDB_NAME_ALIAS into cache. * * If lowercase is set, all aliases are duplicated in lowercase as well. */ errno_t sysdb_attrs_get_aliases(TALLOC_CTX *mem_ctx, struct sysdb_attrs *attrs, const char *primary, bool lowercase, const char ***_aliases) { TALLOC_CTX *tmp_ctx = NULL; struct ldb_message_element *sysdb_name_el; size_t i, j, ai; errno_t ret; const char **aliases = NULL; const char *name; char *lower; if (_aliases == NULL) return EINVAL; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } ret = sysdb_attrs_get_el(attrs, SYSDB_NAME, &sysdb_name_el); if (ret != EOK || sysdb_name_el->num_values == 0) { ret = EINVAL; goto done; } aliases = talloc_array(tmp_ctx, const char *, sysdb_name_el->num_values + 1); if (!aliases) { ret = ENOMEM; goto done; } if (lowercase) { DEBUG(SSSDBG_TRACE_INTERNAL, "Domain is case-insensitive; will add lowercased aliases\n"); } ai = 0; for (i=0; i < sysdb_name_el->num_values; i++) { name = (const char *)sysdb_name_el->values[i].data; if (lowercase) { /* Domain is case-insensitive. Save the lower-cased version */ lower = sss_tc_utf8_str_tolower(tmp_ctx, name); if (!lower) { ret = ENOMEM; goto done; } for (j=0; j < ai; j++) { if (sss_utf8_case_eq((const uint8_t *) aliases[j], (const uint8_t *) lower) == ENOMATCH) { break; } } if (ai == 0 || j < ai) { aliases[ai] = talloc_strdup(aliases, lower); if (!aliases[ai]) { ret = ENOMEM; goto done; } ai++; } } else { /* Domain is case-sensitive. Save it as-is */ if (strcmp(primary, name) != 0) { aliases[ai] = talloc_strdup(aliases, name); if (!aliases[ai]) { ret = ENOMEM; goto done; } ai++; } } } aliases[ai] = NULL; ret = EOK; done: *_aliases = talloc_steal(mem_ctx, aliases); talloc_free(tmp_ctx); return ret; } static errno_t _sysdb_attrs_primary_name_list(struct sss_domain_info *domain, TALLOC_CTX *mem_ctx, struct sysdb_attrs **attr_list, size_t attr_count, const char *ldap_attr, bool qualify_names, char ***name_list) { errno_t ret; size_t i, j; char **list; const char *name; /* Assume that every entry has a primary name */ list = talloc_array(mem_ctx, char *, attr_count+1); if (!list) { return ENOMEM; } j = 0; for (i = 0; i < attr_count; i++) { ret = sysdb_attrs_primary_name(domain->sysdb, attr_list[i], ldap_attr, &name); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not determine primary name\n"); /* Skip and continue. Don't advance 'j' */ continue; } if (qualify_names == false) { list[j] = talloc_strdup(list, name); } else { list[j] = sss_create_internal_fqname(list, name, domain->name); } if (!list[j]) { ret = ENOMEM; goto done; } j++; } /* NULL-terminate the list */ list[j] = NULL; *name_list = list; ret = EOK; done: if (ret != EOK) { talloc_free(list); } return ret; } errno_t sysdb_attrs_primary_name_list(struct sss_domain_info *domain, TALLOC_CTX *mem_ctx, struct sysdb_attrs **attr_list, size_t attr_count, const char *ldap_attr, char ***name_list) { return _sysdb_attrs_primary_name_list(domain, mem_ctx, attr_list, attr_count, ldap_attr, false, name_list); } errno_t sysdb_attrs_primary_fqdn_list(struct sss_domain_info *domain, TALLOC_CTX *mem_ctx, struct sysdb_attrs **attr_list, size_t attr_count, const char *ldap_attr, char ***name_list) { return _sysdb_attrs_primary_name_list(domain, mem_ctx, attr_list, attr_count, ldap_attr, true, name_list); } errno_t sysdb_msg2attrs(TALLOC_CTX *mem_ctx, size_t count, struct ldb_message **msgs, struct sysdb_attrs ***attrs) { int i; struct sysdb_attrs **a; a = talloc_array(mem_ctx, struct sysdb_attrs *, count); if (a == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc_array failed.\n"); return ENOMEM; } for (i = 0; i < count; i++) { a[i] = talloc(a, struct sysdb_attrs); if (a[i] == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); talloc_free(a); return ENOMEM; } a[i]->num = msgs[i]->num_elements; a[i]->a = talloc_steal(a[i], msgs[i]->elements); } *attrs = a; return EOK; } struct ldb_message *sysdb_attrs2msg(TALLOC_CTX *mem_ctx, struct ldb_dn *entry_dn, struct sysdb_attrs *attrs, int mod_op) { struct ldb_message *msg; errno_t ret; msg = ldb_msg_new(mem_ctx); if (msg == NULL) { ret = ENOMEM; goto done; } msg->dn = entry_dn; msg->elements = talloc_array(msg, struct ldb_message_element, attrs->num); if (msg->elements == NULL) { ret = ENOMEM; goto done; } for (int i = 0; i < attrs->num; i++) { msg->elements[i] = attrs->a[i]; msg->elements[i].flags = mod_op; } msg->num_elements = attrs->num; ret = EOK; done: if (ret != EOK) { talloc_zfree(msg); } return msg; } int sysdb_compare_usn(const char *a, const char *b) { size_t len_a; size_t len_b; if (a == NULL) { return -1; } if (b == NULL) { return 1; } len_a = strlen(a); len_b = strlen(b); /* trim leading zeros */ while (len_a > 0 && *a == '0') { a++; len_a--; } while (len_b > 0 && *b == '0') { b++; len_b--; } /* less digits means lower number */ if (len_a < len_b) { return -1; } /* more digits means bigger number */ if (len_a > len_b) { return 1; } /* now we can compare digits since alphabetical order is the same * as numeric order */ return strcmp(a, b); } errno_t sysdb_get_highest_usn(TALLOC_CTX *mem_ctx, struct sysdb_attrs **attrs, size_t num_attrs, char **_usn) { const char *highest = NULL; const char *current = NULL; char *usn; errno_t ret; size_t i; if (num_attrs == 0 || attrs == NULL) { goto done; } for (i = 0; i < num_attrs; i++) { ret = sysdb_attrs_get_string(attrs[i], SYSDB_USN, ¤t); if (ret == ENOENT) { /* USN value is not present, assuming zero. */ current = "0"; } else if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Failed to retrieve USN value " "[%d]: %s\n", ret, sss_strerror(ret)); return ret; } if (current == NULL) { continue; } if (highest == NULL) { highest = current; continue; } if (sysdb_compare_usn(current, highest) > 0 ) { highest = current; } } done: if (highest == NULL) { usn = talloc_strdup(mem_ctx, "0"); } else { usn = talloc_strdup(mem_ctx, highest); } if (usn == NULL) { return ENOMEM; } *_usn = usn; return EOK; } static int sysdb_ldb_msg_string_helper(struct ldb_message *msg, int flags, const char *attr, const char *value) { int ret; ret = ldb_msg_add_empty(msg, attr, flags, NULL); if (ret == LDB_SUCCESS) { ret = ldb_msg_add_string(msg, attr, value); if (ret == LDB_SUCCESS) return EOK; } return ENOMEM; } int sysdb_add_string(struct ldb_message *msg, const char *attr, const char *value) { return sysdb_ldb_msg_string_helper(msg, LDB_FLAG_MOD_ADD, attr, value); } int sysdb_replace_string(struct ldb_message *msg, const char *attr, const char *value) { return sysdb_ldb_msg_string_helper(msg, LDB_FLAG_MOD_REPLACE, attr, value); } int sysdb_delete_string(struct ldb_message *msg, const char *attr, const char *value) { return sysdb_ldb_msg_string_helper(msg, LDB_FLAG_MOD_DELETE, attr, value); } static int sysdb_ldb_msg_ulong_helper(struct ldb_message *msg, int flags, const char *attr, unsigned long value) { int ret; ret = ldb_msg_add_empty(msg, attr, flags, NULL); if (ret == LDB_SUCCESS) { ret = ldb_msg_add_fmt(msg, attr, "%lu", value); if (ret == LDB_SUCCESS) return EOK; } return ENOMEM; } int sysdb_add_ulong(struct ldb_message *msg, const char *attr, unsigned long value) { return sysdb_ldb_msg_ulong_helper(msg, LDB_FLAG_MOD_ADD, attr, value); } int sysdb_replace_ulong(struct ldb_message *msg, const char *attr, unsigned long value) { return sysdb_ldb_msg_ulong_helper(msg, LDB_FLAG_MOD_REPLACE, attr, value); } int sysdb_delete_ulong(struct ldb_message *msg, const char *attr, unsigned long value) { return sysdb_ldb_msg_ulong_helper(msg, LDB_FLAG_MOD_DELETE, attr, value); } bool is_ts_ldb_dn(struct ldb_dn *dn) { const char *sysdb_comp_name = NULL; const struct ldb_val *sysdb_comp_val = NULL; if (dn == NULL) { return false; } sysdb_comp_name = ldb_dn_get_component_name(dn, 1); if (strcasecmp("cn", sysdb_comp_name) != 0) { /* The second component name is not "cn" */ return false; } sysdb_comp_val = ldb_dn_get_component_val(dn, 1); if (strncasecmp("users", (const char *) sysdb_comp_val->data, sysdb_comp_val->length) == 0) { return true; } sysdb_comp_val = ldb_dn_get_component_val(dn, 1); if (strncasecmp("groups", (const char *) sysdb_comp_val->data, sysdb_comp_val->length) == 0) { return true; } return false; } bool sysdb_msg_attrs_modts_differs(struct ldb_message *old_entry, struct sysdb_attrs *new_entry) { const char *old_entry_ts_attr = NULL; const char *new_entry_ts_attr = NULL; errno_t ret; old_entry_ts_attr = ldb_msg_find_attr_as_string(old_entry, SYSDB_ORIG_MODSTAMP, NULL); if (old_entry_ts_attr == NULL) { /* we didn't know the originalModifyTimestamp earlier. Regardless * of whether the new_entry has the timestamp, we should do * a comparison of the attributes */ return true; } if (new_entry == NULL) { return false; } ret = sysdb_attrs_get_string(new_entry, SYSDB_ORIG_MODSTAMP, &new_entry_ts_attr); if (ret != EOK) { /* Nothing to compare against in the new entry either, do * a comparison of the attributes */ return true; } if (old_entry_ts_attr != NULL && new_entry_ts_attr != NULL && strcmp(old_entry_ts_attr, new_entry_ts_attr) == 0) { return false; } return true; } static bool sysdb_ldb_msg_difference(struct ldb_message *db_msg, struct ldb_message *mod_msg) { struct ldb_message_element *mod_msg_el; struct ldb_message_element *db_msg_el; int el_differs; for (unsigned i = 0; i < mod_msg->num_elements; i++) { mod_msg_el = &mod_msg->elements[i]; switch (mod_msg_el->flags) { case 0: /* Unspecified flags are internally converted to SYSDB_MOD_REP in * sysdb_set_group_attr, do the same here */ case SYSDB_MOD_ADD: case SYSDB_MOD_REP: db_msg_el = ldb_msg_find_element(db_msg, mod_msg_el->name); if (db_msg_el == NULL) { /* The attribute to be added does not exist in the target * message, this is a modification. Special-case adding * empty elements which also do not exist in the target * message. This is how sysdb callers ensure a particular * element is not present in the database. */ if (mod_msg_el->num_values > 0) { /* We can ignore additions of timestamp attributes */ return true; } break; } el_differs = ldb_msg_element_compare(db_msg_el, mod_msg_el); if (el_differs) { /* We are replacing or extending element, there is a difference. If * some values already exist and ldb_add is not permissive, * ldb will throw an error, but that's not our job to check.. */ if (is_ts_cache_attr(mod_msg_el->name) == false) { /* We can ignore changes to timestamp attributes */ return true; } } break; case SYSDB_MOD_DEL: db_msg_el = ldb_msg_find_element(db_msg, mod_msg_el->name); if (db_msg_el != NULL) { /* We are deleting a valid element, there is a difference */ return true; } break; } } return false; } bool sysdb_entry_attrs_diff(struct sysdb_ctx *sysdb, struct ldb_dn *entry_dn, struct sysdb_attrs *attrs, int mod_op) { struct ldb_message *new_entry_msg = NULL; TALLOC_CTX *tmp_ctx; bool differs = true; int lret; errno_t ret; struct ldb_result *res; const char *attrnames[attrs->num+1]; if (sysdb->ldb_ts == NULL) { return true; } if (is_ts_ldb_dn(entry_dn) == false) { return true; } tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { goto done; } new_entry_msg = sysdb_attrs2msg(tmp_ctx, entry_dn, attrs, mod_op); if (new_entry_msg == NULL) { goto done; } for (int i = 0; i < attrs->num; i++) { attrnames[i] = attrs->a[i].name; } attrnames[attrs->num] = NULL; lret = ldb_search(sysdb->ldb, tmp_ctx, &res, entry_dn, LDB_SCOPE_BASE, attrnames, NULL); if (lret != LDB_SUCCESS) { ret = sysdb_error_to_errno(lret); DEBUG(SSSDBG_MINOR_FAILURE, "Cannot search sysdb: %d\n", ret); goto done; } if (res->count == 0) { return true; } else if (res->count != 1) { ret = EIO; goto done; } differs = sysdb_ldb_msg_difference(res->msgs[0], new_entry_msg); done: talloc_free(tmp_ctx); return differs; }