sysdb.c revision dd285415d7a8d8376207960cfa3e977524c3b98c
/*
SSSD
System Database
Copyright (C) 2008-2011 Simo Sorce <ssorce@redhat.com>
Copyright (C) 2008-2011 Stephen Gallagher <ssorce@redhat.com>
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/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 <time.h>
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;
}
errno_t sysdb_attrs_primary_name_list(struct sysdb_ctx *sysdb,
TALLOC_CTX *mem_ctx,
struct sysdb_attrs **attr_list,
size_t attr_count,
const char *ldap_attr,
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(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;
}
list[j] = talloc_strdup(list, 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_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;
}
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, &current);
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;
}