/* SSSD System Database Copyright (C) Simo Sorce 2008 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 "db/sysdb_private.h" #include "db/sysdb_services.h" #include "db/sysdb_autofs.h" #include "util/crypto/sss_crypto.h" #include "util/cert.h" #include static uint32_t get_attr_as_uint32(struct ldb_message *msg, const char *attr) { const struct ldb_val *v = ldb_msg_find_ldb_val(msg, attr); long long int l; if (!v || !v->data) { return 0; } errno = 0; l = strtoll((const char *)v->data, NULL, 10); if (errno) { return (uint32_t)-1; } if (l < 0 || l > ((uint32_t)(-1))) { return (uint32_t)-1; } return l; } /* * The wrapper around ldb_modify that uses LDB_CONTROL_PERMISSIVE_MODIFY_OID * so that on adds entries that already exist are skipped and similarly * entries that are missing are ignored on deletes * * Please note this function returns LDB error codes, not sysdb error * codes on purpose, see usage in callers! */ int sss_ldb_modify_permissive(struct ldb_context *ldb, struct ldb_message *msg) { struct ldb_request *req; int ret = EOK; ret = ldb_build_mod_req(&req, ldb, ldb, msg, NULL, NULL, ldb_op_default_callback, NULL); if (ret != LDB_SUCCESS) return ret; ret = ldb_request_add_control(req, LDB_CONTROL_PERMISSIVE_MODIFY_OID, false, NULL); if (ret != LDB_SUCCESS) { talloc_free(req); return ret; } ret = ldb_request(ldb, req); if (ret == LDB_SUCCESS) { ret = ldb_wait(req->handle, LDB_WAIT_ALL); } talloc_free(req); /* Please note this function returns LDB error codes, not sysdb error * codes on purpose, see usage in callers! */ return ret; } #define ERROR_OUT(v, r, l) do { v = r; goto l; } while(0) /* =Remove-Entry-From-Sysdb=============================================== */ static int sysdb_delete_cache_entry(struct ldb_context *ldb, struct ldb_dn *dn, bool ignore_not_found) { int ret; ret = ldb_delete(ldb, dn); switch (ret) { case LDB_SUCCESS: return EOK; case LDB_ERR_NO_SUCH_OBJECT: if (ignore_not_found) { return EOK; } /* fall through */ default: DEBUG(SSSDBG_CRIT_FAILURE, "LDB Error: %s(%d)\nError Message: [%s]\n", ldb_strerror(ret), ret, ldb_errstring(ldb)); return sysdb_error_to_errno(ret); } } static int sysdb_delete_ts_entry(struct sysdb_ctx *sysdb, struct ldb_dn *dn) { if (sysdb->ldb_ts == NULL) { return EOK; } return sysdb_delete_cache_entry(sysdb->ldb_ts, dn, true); } int sysdb_delete_entry(struct sysdb_ctx *sysdb, struct ldb_dn *dn, bool ignore_not_found) { errno_t ret; ret = sysdb_delete_cache_entry(sysdb->ldb, dn, ignore_not_found); if (ret == EOK) { ret = sysdb_delete_ts_entry(sysdb, dn); DEBUG(SSSDBG_MINOR_FAILURE, "sysdb_delete_ts_entry failed: %d\n", ret); } else { DEBUG(SSSDBG_OP_FAILURE, "sysdb_delete_cache_entry failed: %d\n", ret); } return ret; } /* =Remove-Subentries-From-Sysdb=========================================== */ int sysdb_delete_recursive(struct sysdb_ctx *sysdb, struct ldb_dn *dn, bool ignore_not_found) { const char *no_attrs[] = { NULL }; struct ldb_message **msgs; size_t msgs_count; int ret; int i; TALLOC_CTX *tmp_ctx; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } ret = ldb_transaction_start(sysdb->ldb); if (ret) { ret = sysdb_error_to_errno(ret); goto done; } ret = sysdb_search_entry(tmp_ctx, sysdb, dn, LDB_SCOPE_SUBTREE, "(distinguishedName=*)", no_attrs, &msgs_count, &msgs); if (ret) { if (ignore_not_found && ret == ENOENT) { ret = EOK; } if (ret) { DEBUG(SSSDBG_TRACE_FUNC, "Search error: %d (%s)\n", ret, strerror(ret)); } goto done; } DEBUG(SSSDBG_TRACE_ALL, "Found [%zu] items to delete.\n", msgs_count); qsort(msgs, msgs_count, sizeof(struct ldb_message *), compare_ldb_dn_comp_num); for (i = 0; i < msgs_count; i++) { DEBUG(SSSDBG_TRACE_ALL, "Trying to delete [%s].\n", ldb_dn_get_linearized(msgs[i]->dn)); ret = sysdb_delete_entry(sysdb, msgs[i]->dn, false); if (ret) { goto done; } } done: if (ret == EOK) { ret = ldb_transaction_commit(sysdb->ldb); ret = sysdb_error_to_errno(ret); } else { ldb_transaction_cancel(sysdb->ldb); } talloc_free(tmp_ctx); return ret; } /* =Search-Entry========================================================== */ static int sysdb_cache_search_entry(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, struct ldb_dn *base_dn, enum ldb_scope scope, const char *filter, const char **attrs, size_t *_msgs_count, struct ldb_message ***_msgs) { TALLOC_CTX *tmp_ctx; struct ldb_result *res; int ret; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { ret = ENOMEM; goto done; } ret = ldb_search(ldb, tmp_ctx, &res, base_dn, scope, attrs, filter?"%s":NULL, filter); if (ret != EOK) { ret = sysdb_error_to_errno(ret); goto done; } *_msgs_count = res->count; *_msgs = talloc_steal(mem_ctx, res->msgs); if (res->count == 0) { ret = ENOENT; goto done; } done: talloc_zfree(tmp_ctx); return ret; } int sysdb_search_entry(TALLOC_CTX *mem_ctx, struct sysdb_ctx *sysdb, struct ldb_dn *base_dn, enum ldb_scope scope, const char *filter, const char **attrs, size_t *_msgs_count, struct ldb_message ***_msgs) { errno_t ret; ret = sysdb_cache_search_entry(mem_ctx, sysdb->ldb, base_dn, scope, filter, attrs, _msgs_count, _msgs); if (ret != EOK) { return ret; } return sysdb_merge_msg_list_ts_attrs(sysdb, *_msgs_count, *_msgs, attrs); } int sysdb_search_ts_entry(TALLOC_CTX *mem_ctx, struct sysdb_ctx *sysdb, struct ldb_dn *base_dn, enum ldb_scope scope, const char *filter, const char **attrs, size_t *_msgs_count, struct ldb_message ***_msgs) { if (sysdb->ldb_ts == NULL) { if (_msgs_count != NULL) { *_msgs_count = 0; } if (_msgs != NULL) { *_msgs = NULL; } return EOK; } return sysdb_cache_search_entry(mem_ctx, sysdb->ldb_ts, base_dn, scope, filter, attrs, _msgs_count, _msgs); } /* =Search-Entry-by-SID-string============================================ */ int sysdb_search_entry_by_sid_str(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *search_base, const char *filter_str, const char *sid_str, const char **attrs, struct ldb_message **msg) { TALLOC_CTX *tmp_ctx; const char *def_attrs[] = { SYSDB_NAME, SYSDB_SID_STR, NULL }; struct ldb_message **msgs = NULL; struct ldb_dn *basedn; size_t msgs_count = 0; char *filter; int ret; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } basedn = ldb_dn_new_fmt(tmp_ctx, domain->sysdb->ldb, search_base, domain->name); if (!basedn) { ret = ENOMEM; goto done; } filter = talloc_asprintf(tmp_ctx, filter_str, sid_str); if (!filter) { ret = ENOMEM; goto done; } ret = sysdb_search_entry(tmp_ctx, domain->sysdb, basedn, LDB_SCOPE_SUBTREE, filter, attrs?attrs:def_attrs, &msgs_count, &msgs); if (ret) { goto done; } *msg = talloc_steal(mem_ctx, msgs[0]); done: if (ret == ENOENT) { DEBUG(SSSDBG_TRACE_FUNC, "No such entry\n"); } else if (ret) { DEBUG(SSSDBG_OP_FAILURE, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(tmp_ctx); return ret; } /* =Search-User-by-[UID/SID/NAME]============================================= */ enum sysdb_obj_type { SYSDB_UNKNOWN = 0, SYSDB_USER, SYSDB_GROUP }; static int sysdb_search_by_name(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *name, enum sysdb_obj_type type, const char **attrs, struct ldb_message **msg) { TALLOC_CTX *tmp_ctx; const char *def_attrs[] = { SYSDB_NAME, NULL, NULL }; const char *base_tmpl = NULL; const char *filter_tmpl = NULL; struct ldb_message **msgs = NULL; struct ldb_dn *basedn; size_t msgs_count = 0; char *sanitized_name; char *lc_sanitized_name; char *filter; int ret; switch (type) { case SYSDB_USER: def_attrs[1] = SYSDB_UIDNUM; base_tmpl = SYSDB_TMPL_USER_BASE; filter_tmpl = SYSDB_PWNAM_FILTER; break; case SYSDB_GROUP: def_attrs[1] = SYSDB_GIDNUM; base_tmpl = SYSDB_TMPL_GROUP_BASE; filter_tmpl = SYSDB_GRNAM_FILTER; break; default: return EINVAL; } tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } basedn = ldb_dn_new_fmt(tmp_ctx, domain->sysdb->ldb, base_tmpl, domain->name); if (!basedn) { ret = ENOMEM; goto done; } ret = sss_filter_sanitize_for_dom(tmp_ctx, name, domain, &sanitized_name, &lc_sanitized_name); if (ret != EOK) { goto done; } filter = talloc_asprintf(tmp_ctx, filter_tmpl, lc_sanitized_name, sanitized_name, sanitized_name); if (!filter) { ret = ENOMEM; goto done; } ret = sysdb_search_entry(tmp_ctx, domain->sysdb, basedn, LDB_SCOPE_SUBTREE, filter, attrs?attrs:def_attrs, &msgs_count, &msgs); if (ret) { goto done; } ret = sysdb_merge_msg_list_ts_attrs(domain->sysdb, msgs_count, msgs, attrs); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Cannot retrieve timestamp attributes\n"); } *msg = talloc_steal(mem_ctx, msgs[0]); done: if (ret == ENOENT) { DEBUG(SSSDBG_TRACE_FUNC, "No such entry\n"); } else if (ret) { DEBUG(SSSDBG_OP_FAILURE, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(tmp_ctx); return ret; } int sysdb_search_user_by_name(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *name, const char **attrs, struct ldb_message **msg) { return sysdb_search_by_name(mem_ctx, domain, name, SYSDB_USER, attrs, msg); } int sysdb_search_user_by_uid(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, uid_t uid, const char **attrs, struct ldb_message **msg) { TALLOC_CTX *tmp_ctx; const char *def_attrs[] = { SYSDB_NAME, SYSDB_UIDNUM, NULL }; struct ldb_message **msgs = NULL; struct ldb_dn *basedn; size_t msgs_count = 0; char *filter; int ret; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } basedn = sysdb_user_base_dn(tmp_ctx, domain); if (!basedn) { ret = ENOMEM; goto done; } filter = talloc_asprintf(tmp_ctx, SYSDB_PWUID_FILTER, (unsigned long)uid); if (!filter) { ret = ENOMEM; goto done; } /* Use SUBTREE scope here, not ONELEVEL * There is a bug in LDB that makes ONELEVEL searches extremely * slow (it ignores indexing) */ ret = sysdb_search_entry(tmp_ctx, domain->sysdb, basedn, LDB_SCOPE_SUBTREE, filter, attrs?attrs:def_attrs, &msgs_count, &msgs); if (ret) { goto done; } *msg = talloc_steal(mem_ctx, msgs[0]); done: if (ret == ENOENT) { DEBUG(SSSDBG_TRACE_FUNC, "No such entry\n"); } else if (ret) { DEBUG(SSSDBG_OP_FAILURE, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(tmp_ctx); return ret; } int sysdb_search_user_by_sid_str(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *sid_str, const char **attrs, struct ldb_message **msg) { return sysdb_search_entry_by_sid_str(mem_ctx, domain, SYSDB_TMPL_USER_BASE, SYSDB_PWSID_FILTER, sid_str, attrs, msg); } int sysdb_search_user_by_upn_res(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *upn, const char **attrs, struct ldb_result **out_res) { TALLOC_CTX *tmp_ctx; struct ldb_result *res; struct ldb_dn *base_dn; int ret; const char *def_attrs[] = { SYSDB_NAME, SYSDB_UPN, SYSDB_CANONICAL_UPN, NULL }; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { ret = ENOMEM; goto done; } base_dn = sysdb_base_dn(domain->sysdb, tmp_ctx); if (base_dn == NULL) { ret = ENOMEM; goto done; } ret = ldb_search(domain->sysdb->ldb, tmp_ctx, &res, base_dn, LDB_SCOPE_SUBTREE, attrs ? attrs : def_attrs, SYSDB_PWUPN_FILTER, upn, upn); if (ret != EOK) { ret = sysdb_error_to_errno(ret); goto done; } if (res->count == 0) { /* set result anyway */ *out_res = talloc_steal(mem_ctx, res); ret = ENOENT; goto done; } else if (res->count > 1) { DEBUG(SSSDBG_OP_FAILURE, "Search for upn [%s] returns more than one result.\n", upn); ret = EINVAL; goto done; } /* Merge in the timestamps from the fast ts db */ ret = sysdb_merge_res_ts_attrs(domain->sysdb, res, attrs ? attrs : def_attrs); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Cannot merge timestamp cache values\n"); /* non-fatal */ } *out_res = talloc_steal(mem_ctx, res); ret = EOK; done: talloc_zfree(tmp_ctx); return ret; } int sysdb_search_user_by_upn(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *upn, const char **attrs, struct ldb_message **msg) { TALLOC_CTX *tmp_ctx; struct ldb_result *res; errno_t ret; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { ret = ENOMEM; goto done; } ret = sysdb_search_user_by_upn_res(tmp_ctx, domain, upn, attrs, &res); if (ret == ENOENT) { DEBUG(SSSDBG_TRACE_FUNC, "No entry with upn [%s] found.\n", upn); goto done; } else if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Error: %d (%s)\n", ret, strerror(ret)); goto done; } *msg = talloc_steal(mem_ctx, res->msgs[0]); ret = EOK; done: talloc_zfree(tmp_ctx); return ret; } /* =Search-Group-by-[GID/SID/NAME]============================================ */ int sysdb_search_group_by_name(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *name, const char **attrs, struct ldb_message **msg) { return sysdb_search_by_name(mem_ctx, domain, name, SYSDB_GROUP, attrs, msg); } int sysdb_search_group_by_gid(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, gid_t gid, const char **attrs, struct ldb_message **msg) { TALLOC_CTX *tmp_ctx; const char *def_attrs[] = { SYSDB_NAME, SYSDB_GIDNUM, NULL }; struct ldb_message **msgs = NULL; struct ldb_dn *basedn; size_t msgs_count = 0; char *filter; int ret; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } basedn = sysdb_group_base_dn(tmp_ctx, domain); if (!basedn) { ret = ENOMEM; goto done; } filter = talloc_asprintf(tmp_ctx, SYSDB_GRGID_FILTER, (unsigned long)gid); if (!filter) { ret = ENOMEM; goto done; } /* Use SUBTREE scope here, not ONELEVEL * There is a bug in LDB that makes ONELEVEL searches extremely * slow (it ignores indexing) */ ret = sysdb_search_entry(tmp_ctx, domain->sysdb, basedn, LDB_SCOPE_SUBTREE, filter, attrs?attrs:def_attrs, &msgs_count, &msgs); if (ret) { goto done; } *msg = talloc_steal(mem_ctx, msgs[0]); done: if (ret == ENOENT) { DEBUG(SSSDBG_TRACE_FUNC, "No such entry\n"); } else if (ret) { DEBUG(SSSDBG_OP_FAILURE, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(tmp_ctx); return ret; } int sysdb_search_group_by_sid_str(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *sid_str, const char **attrs, struct ldb_message **msg) { return sysdb_search_entry_by_sid_str(mem_ctx, domain, SYSDB_TMPL_GROUP_BASE, SYSDB_GRSID_FILTER, sid_str, attrs, msg); } /* =Search-Group-by-Name============================================ */ int sysdb_search_netgroup_by_name(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *name, const char **attrs, struct ldb_message **msg) { TALLOC_CTX *tmp_ctx; static const char *def_attrs[] = { SYSDB_NAME, NULL }; struct ldb_message **msgs = NULL; struct ldb_dn *basedn; size_t msgs_count = 0; int ret; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } basedn = sysdb_netgroup_dn(tmp_ctx, domain, name); if (!basedn) { ret = ENOMEM; goto done; } ret = sysdb_search_entry(tmp_ctx, domain->sysdb, basedn, LDB_SCOPE_BASE, NULL, attrs?attrs:def_attrs, &msgs_count, &msgs); if (ret) { goto done; } *msg = talloc_steal(mem_ctx, msgs[0]); done: if (ret == ENOENT) { DEBUG(SSSDBG_TRACE_FUNC, "No such entry\n"); } else if (ret) { DEBUG(SSSDBG_OP_FAILURE, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(tmp_ctx); return ret; } /* =Timestamp-cache-functions==============================================*/ /* If modifyTimestamp is the same in the TS cache, return EOK. Return ERR_NO_TS * if there is no timestamps cache for this domain and ERR_TS_CACHE_MISS if * the entry had changed and the caller needs to update the sysdb cache as well. */ static errno_t sysdb_check_ts_cache(struct sss_domain_info *domain, struct ldb_dn *entry_dn, struct sysdb_attrs *entry) { errno_t ret; TALLOC_CTX *tmp_ctx; size_t msgs_count; struct ldb_message **msgs; bool mod_ts_differs; if (domain->sysdb->ldb_ts == NULL) { return ERR_NO_TS; } tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } /* Check if the entry is in the timestamp cache */ ret = sysdb_search_ts_entry(tmp_ctx, domain->sysdb, entry_dn, LDB_SCOPE_BASE, NULL, sysdb_ts_cache_attrs, &msgs_count, &msgs); if (ret != EOK) { DEBUG(SSSDBG_TRACE_INTERNAL, "Cannot find TS cache entry for [%s]: [%d]: %s\n", ldb_dn_get_linearized(entry_dn), ret, sss_strerror(ret)); goto done; } if (msgs_count != 1) { DEBUG(SSSDBG_CRIT_FAILURE, "Expected 1 result for base search, got %zu\n", msgs_count); return EIO; } mod_ts_differs = sysdb_msg_attrs_modts_differs(msgs[0], entry); if (mod_ts_differs == true) { ret = ERR_TS_CACHE_MISS; goto done; } ret = EOK; done: talloc_zfree(tmp_ctx); return ret; } static int sysdb_set_ts_entry_attr(struct sysdb_ctx *sysdb, struct ldb_dn *entry_dn, struct sysdb_attrs *attrs, int mod_op); static errno_t sysdb_create_ts_entry(struct sysdb_ctx *sysdb, struct ldb_dn *entry_dn, struct sysdb_attrs *attrs) { struct ldb_message *msg; errno_t ret; int lret; TALLOC_CTX *tmp_ctx; if (sysdb->ldb_ts == NULL || attrs->num == 0) { return EOK; } tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } if (entry_dn == NULL) { ret = EINVAL; goto done; } msg = sysdb_attrs2msg(tmp_ctx, entry_dn, attrs, 0); if (msg == NULL) { ret = ENOMEM; goto done; } lret = ldb_add(sysdb->ldb_ts, msg); if (lret != LDB_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, "ldb_add failed: [%s](%d)[%s]\n", ldb_strerror(lret), lret, ldb_errstring(sysdb->ldb_ts)); } ret = sysdb_error_to_errno(lret); done: if (ret == ENOENT) { DEBUG(SSSDBG_TRACE_FUNC, "No such entry\n"); } else if (ret) { DEBUG(SSSDBG_OP_FAILURE, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(tmp_ctx); return ret; } static struct sysdb_attrs *ts_obj_attrs(TALLOC_CTX *mem_ctx, enum sysdb_obj_type obj_type) { struct sysdb_attrs *attrs; const char *oc; errno_t ret; switch (obj_type) { case SYSDB_USER: oc = SYSDB_USER_CLASS; break; case SYSDB_GROUP: oc = SYSDB_GROUP_CLASS; break; default: return NULL; } attrs = sysdb_new_attrs(mem_ctx); if (attrs == NULL) { return NULL; } ret = sysdb_attrs_add_string(attrs, SYSDB_OBJECTCLASS, oc); if (ret != EOK) { talloc_free(attrs); return NULL; } return attrs; } static errno_t sysdb_update_ts_cache(struct sss_domain_info *domain, struct ldb_dn *entry_dn, struct sysdb_attrs *entry_attrs, struct sysdb_attrs *ts_attrs, int mod_op, uint64_t cache_timeout, time_t now) { errno_t ret; TALLOC_CTX *tmp_ctx; const char *modstamp; if (domain->sysdb->ldb_ts == NULL) { DEBUG(SSSDBG_TRACE_INTERNAL, "No timestamp cache for this domain\n"); return EOK; } tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } if (ts_attrs == NULL) { ts_attrs = sysdb_new_attrs(tmp_ctx); if (ts_attrs == NULL) { ret = ENOMEM; goto done; } } ret = sysdb_attrs_add_time_t(ts_attrs, SYSDB_LAST_UPDATE, now); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to add %s to tsdb\n", SYSDB_LAST_UPDATE); goto done; } ret = sysdb_attrs_add_time_t(ts_attrs, SYSDB_CACHE_EXPIRE, ((cache_timeout) ? (now + cache_timeout) : 0)); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to add %s to tsdb\n", SYSDB_CACHE_EXPIRE); goto done; } if (entry_attrs != NULL) { ret = sysdb_attrs_get_string(entry_attrs, SYSDB_ORIG_MODSTAMP, &modstamp); if (ret == EOK) { ret = sysdb_attrs_add_string(ts_attrs, SYSDB_ORIG_MODSTAMP, modstamp); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to add %s to tsdb\n", SYSDB_ORIG_MODSTAMP); goto done; } } } ret = sysdb_set_ts_entry_attr(domain->sysdb, entry_dn, ts_attrs, mod_op); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Cannot set ts attrs for group %s\n", ldb_dn_get_linearized(entry_dn)); /* Not fatal */ } ret = EOK; done: talloc_free(tmp_ctx); return ret; } static errno_t sysdb_check_and_update_ts_cache(struct sss_domain_info *domain, struct ldb_dn *entry_dn, struct sysdb_attrs *attrs, uint64_t cache_timeout, time_t now) { errno_t ret; ret = sysdb_check_ts_cache(domain, entry_dn, attrs); switch (ret) { case ENOENT: DEBUG(SSSDBG_TRACE_INTERNAL, "No timestamps entry\n"); break; case EOK: /* The entry's timestamp was the same. Just update the ts cache */ ret = sysdb_update_ts_cache(domain, entry_dn, attrs, NULL, SYSDB_MOD_REP, cache_timeout, now); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot update the timestamps cache [%d]: %s\n", ret, sss_strerror(ret)); } break; case ERR_TS_CACHE_MISS: case ERR_NO_TS: /* Either there is no cache or the cache is up-do-date. Just report * what's up */ break; default: DEBUG(SSSDBG_OP_FAILURE, "Error checking the timestamps cache [%d]: %s\n", ret, sss_strerror(ret)); break; } return ret; } static errno_t get_sysdb_obj_dn(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, enum sysdb_obj_type obj_type, const char *obj_name, struct ldb_dn **_obj_dn) { struct ldb_dn *obj_dn; switch (obj_type) { case SYSDB_USER: obj_dn = sysdb_user_dn(mem_ctx, domain, obj_name); break; case SYSDB_GROUP: obj_dn = sysdb_group_dn(mem_ctx, domain, obj_name); break; default: return EINVAL; } if (obj_dn == NULL) { return ENOMEM; } *_obj_dn = obj_dn; return EOK; } static errno_t sysdb_check_and_update_ts_obj(struct sss_domain_info *domain, enum sysdb_obj_type obj_type, const char *obj_name, struct sysdb_attrs *attrs, uint64_t cache_timeout, time_t now) { struct ldb_dn *entry_dn; TALLOC_CTX *tmp_ctx; errno_t ret; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } ret = get_sysdb_obj_dn(tmp_ctx, domain, obj_type, obj_name, &entry_dn); if (ret != EOK) { goto done; } ret = sysdb_check_and_update_ts_cache(domain, entry_dn, attrs, cache_timeout, now); done: talloc_zfree(tmp_ctx); return ret; } static errno_t sysdb_create_ts_obj(struct sss_domain_info *domain, enum sysdb_obj_type obj_type, const char *obj_name, uint64_t cache_timeout, time_t now) { struct ldb_dn *entry_dn; struct sysdb_attrs *ts_attrs = NULL; TALLOC_CTX *tmp_ctx; errno_t ret; if (domain->sysdb->ldb_ts == NULL) { return EOK; } tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } ret = get_sysdb_obj_dn(tmp_ctx, domain, obj_type, obj_name, &entry_dn); if (ret != EOK) { goto done; } ts_attrs = ts_obj_attrs(tmp_ctx, obj_type); if (ts_attrs == NULL) { ret = ENOMEM; goto done; } ret = sysdb_update_ts_cache(domain, entry_dn, NULL, ts_attrs, SYSDB_MOD_ADD, cache_timeout, now); if (ret != EOK) { goto done; } done: talloc_zfree(tmp_ctx); return ret; } static errno_t sysdb_check_and_update_ts_usr(struct sss_domain_info *domain, const char *grp_name, struct sysdb_attrs *attrs, uint64_t cache_timeout, time_t now) { return sysdb_check_and_update_ts_obj(domain, SYSDB_USER, grp_name, attrs, cache_timeout, now); } static errno_t sysdb_check_and_update_ts_grp(struct sss_domain_info *domain, const char *grp_name, struct sysdb_attrs *attrs, uint64_t cache_timeout, time_t now) { return sysdb_check_and_update_ts_obj(domain, SYSDB_GROUP, grp_name, attrs, cache_timeout, now); } static errno_t sysdb_create_ts_grp(struct sss_domain_info *domain, const char *grp_name, uint64_t cache_timeout, time_t now) { return sysdb_create_ts_obj(domain, SYSDB_GROUP, grp_name, cache_timeout, now); } static errno_t sysdb_create_ts_usr(struct sss_domain_info *domain, const char *usr_name, uint64_t cache_timeout, time_t now) { return sysdb_create_ts_obj(domain, SYSDB_USER, usr_name, cache_timeout, now); } /* =Replace-Attributes-On-Entry=========================================== */ static int sysdb_set_cache_entry_attr(struct ldb_context *ldb, struct ldb_dn *entry_dn, struct sysdb_attrs *attrs, int mod_op) { struct ldb_message *msg; int ret; int lret; TALLOC_CTX *tmp_ctx; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } if (entry_dn == NULL || attrs->num == 0) { ret = EINVAL; goto done; } msg = sysdb_attrs2msg(tmp_ctx, entry_dn, attrs, mod_op); if (msg == NULL) { ret = ENOMEM; goto done; } lret = ldb_modify(ldb, msg); if (lret != LDB_SUCCESS) { DEBUG(SSSDBG_MINOR_FAILURE, "ldb_modify failed: [%s](%d)[%s]\n", ldb_strerror(lret), lret, ldb_errstring(ldb)); } ret = sysdb_error_to_errno(lret); done: if (ret == ENOENT) { DEBUG(SSSDBG_TRACE_FUNC, "No such entry\n"); } else if (ret) { DEBUG(SSSDBG_OP_FAILURE, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(tmp_ctx); return ret; } int sysdb_set_entry_attr(struct sysdb_ctx *sysdb, struct ldb_dn *entry_dn, struct sysdb_attrs *attrs, int mod_op) { bool sysdb_write = true; errno_t ret = EOK; errno_t tret = EOK; sysdb_write = sysdb_entry_attrs_diff(sysdb, entry_dn, attrs, mod_op); if (sysdb_write == true) { ret = sysdb_set_cache_entry_attr(sysdb->ldb, entry_dn, attrs, mod_op); } if (ret == EOK) { tret = sysdb_set_ts_entry_attr(sysdb, entry_dn, attrs, mod_op); if (tret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Cannot set ts attrs for %s\n", ldb_dn_get_linearized(entry_dn)); /* Not fatal */ } } return ret; } static int sysdb_rep_ts_entry_attr(struct sysdb_ctx *sysdb, struct ldb_dn *entry_dn, struct sysdb_attrs *attrs) { if (sysdb->ldb_ts == NULL || attrs->num == 0) { return EOK; } return sysdb_set_cache_entry_attr(sysdb->ldb_ts, entry_dn, attrs, SYSDB_MOD_REP); } static int sysdb_set_ts_entry_attr(struct sysdb_ctx *sysdb, struct ldb_dn *entry_dn, struct sysdb_attrs *attrs, int mod_op) { struct sysdb_attrs *ts_attrs; TALLOC_CTX *tmp_ctx; errno_t ret; if (sysdb->ldb_ts == NULL) { return EOK; } tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } ts_attrs = sysdb_filter_ts_attrs(tmp_ctx, attrs); if (ts_attrs == NULL) { ret = ENOMEM; goto done; } switch (mod_op) { case SYSDB_MOD_REP: ret = sysdb_rep_ts_entry_attr(sysdb, entry_dn, ts_attrs); break; case SYSDB_MOD_ADD: ret = sysdb_create_ts_entry(sysdb, entry_dn, ts_attrs); break; default: ret = EINVAL; break; } done: talloc_zfree(tmp_ctx); return ret; } /* =Replace-Attributes-On-User============================================ */ int sysdb_set_user_attr(struct sss_domain_info *domain, const char *name, struct sysdb_attrs *attrs, int mod_op) { struct ldb_dn *dn; TALLOC_CTX *tmp_ctx; errno_t ret; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } dn = sysdb_user_dn(tmp_ctx, domain, name); if (!dn) { ret = ENOMEM; goto done; } ret = sysdb_set_entry_attr(domain->sysdb, dn, attrs, mod_op); if (ret != EOK) { goto done; } ret = EOK; done: talloc_zfree(tmp_ctx); return ret; } /* =Replace-Attributes-On-Group=========================================== */ int sysdb_set_group_attr(struct sss_domain_info *domain, const char *name, struct sysdb_attrs *attrs, int mod_op) { struct ldb_dn *dn; TALLOC_CTX *tmp_ctx; errno_t ret; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { ret = ENOMEM; goto done; } dn = sysdb_group_dn(tmp_ctx, domain, name); if (!dn) { ret = ENOMEM; goto done; } ret = sysdb_set_entry_attr(domain->sysdb, dn, attrs, mod_op); if (ret) { goto done; } ret = EOK; done: talloc_free(tmp_ctx); return ret; } /* =Replace-Attributes-On-Netgroup=========================================== */ int sysdb_set_netgroup_attr(struct sss_domain_info *domain, const char *name, struct sysdb_attrs *attrs, int mod_op) { errno_t ret; struct ldb_dn *dn; TALLOC_CTX *tmp_ctx; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } dn = sysdb_netgroup_dn(tmp_ctx, domain, name); if (!dn) { ret = ENOMEM; goto done; } ret = sysdb_set_entry_attr(domain->sysdb, dn, attrs, mod_op); done: talloc_free(tmp_ctx); return ret; } /* =Get-New-ID============================================================ */ int sysdb_get_new_id(struct sss_domain_info *domain, uint32_t *_id) { TALLOC_CTX *tmp_ctx; const char *attrs_1[] = { SYSDB_NEXTID, NULL }; const char *attrs_2[] = { SYSDB_UIDNUM, SYSDB_GIDNUM, NULL }; struct ldb_dn *base_dn; char *filter; uint32_t new_id = 0; struct ldb_message **msgs; size_t count; struct ldb_message *msg; uint32_t id; int ret; int i; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } base_dn = sysdb_domain_dn(tmp_ctx, domain); if (!base_dn) { talloc_zfree(tmp_ctx); return ENOMEM; } ret = ldb_transaction_start(domain->sysdb->ldb); if (ret) { talloc_zfree(tmp_ctx); ret = sysdb_error_to_errno(ret); return ret; } ret = sysdb_search_entry(tmp_ctx, domain->sysdb, base_dn, LDB_SCOPE_BASE, SYSDB_NEXTID_FILTER, attrs_1, &count, &msgs); switch (ret) { case EOK: new_id = get_attr_as_uint32(msgs[0], SYSDB_NEXTID); if (new_id == (uint32_t)(-1)) { DEBUG(SSSDBG_CRIT_FAILURE, "Invalid Next ID in domain %s\n", domain->name); ret = ERANGE; goto done; } if (new_id < domain->id_min) { new_id = domain->id_min; } if ((domain->id_max != 0) && (new_id > domain->id_max)) { DEBUG(SSSDBG_FATAL_FAILURE, "Failed to allocate new id, out of range (%u/%u)\n", new_id, domain->id_max); ret = ERANGE; goto done; } break; case ENOENT: /* looks like the domain is not initialized yet, use min_id */ new_id = domain->id_min; break; default: goto done; } talloc_zfree(msgs); count = 0; /* verify the id is actually really free. * search all entries with id >= new_id and < max_id */ if (domain->id_max) { filter = talloc_asprintf(tmp_ctx, "(|(&(%s>=%u)(%s<=%u))(&(%s>=%u)(%s<=%u)))", SYSDB_UIDNUM, new_id, SYSDB_UIDNUM, domain->id_max, SYSDB_GIDNUM, new_id, SYSDB_GIDNUM, domain->id_max); } else { filter = talloc_asprintf(tmp_ctx, "(|(%s>=%u)(%s>=%u))", SYSDB_UIDNUM, new_id, SYSDB_GIDNUM, new_id); } if (!filter) { DEBUG(SSSDBG_TRACE_FUNC, "Error: Out of memory\n"); ret = ENOMEM; goto done; } ret = sysdb_search_entry(tmp_ctx, domain->sysdb, base_dn, LDB_SCOPE_SUBTREE, filter, attrs_2, &count, &msgs); switch (ret) { /* if anything was found, find the maximum and increment past it */ case EOK: for (i = 0; i < count; i++) { id = get_attr_as_uint32(msgs[i], SYSDB_UIDNUM); if (id != (uint32_t)(-1)) { if (id > new_id) new_id = id; } id = get_attr_as_uint32(msgs[i], SYSDB_GIDNUM); if (id != (uint32_t)(-1)) { if (id > new_id) new_id = id; } } new_id++; /* check again we are not falling out of range */ if ((domain->id_max != 0) && (new_id > domain->id_max)) { DEBUG(SSSDBG_FATAL_FAILURE, "Failed to allocate new id, out of range (%u/%u)\n", new_id, domain->id_max); ret = ERANGE; goto done; } break; case ENOENT: break; default: goto done; } talloc_zfree(msgs); count = 0; /* finally store the new next id */ msg = ldb_msg_new(tmp_ctx); if (!msg) { DEBUG(SSSDBG_TRACE_FUNC, "Error: Out of memory\n"); ret = ENOMEM; goto done; } msg->dn = base_dn; ret = sysdb_replace_ulong(msg, SYSDB_NEXTID, new_id + 1); if (ret) { goto done; } ret = ldb_modify(domain->sysdb->ldb, msg); if (ret != LDB_SUCCESS) { DEBUG(SSSDBG_MINOR_FAILURE, "ldb_modify failed: [%s](%d)[%s]\n", ldb_strerror(ret), ret, ldb_errstring(domain->sysdb->ldb)); } ret = sysdb_error_to_errno(ret); *_id = new_id; done: if (ret == EOK) { ret = ldb_transaction_commit(domain->sysdb->ldb); ret = sysdb_error_to_errno(ret); } else { ldb_transaction_cancel(domain->sysdb->ldb); } if (ret) { DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(tmp_ctx); return ret; } /* =Add-Basic-User-NO-CHECKS============================================== */ int sysdb_add_basic_user(struct sss_domain_info *domain, const char *name, uid_t uid, gid_t gid, const char *gecos, const char *homedir, const char *shell) { struct ldb_message *msg; int ret; TALLOC_CTX *tmp_ctx; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } msg = ldb_msg_new(tmp_ctx); if (!msg) { ret = ENOMEM; goto done; } /* user dn */ msg->dn = sysdb_user_dn(msg, domain, name); if (!msg->dn) { ERROR_OUT(ret, ENOMEM, done); } ret = sysdb_add_string(msg, SYSDB_OBJECTCLASS, SYSDB_USER_CLASS); if (ret) goto done; ret = sysdb_add_string(msg, SYSDB_NAME, name); if (ret) goto done; ret = sysdb_add_ulong(msg, SYSDB_UIDNUM, (unsigned long)uid); if (ret) goto done; ret = sysdb_add_ulong(msg, SYSDB_GIDNUM, (unsigned long)gid); if (ret) goto done; /* We set gecos to be the same as fullname on user creation, * But we will not enforce coherency after that, it's up to * admins to decide if they want to keep it in sync if they change * one of the 2 */ if (gecos && *gecos) { ret = sysdb_add_string(msg, SYSDB_FULLNAME, gecos); if (ret) goto done; ret = sysdb_add_string(msg, SYSDB_GECOS, gecos); if (ret) goto done; } if (homedir && *homedir) { ret = sysdb_add_string(msg, SYSDB_HOMEDIR, homedir); if (ret) goto done; } if (shell && *shell) { ret = sysdb_add_string(msg, SYSDB_SHELL, shell); if (ret) goto done; } /* creation time */ ret = sysdb_add_ulong(msg, SYSDB_CREATE_TIME, (unsigned long)time(NULL)); if (ret) goto done; ret = ldb_add(domain->sysdb->ldb, msg); ret = sysdb_error_to_errno(ret); done: if (ret) { DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(tmp_ctx); return ret; } static errno_t sysdb_remove_ghost_from_group(struct sss_domain_info *dom, struct ldb_message *group, struct ldb_message_element *alias_el, const char *name, const char *orig_dn, const char *userdn) { TALLOC_CTX *tmp_ctx; struct ldb_message *msg; struct ldb_message_element *orig_members; bool add_member = false; errno_t ret = EOK; int i; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOENT; } msg = ldb_msg_new(tmp_ctx); if (!msg) { ERROR_OUT(ret, ENOMEM, done); } msg->dn = group->dn; if (orig_dn == NULL) { /* We have no way of telling which groups this user belongs to. * Add it to all that reference it in the ghost attribute */ add_member = true; } else { add_member = false; orig_members = ldb_msg_find_element(group, SYSDB_ORIG_MEMBER); if (orig_members) { for (i = 0; i < orig_members->num_values; i++) { if (strcmp((const char *) orig_members->values[i].data, orig_dn) == 0) { /* This is a direct member. Add the member attribute */ add_member = true; } } } else { /* Nothing to compare the originalDN with. Let's rely on the * memberof plugin to do the right thing during initgroups.. */ add_member = true; } } if (add_member) { ret = sysdb_add_string(msg, SYSDB_MEMBER, userdn); if (ret) goto done; } ret = sysdb_delete_string(msg, SYSDB_GHOST, name); if (ret) goto done; /* Delete aliases from the ghost attribute as well */ for (i = 0; i < alias_el->num_values; i++) { if (strcmp((const char *)alias_el->values[i].data, name) == 0) { continue; } ret = ldb_msg_add_string(msg, SYSDB_GHOST, (char *) alias_el->values[i].data); if (ret != LDB_SUCCESS) { ERROR_OUT(ret, EINVAL, done); } } ret = sss_ldb_modify_permissive(dom->sysdb->ldb, msg); if (ret != LDB_SUCCESS) { DEBUG(SSSDBG_MINOR_FAILURE, "sss_ldb_modify_permissive failed: [%s](%d)[%s]\n", ldb_strerror(ret), ret, ldb_errstring(dom->sysdb->ldb)); } ret = sysdb_error_to_errno(ret); if (ret != EOK) { goto done; } talloc_zfree(msg); done: talloc_free(tmp_ctx); return ret; } static errno_t sysdb_remove_ghostattr_from_groups(struct sss_domain_info *domain, const char *orig_dn, struct sysdb_attrs *attrs, const char *name) { TALLOC_CTX *tmp_ctx; struct ldb_message **groups; struct ldb_message_element *alias_el; struct ldb_dn *tmpdn; const char *group_attrs[] = {SYSDB_NAME, SYSDB_GHOST, SYSDB_ORIG_MEMBER, NULL}; const char *userdn; char *sanitized_name; char *filter; errno_t ret = EOK; size_t group_count = 0; int i; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOENT; } ret = sss_filter_sanitize(tmp_ctx, name, &sanitized_name); if (ret != EOK) { goto done; } filter = talloc_asprintf(tmp_ctx, "(|(%s=%s)", SYSDB_GHOST, sanitized_name); if (!filter) { ret = ENOMEM; goto done; } ret = sysdb_attrs_get_el(attrs, SYSDB_NAME_ALIAS, &alias_el); if (ret != EOK) { goto done; } for (i = 0; i < alias_el->num_values; i++) { if (strcmp((const char *)alias_el->values[i].data, name) == 0) { continue; } filter = talloc_asprintf_append(filter, "(%s=%s)", SYSDB_GHOST, alias_el->values[i].data); if (filter == NULL) { ret = ENOMEM; goto done; } } filter = talloc_asprintf_append(filter, ")"); if (filter == NULL) { ret = ENOMEM; goto done; } tmpdn = sysdb_user_dn(tmp_ctx, domain, name); if (!tmpdn) { ERROR_OUT(ret, ENOMEM, done); } userdn = ldb_dn_get_linearized(tmpdn); if (!userdn) { ERROR_OUT(ret, EINVAL, done); } /* To cover cross-domain group-membership we must search in all * sub-domains. */ tmpdn = ldb_dn_new(tmp_ctx, domain->sysdb->ldb, SYSDB_BASE); if (!tmpdn) { ret = ENOMEM; goto done; } /* We need to find all groups that contain this object as a ghost user * and replace the ghost user by actual member record in direct parents. * Note that this object can be referred to either by its name or any * of its aliases */ ret = sysdb_search_entry(tmp_ctx, domain->sysdb, tmpdn, LDB_SCOPE_SUBTREE, filter, group_attrs, &group_count, &groups); if (ret != EOK && ret != ENOENT) { goto done; } for (i = 0; i < group_count; i++) { sysdb_remove_ghost_from_group(domain, groups[i], alias_el, name, orig_dn, userdn); } ret = EOK; done: talloc_free(tmp_ctx); return ret; } /* =Add-User-Function===================================================== */ int sysdb_add_user(struct sss_domain_info *domain, const char *name, uid_t uid, gid_t gid, const char *gecos, const char *homedir, const char *shell, const char *orig_dn, struct sysdb_attrs *attrs, int cache_timeout, time_t now) { TALLOC_CTX *tmp_ctx; struct ldb_message *msg; struct sysdb_attrs *id_attrs; uint32_t id; int ret; if (domain->mpg) { if (gid != 0) { DEBUG(SSSDBG_FATAL_FAILURE, "Cannot add user with arbitrary GID in MPG domain!\n"); return EINVAL; } gid = uid; } if (domain->id_max != 0 && uid != 0 && (uid < domain->id_min || uid > domain->id_max)) { DEBUG(SSSDBG_OP_FAILURE, "Supplied uid [%"SPRIuid"] is not in the allowed range " "[%d-%d].\n", uid, domain->id_min, domain->id_max); return ERANGE; } if (domain->id_max != 0 && gid != 0 && (gid < domain->id_min || gid > domain->id_max)) { DEBUG(SSSDBG_OP_FAILURE, "Supplied gid [%"SPRIgid"] is not in the allowed range " "[%d-%d].\n", gid, domain->id_min, domain->id_max); return ERANGE; } tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } ret = ldb_transaction_start(domain->sysdb->ldb); if (ret) { ret = sysdb_error_to_errno(ret); talloc_free(tmp_ctx); return ret; } if (domain->mpg) { /* In MPG domains you can't have groups with the same name as users, * search if a group with the same name exists. * Don't worry about users, if we try to add a user with the same * name the operation will fail */ ret = sysdb_search_group_by_name(tmp_ctx, domain, name, NULL, &msg); if (ret != ENOENT) { if (ret == EOK) ret = EEXIST; goto done; } } /* check no other user with the same uid exist */ if (uid != 0) { ret = sysdb_search_user_by_uid(tmp_ctx, domain, uid, NULL, &msg); if (ret != ENOENT) { if (ret == EOK) ret = EEXIST; goto done; } } /* try to add the user */ ret = sysdb_add_basic_user(domain, name, uid, gid, gecos, homedir, shell); if (ret) goto done; ret = sysdb_create_ts_usr(domain, name, cache_timeout, now); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Cannot create user timestamp entry\n"); /* Not fatal */ } if (uid == 0) { ret = sysdb_get_new_id(domain, &id); if (ret) goto done; id_attrs = sysdb_new_attrs(tmp_ctx); if (!id_attrs) { ret = ENOMEM; goto done; } ret = sysdb_attrs_add_uint32(id_attrs, SYSDB_UIDNUM, id); if (ret) goto done; if (domain->mpg) { ret = sysdb_attrs_add_uint32(id_attrs, SYSDB_GIDNUM, id); if (ret) goto done; } ret = sysdb_set_user_attr(domain, name, id_attrs, SYSDB_MOD_REP); /* continue on success, to commit additional attrs */ if (ret) goto done; } if (!attrs) { attrs = sysdb_new_attrs(tmp_ctx); if (!attrs) { ret = ENOMEM; goto done; } } if (!now) { now = time(NULL); } ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_UPDATE, now); if (ret) goto done; ret = sysdb_attrs_add_time_t(attrs, SYSDB_CACHE_EXPIRE, ((cache_timeout) ? (now + cache_timeout) : 0)); if (ret) goto done; ret = sysdb_set_user_attr(domain, name, attrs, SYSDB_MOD_REP); if (ret) goto done; if (domain->enumerate == false) { /* If we're not enumerating, previous getgr{nam,gid} calls might * have stored ghost users into the cache, so we need to link them * with the newly-created user entry */ ret = sysdb_remove_ghostattr_from_groups(domain, orig_dn, attrs, name); if (ret) goto done; } ret = EOK; done: if (ret == EOK) { ret = ldb_transaction_commit(domain->sysdb->ldb); ret = sysdb_error_to_errno(ret); } else { DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, strerror(ret)); ldb_transaction_cancel(domain->sysdb->ldb); } talloc_zfree(tmp_ctx); return ret; } /* =Add-Basic-Group-NO-CHECKS============================================= */ int sysdb_add_basic_group(struct sss_domain_info *domain, const char *name, gid_t gid) { struct ldb_message *msg; int ret; TALLOC_CTX *tmp_ctx; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } msg = ldb_msg_new(tmp_ctx); if (!msg) { ret = ENOMEM; goto done; } /* group dn */ msg->dn = sysdb_group_dn(msg, domain, name); if (!msg->dn) { ERROR_OUT(ret, ENOMEM, done); } ret = sysdb_add_string(msg, SYSDB_OBJECTCLASS, SYSDB_GROUP_CLASS); if (ret) goto done; ret = sysdb_add_string(msg, SYSDB_NAME, name); if (ret) goto done; ret = sysdb_add_ulong(msg, SYSDB_GIDNUM, (unsigned long)gid); if (ret) goto done; /* creation time */ ret = sysdb_add_ulong(msg, SYSDB_CREATE_TIME, (unsigned long)time(NULL)); if (ret) goto done; ret = ldb_add(domain->sysdb->ldb, msg); ret = sysdb_error_to_errno(ret); done: if (ret) { DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(tmp_ctx); return ret; } /* =Add-Group-Function==================================================== */ int sysdb_add_group(struct sss_domain_info *domain, const char *name, gid_t gid, struct sysdb_attrs *attrs, int cache_timeout, time_t now) { TALLOC_CTX *tmp_ctx; struct ldb_message *msg; uint32_t id; int ret; bool posix; if (domain->id_max != 0 && gid != 0 && (gid < domain->id_min || gid > domain->id_max)) { DEBUG(SSSDBG_OP_FAILURE, "Supplied gid [%"SPRIgid"] is not in the allowed range " "[%d-%d].\n", gid, domain->id_min, domain->id_max); return ERANGE; } tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } ret = ldb_transaction_start(domain->sysdb->ldb); if (ret) { ret = sysdb_error_to_errno(ret); talloc_free(tmp_ctx); return ret; } if (domain->mpg) { /* In MPG domains you can't have groups with the same name as users, * search if a group with the same name exists. * Don't worry about users, if we try to add a user with the same * name the operation will fail */ ret = sysdb_search_user_by_name(tmp_ctx, domain, name, NULL, &msg); if (ret != ENOENT) { if (ret == EOK) { DEBUG(SSSDBG_TRACE_LIBS, "MPG domain contains a user " "with the same name - %s.\n", name); ret = EEXIST; } else { DEBUG(SSSDBG_TRACE_LIBS, "sysdb_search_user_by_name failed for user %s.\n", name); } goto done; } } /* check no other groups with the same gid exist */ if (gid != 0) { ret = sysdb_search_group_by_gid(tmp_ctx, domain, gid, NULL, &msg); if (ret != ENOENT) { if (ret == EOK) { DEBUG(SSSDBG_TRACE_LIBS, "Group with the same gid exists: [%"SPRIgid"].\n", gid); ret = EEXIST; } else { DEBUG(SSSDBG_TRACE_LIBS, "sysdb_search_group_by_gid failed for gid: " "[%"SPRIgid"].\n", gid); } goto done; } } /* try to add the group */ ret = sysdb_add_basic_group(domain, name, gid); if (ret) { DEBUG(SSSDBG_TRACE_LIBS, "sysdb_add_basic_group failed for: %s with gid: " "[%"SPRIgid"].\n", name, gid); goto done; } ret = sysdb_create_ts_grp(domain, name, cache_timeout, now); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Cannot set timestamp cache attributes for a group\n"); /* Not fatal */ } if (!attrs) { attrs = sysdb_new_attrs(tmp_ctx); if (!attrs) { DEBUG(SSSDBG_TRACE_LIBS, "sysdb_new_attrs failed.\n"); ret = ENOMEM; goto done; } } ret = sysdb_attrs_get_bool(attrs, SYSDB_POSIX, &posix); if (ret == ENOENT) { posix = true; ret = sysdb_attrs_add_bool(attrs, SYSDB_POSIX, true); if (ret) { DEBUG(SSSDBG_TRACE_LIBS, "Failed to add posix attribute.\n"); goto done; } } else if (ret != EOK) { DEBUG(SSSDBG_TRACE_LIBS, "Failed to get posix attribute.\n"); goto done; } if (posix && gid == 0) { ret = sysdb_get_new_id(domain, &id); if (ret) { DEBUG(SSSDBG_TRACE_LIBS, "sysdb_get_new_id failed.\n"); goto done; } ret = sysdb_attrs_add_uint32(attrs, SYSDB_GIDNUM, id); if (ret) { DEBUG(SSSDBG_TRACE_LIBS, "Failed to add new gid.\n"); goto done; } } if (!now) { now = time(NULL); } ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_UPDATE, now); if (ret) { DEBUG(SSSDBG_TRACE_LIBS, "Failed to add sysdb-last-update.\n"); goto done; } ret = sysdb_attrs_add_time_t(attrs, SYSDB_CACHE_EXPIRE, ((cache_timeout) ? (now + cache_timeout) : 0)); if (ret) { DEBUG(SSSDBG_TRACE_LIBS, "Failed to add sysdb-cache-expire.\n"); goto done; } ret = sysdb_set_group_attr(domain, name, attrs, SYSDB_MOD_REP); if (ret) { DEBUG(SSSDBG_TRACE_LIBS, "sysdb_set_group_attr failed.\n"); goto done; } done: if (ret == EOK) { ret = ldb_transaction_commit(domain->sysdb->ldb); ret = sysdb_error_to_errno(ret); } else { DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, strerror(ret)); ldb_transaction_cancel(domain->sysdb->ldb); } talloc_zfree(tmp_ctx); return ret; } int sysdb_add_incomplete_group(struct sss_domain_info *domain, const char *name, gid_t gid, const char *original_dn, const char *sid_str, const char *uuid, bool posix, time_t now) { TALLOC_CTX *tmp_ctx; int ret; struct sysdb_attrs *attrs; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } /* try to add the group */ ret = sysdb_add_basic_group(domain, name, gid); if (ret) goto done; if (!now) { now = time(NULL); } ret = sysdb_create_ts_grp(domain, name, now-1, now); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Cannot set timestamp cache attributes for a group\n"); /* Not fatal */ } attrs = sysdb_new_attrs(tmp_ctx); if (!attrs) { ret = ENOMEM; goto done; } ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_UPDATE, now); if (ret) goto done; ret = sysdb_attrs_add_time_t(attrs, SYSDB_CACHE_EXPIRE, now-1); if (ret) goto done; ret = sysdb_attrs_add_bool(attrs, SYSDB_POSIX, posix); if (ret) goto done; if (original_dn) { ret = sysdb_attrs_add_string(attrs, SYSDB_ORIG_DN, original_dn); if (ret) goto done; } if (sid_str) { ret = sysdb_attrs_add_string(attrs, SYSDB_SID_STR, sid_str); if (ret) goto done; } if (uuid) { ret = sysdb_attrs_add_string(attrs, SYSDB_UUID, uuid); if (ret) goto done; } ret = sysdb_set_group_attr(domain, name, attrs, SYSDB_MOD_REP); done: if (ret != EOK) { DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(tmp_ctx); return ret; } /* =Add-Or-Remove-Group-Memeber=========================================== */ /* mod_op must be either SYSDB_MOD_ADD or SYSDB_MOD_DEL */ int sysdb_mod_group_member(struct sss_domain_info *domain, struct ldb_dn *member_dn, struct ldb_dn *group_dn, int mod_op) { struct ldb_message *msg; const char *dn; int ret; msg = ldb_msg_new(NULL); if (!msg) { ERROR_OUT(ret, ENOMEM, fail); } msg->dn = group_dn; ret = ldb_msg_add_empty(msg, SYSDB_MEMBER, mod_op, NULL); if (ret != LDB_SUCCESS) { ERROR_OUT(ret, ENOMEM, fail); } dn = ldb_dn_get_linearized(member_dn); if (!dn) { ERROR_OUT(ret, EINVAL, fail); } ret = ldb_msg_add_string(msg, SYSDB_MEMBER, dn); if (ret != LDB_SUCCESS) { ERROR_OUT(ret, EINVAL, fail); } ret = ldb_modify(domain->sysdb->ldb, msg); if (ret != LDB_SUCCESS) { DEBUG(SSSDBG_MINOR_FAILURE, "ldb_modify failed: [%s](%d)[%s]\n", ldb_strerror(ret), ret, ldb_errstring(domain->sysdb->ldb)); } ret = sysdb_error_to_errno(ret); fail: if (ret) { DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(msg); return ret; } errno_t sysdb_refresh_group_memberships(struct sss_domain_info *dom, struct ldb_dn *dn) { int ret; TALLOC_CTX *tmp_ctx; const char *attrs[] = { SYSDB_MEMBEROF, NULL }; size_t msgs_count; struct ldb_message **msgs; struct ldb_message_element *groups; size_t c; struct ldb_dn *group_dn; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); return ENOMEM; } ret = sysdb_search_entry(tmp_ctx, dom->sysdb, dn, LDB_SCOPE_BASE, NULL, attrs, &msgs_count, &msgs); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_entry failed for [%s].\n", ldb_dn_get_linearized(dn)); goto done; } if (msgs_count != 1) { DEBUG(SSSDBG_OP_FAILURE, "Expected 1 result for base search, got [%d].\n", msgs_count); ret = EINVAL; goto done; } groups = ldb_msg_find_element(msgs[0], SYSDB_MEMBEROF); if (groups == NULL) { DEBUG(SSSDBG_TRACE_ALL, "[%s] is not member of any group.\n", ldb_dn_get_linearized(dn)); ret = EOK; goto done; } for (c = 0; c < groups->num_values; c++) { group_dn = ldb_dn_from_ldb_val(tmp_ctx, dom->sysdb->ldb, &groups->values[c]); if (group_dn == NULL) { DEBUG(SSSDBG_OP_FAILURE, "ldb_dn_from_ldb_val failed.\n"); ret = ENOMEM; goto done; } ret = sysdb_mod_group_member(dom, dn, group_dn, SYSDB_MOD_DEL); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_mod_group_member SYSDB_MOD_DEL failed.\n"); goto done; } ret = sysdb_mod_group_member(dom, dn, group_dn, SYSDB_MOD_ADD); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_mod_group_member SYSDB_MOD_ADD failed.\n"); goto done; } } ret = EOK; done: talloc_free(tmp_ctx); return ret; } /* =Add-Basic-Netgroup-NO-CHECKS============================================= */ int sysdb_add_basic_netgroup(struct sss_domain_info *domain, const char *name, const char *description) { struct ldb_message *msg; int ret; msg = ldb_msg_new(NULL); if (!msg) { return ENOMEM; } /* netgroup dn */ msg->dn = sysdb_netgroup_dn(msg, domain, name); if (!msg->dn) { ERROR_OUT(ret, ENOMEM, done); } ret = sysdb_add_string(msg, SYSDB_OBJECTCLASS, SYSDB_NETGROUP_CLASS); if (ret) goto done; ret = sysdb_add_string(msg, SYSDB_NAME, name); if (ret) goto done; if (description && *description) { ret = sysdb_add_string(msg, SYSDB_DESCRIPTION, description); if (ret) goto done; } /* creation time */ ret = sysdb_add_ulong(msg, SYSDB_CREATE_TIME, (unsigned long) time(NULL)); if (ret) goto done; ret = ldb_add(domain->sysdb->ldb, msg); ret = sysdb_error_to_errno(ret); done: if (ret) { DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(msg); return ret; } /* =Add-Netgroup-Function==================================================== */ int sysdb_add_netgroup(struct sss_domain_info *domain, const char *name, const char *description, struct sysdb_attrs *attrs, char **missing, int cache_timeout, time_t now) { TALLOC_CTX *tmp_ctx; int ret; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } ret = ldb_transaction_start(domain->sysdb->ldb); if (ret) { ret = sysdb_error_to_errno(ret); talloc_free(tmp_ctx); return ret; } /* try to add the netgroup */ ret = sysdb_add_basic_netgroup(domain, name, description); if (ret && ret != EEXIST) goto done; if (!attrs) { attrs = sysdb_new_attrs(tmp_ctx); if (!attrs) { ret = ENOMEM; goto done; } } if (!now) { now = time(NULL); } ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_UPDATE, now); if (ret) goto done; ret = sysdb_attrs_add_time_t(attrs, SYSDB_CACHE_EXPIRE, ((cache_timeout) ? (now + cache_timeout) : 0)); if (ret) goto done; ret = sysdb_set_netgroup_attr(domain, name, attrs, SYSDB_MOD_REP); if (missing) { ret = sysdb_remove_attrs(domain, name, SYSDB_MEMBER_NETGROUP, missing); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Could not remove missing attributes\n"); } } done: if (ret == EOK) { ret = ldb_transaction_commit(domain->sysdb->ldb); ret = sysdb_error_to_errno(ret); } if (ret != EOK) { DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, strerror(ret)); ldb_transaction_cancel(domain->sysdb->ldb); } talloc_zfree(tmp_ctx); return ret; } /* =Store-Users-(Native/Legacy)-(replaces-existing-data)================== */ static errno_t sysdb_store_new_user(struct sss_domain_info *domain, const char *name, uid_t uid, gid_t gid, const char *gecos, const char *homedir, const char *shell, const char *orig_dn, struct sysdb_attrs *attrs, uint64_t cache_timeout, time_t now); static errno_t sysdb_store_user_attrs(struct sss_domain_info *domain, const char *name, uid_t uid, gid_t gid, const char *gecos, const char *homedir, const char *shell, const char *orig_dn, struct sysdb_attrs *attrs, char **remove_attrs, uint64_t cache_timeout, time_t now); /* if one of the basic attributes is empty ("") as opposed to NULL, * this will just remove it */ int sysdb_store_user(struct sss_domain_info *domain, const char *name, const char *pwd, uid_t uid, gid_t gid, const char *gecos, const char *homedir, const char *shell, const char *orig_dn, struct sysdb_attrs *attrs, char **remove_attrs, uint64_t cache_timeout, time_t now) { TALLOC_CTX *tmp_ctx; struct ldb_message *msg; int ret; errno_t sret = EOK; bool in_transaction = false; ret = sysdb_check_and_update_ts_usr(domain, name, attrs, cache_timeout, now); if (ret == EOK) { DEBUG(SSSDBG_TRACE_LIBS, "The user record of %s did not change, only updated " "the timestamp cache\n", name); return EOK; } tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } if (!attrs) { attrs = sysdb_new_attrs(tmp_ctx); if (!attrs) { ret = ENOMEM; goto done; } } if (pwd && (domain->legacy_passwords || !*pwd)) { ret = sysdb_attrs_add_string(attrs, SYSDB_PWD, pwd); if (ret) goto done; } ret = sysdb_transaction_start(domain->sysdb); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); goto done; } in_transaction = true; ret = sysdb_search_user_by_name(tmp_ctx, domain, name, NULL, &msg); if (ret && ret != ENOENT) { goto done; } if (ret == ENOENT) { DEBUG(SSSDBG_TRACE_LIBS, "User %s does not exist.\n", name); } /* get transaction timestamp */ if (!now) { now = time(NULL); } if (ret == ENOENT) { /* the user doesn't exist, turn into adding a user */ ret = sysdb_store_new_user(domain, name, uid, gid, gecos, homedir, shell, orig_dn, attrs, cache_timeout, now); } else { /* the user exists, let's just replace attributes when set */ ret = sysdb_store_user_attrs(domain, name, uid, gid, gecos, homedir, shell, orig_dn, attrs, remove_attrs, cache_timeout, now); } if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cache update failed: %d\n", ret); goto done; } sret = sysdb_transaction_commit(domain->sysdb); if (sret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); ret = EIO; goto done; } in_transaction = false; done: if (in_transaction) { sret = sysdb_transaction_cancel(domain->sysdb); if (sret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); } } if (ret) { DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(tmp_ctx); return ret; } static errno_t sysdb_store_new_user(struct sss_domain_info *domain, const char *name, uid_t uid, gid_t gid, const char *gecos, const char *homedir, const char *shell, const char *orig_dn, struct sysdb_attrs *attrs, uint64_t cache_timeout, time_t now) { errno_t ret; ret = sysdb_add_user(domain, name, uid, gid, gecos, homedir, shell, orig_dn, attrs, cache_timeout, now); if (ret == EEXIST) { /* This may be a user rename. If there is a user with the * same UID, remove it and try to add the basic user again */ ret = sysdb_delete_user(domain, NULL, uid); if (ret == ENOENT) { /* Not found by UID, return the original EEXIST, * this may be a conflict in MPG domain or something * else */ return EEXIST; } else if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_delete_user failed.\n"); return ret; } DEBUG(SSSDBG_TRACE_FUNC, "A user with the same UID [%llu] was removed from the " "cache\n", (unsigned long long) uid); ret = sysdb_add_user(domain, name, uid, gid, gecos, homedir, shell, orig_dn, attrs, cache_timeout, now); if (ret) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_add_user failed (while renaming user) for: " "%s [%"SPRIgid"].\n", name, gid); return ret; } } return EOK; } static errno_t sysdb_store_user_attrs(struct sss_domain_info *domain, const char *name, uid_t uid, gid_t gid, const char *gecos, const char *homedir, const char *shell, const char *orig_dn, struct sysdb_attrs *attrs, char **remove_attrs, uint64_t cache_timeout, time_t now) { errno_t ret; if (uid) { ret = sysdb_attrs_add_uint32(attrs, SYSDB_UIDNUM, uid); if (ret) return ret; } if (gid) { ret = sysdb_attrs_add_uint32(attrs, SYSDB_GIDNUM, gid); if (ret) return ret; } if (uid && !gid && domain->mpg) { ret = sysdb_attrs_add_uint32(attrs, SYSDB_GIDNUM, uid); if (ret) return ret; } if (gecos) { ret = sysdb_attrs_add_string(attrs, SYSDB_GECOS, gecos); if (ret) return ret; } if (homedir) { ret = sysdb_attrs_add_string(attrs, SYSDB_HOMEDIR, homedir); if (ret) return ret; } if (shell) { ret = sysdb_attrs_add_string(attrs, SYSDB_SHELL, shell); if (ret) return ret; } ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_UPDATE, now); if (ret) return ret; ret = sysdb_attrs_add_time_t(attrs, SYSDB_CACHE_EXPIRE, ((cache_timeout) ? (now + cache_timeout) : 0)); if (ret) return ret; ret = sysdb_set_user_attr(domain, name, attrs, SYSDB_MOD_REP); if (ret) return ret; if (remove_attrs) { ret = sysdb_remove_attrs(domain, name, SYSDB_MEMBER_USER, remove_attrs); if (ret != EOK) { DEBUG(SSSDBG_CONF_SETTINGS, "Could not remove missing attributes\n"); } } return EOK; } /* =Store-Group-(Native/Legacy)-(replaces-existing-data)================== */ /* this function does not check that all user members are actually present */ static errno_t sysdb_store_new_group(struct sss_domain_info *domain, const char *name, gid_t gid, struct sysdb_attrs *attrs, uint64_t cache_timeout, time_t now); static errno_t sysdb_store_group_attrs(struct sss_domain_info *domain, const char *name, gid_t gid, struct ldb_message *cached_group, struct sysdb_attrs *attrs, uint64_t cache_timeout, time_t now); int sysdb_store_group(struct sss_domain_info *domain, const char *name, gid_t gid, struct sysdb_attrs *attrs, uint64_t cache_timeout, time_t now) { TALLOC_CTX *tmp_ctx; static const char *src_attrs[] = { "*", NULL }; struct ldb_message *msg; bool new_group = false; int ret; errno_t sret = EOK; bool in_transaction = false; ret = sysdb_check_and_update_ts_grp(domain, name, attrs, cache_timeout, now); if (ret == EOK) { DEBUG(SSSDBG_TRACE_LIBS, "The group record of %s did not change, only updated " "the timestamp cache\n", name); return EOK; } tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } ret = sysdb_transaction_start(domain->sysdb); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); goto done; } in_transaction = true; ret = sysdb_search_group_by_name(tmp_ctx, domain, name, src_attrs, &msg); if (ret && ret != ENOENT) { DEBUG(SSSDBG_MINOR_FAILURE, "sysdb_search_group_by_name failed for %s with: [%d][%s].\n", name, ret, strerror(ret)); goto done; } if (ret == ENOENT) { DEBUG(SSSDBG_TRACE_LIBS, "Group %s does not exist.\n", name); new_group = true; } if (!attrs) { attrs = sysdb_new_attrs(tmp_ctx); if (!attrs) { ret = ENOMEM; goto done; } } /* get transaction timestamp */ if (!now) { now = time(NULL); } if (new_group) { ret = sysdb_store_new_group(domain, name, gid, attrs, cache_timeout, now); } else { ret = sysdb_store_group_attrs(domain, name, gid, msg, attrs, cache_timeout, now); } if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cache update failed: %d\n", ret); goto done; } sret = sysdb_transaction_commit(domain->sysdb); if (sret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); ret = EIO; goto done; } in_transaction = false; done: if (in_transaction) { sret = sysdb_transaction_cancel(domain->sysdb); if (sret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); } } if (ret) { DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(tmp_ctx); return ret; } static errno_t sysdb_store_new_group(struct sss_domain_info *domain, const char *name, gid_t gid, struct sysdb_attrs *attrs, uint64_t cache_timeout, time_t now) { errno_t ret; /* group doesn't exist, turn into adding a group */ ret = sysdb_add_group(domain, name, gid, attrs, cache_timeout, now); if (ret == EEXIST) { /* This may be a group rename. If there is a group with the * same GID, remove it and try to add the basic group again */ DEBUG(SSSDBG_TRACE_LIBS, "sysdb_add_group failed: [EEXIST].\n"); ret = sysdb_delete_group(domain, NULL, gid); if (ret == ENOENT) { /* Not found by GID, return the original EEXIST, * this may be a conflict in MPG domain or something * else */ DEBUG(SSSDBG_OP_FAILURE, "sysdb_delete_group failed (while renaming group). Not " "found by gid: [%"SPRIgid"].\n", gid); return EEXIST; } else if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_add_group failed.\n"); return ret; } DEBUG(SSSDBG_TRACE_FUNC, "A group with the same GID [%"SPRIgid"] was removed from " "the cache\n", gid); ret = sysdb_add_group(domain, name, gid, attrs, cache_timeout, now); if (ret) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_add_group failed (while renaming group) for: " "%s [%"SPRIgid"].\n", name, gid); return ret; } } return EOK; } static errno_t sysdb_store_group_attrs(struct sss_domain_info *domain, const char *name, gid_t gid, struct ldb_message *cached_group, struct sysdb_attrs *attrs, uint64_t cache_timeout, time_t now) { errno_t ret; /* the group exists, let's just replace attributes when set */ if (gid) { ret = sysdb_attrs_add_uint32(attrs, SYSDB_GIDNUM, gid); if (ret) { DEBUG(SSSDBG_TRACE_LIBS, "Failed to add GID.\n"); return ret; } } ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_UPDATE, now); if (ret) { DEBUG(SSSDBG_TRACE_LIBS, "Failed to add sysdb-last-update.\n"); return ret; } ret = sysdb_attrs_add_time_t(attrs, SYSDB_CACHE_EXPIRE, ((cache_timeout) ? (now + cache_timeout) : 0)); if (ret) { DEBUG(SSSDBG_TRACE_LIBS, "Failed to add sysdb-cache-expire.\n"); return ret; } ret = sysdb_set_group_attr(domain, name, attrs, SYSDB_MOD_REP); if (ret) { DEBUG(SSSDBG_TRACE_LIBS, "sysdb_set_group_attr failed.\n"); return ret; } return EOK; } /* =Add-User-to-Group(Native/Legacy)====================================== */ static int sysdb_group_membership_mod(struct sss_domain_info *domain, const char *group, const char *member, enum sysdb_member_type type, int modify_op, bool is_dn) { struct ldb_dn *group_dn; struct ldb_dn *member_dn; char *member_domname; struct sss_domain_info *member_dom; int ret; TALLOC_CTX *tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } ret = sss_parse_internal_fqname(tmp_ctx, member, NULL, &member_domname); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to parser internal fqname '%s' [%d]: %s\n", member, ret, sss_strerror(ret)); goto done; } member_dom = find_domain_by_name(get_domains_head(domain), member_domname, false); if (member_dom == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Domain [%s] was not found\n", member_domname); ret = EINVAL; goto done; } if (type == SYSDB_MEMBER_USER) { member_dn = sysdb_user_dn(tmp_ctx, member_dom, member); } else if (type == SYSDB_MEMBER_GROUP) { member_dn = sysdb_group_dn(tmp_ctx, member_dom, member); } else { ret = EINVAL; goto done; } if (!member_dn) { ret = ENOMEM; goto done; } if (!is_dn) { group_dn = sysdb_group_dn(tmp_ctx, domain, group); } else { group_dn = ldb_dn_new(tmp_ctx, domain->sysdb->ldb, group); } if (!group_dn) { ret = ENOMEM; goto done; } ret = sysdb_mod_group_member(domain, member_dn, group_dn, modify_op); done: talloc_free(tmp_ctx); return ret; } int sysdb_add_group_member(struct sss_domain_info *domain, const char *group, const char *member, enum sysdb_member_type type, bool is_dn) { return sysdb_group_membership_mod(domain, group, member, type, SYSDB_MOD_ADD, is_dn); } /* =Remove-member-from-Group(Native/Legacy)=============================== */ int sysdb_remove_group_member(struct sss_domain_info *domain, const char *group, const char *member, enum sysdb_member_type type, bool is_dn) { return sysdb_group_membership_mod(domain, group, member, type, SYSDB_MOD_DEL, is_dn); } /* =Password-Caching====================================================== */ int sysdb_cache_password_ex(struct sss_domain_info *domain, const char *username, const char *password, enum sss_authtok_type authtok_type, size_t second_factor_len) { TALLOC_CTX *tmp_ctx; struct sysdb_attrs *attrs; char *hash = NULL; char *salt; int ret; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } ret = s3crypt_gen_salt(tmp_ctx, &salt); if (ret) { DEBUG(SSSDBG_CONF_SETTINGS, "Failed to generate random salt.\n"); goto fail; } ret = s3crypt_sha512(tmp_ctx, password, salt, &hash); if (ret) { DEBUG(SSSDBG_CONF_SETTINGS, "Failed to create password hash.\n"); goto fail; } attrs = sysdb_new_attrs(tmp_ctx); if (!attrs) { ERROR_OUT(ret, ENOMEM, fail); } ret = sysdb_attrs_add_string(attrs, SYSDB_CACHEDPWD, hash); if (ret) goto fail; ret = sysdb_attrs_add_long(attrs, SYSDB_CACHEDPWD_TYPE, authtok_type); if (ret) goto fail; if (authtok_type == SSS_AUTHTOK_TYPE_2FA && second_factor_len > 0) { ret = sysdb_attrs_add_long(attrs, SYSDB_CACHEDPWD_FA2_LEN, second_factor_len); if (ret) goto fail; } /* FIXME: should we use a different attribute for chache passwords ?? */ ret = sysdb_attrs_add_long(attrs, "lastCachedPasswordChange", (long)time(NULL)); if (ret) goto fail; ret = sysdb_attrs_add_uint32(attrs, SYSDB_FAILED_LOGIN_ATTEMPTS, 0U); if (ret) goto fail; ret = sysdb_set_user_attr(domain, username, attrs, SYSDB_MOD_REP); if (ret) { goto fail; } talloc_zfree(tmp_ctx); return EOK; fail: if (ret) { DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(tmp_ctx); return ret; } int sysdb_cache_password(struct sss_domain_info *domain, const char *username, const char *password) { return sysdb_cache_password_ex(domain, username, password, SSS_AUTHTOK_TYPE_PASSWORD, 0); } /* =Custom Search================== */ int sysdb_search_custom(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *filter, const char *subtree_name, const char **attrs, size_t *msgs_count, struct ldb_message ***msgs) { TALLOC_CTX *tmp_ctx; struct ldb_dn *basedn = NULL; int ret; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { ret = ENOMEM; goto done; } if (filter == NULL || subtree_name == NULL) { ret = EINVAL; goto done; } basedn = sysdb_custom_subtree_dn(tmp_ctx, domain, subtree_name); if (basedn == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_custom_subtree_dn failed.\n"); ret = ENOMEM; goto done; } if (!ldb_dn_validate(basedn)) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create DN.\n"); ret = EINVAL; goto done; } ret = sysdb_search_entry(mem_ctx, domain->sysdb, basedn, LDB_SCOPE_SUBTREE, filter, attrs, msgs_count, msgs); done: talloc_free(tmp_ctx); return ret; } int sysdb_search_custom_by_name(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *object_name, const char *subtree_name, const char **attrs, size_t *_count, struct ldb_message ***_msgs) { TALLOC_CTX *tmp_ctx; struct ldb_dn *basedn; struct ldb_message **msgs; size_t count; int ret; if (object_name == NULL || subtree_name == NULL) { return EINVAL; } tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } basedn = sysdb_custom_dn(tmp_ctx, domain, object_name, subtree_name); if (basedn == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_custom_dn failed.\n"); ret = ENOMEM; goto done; } if (!ldb_dn_validate(basedn)) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create DN.\n"); ret = EINVAL; goto done; } ret = sysdb_search_entry(tmp_ctx, domain->sysdb, basedn, LDB_SCOPE_BASE, NULL, attrs, &count, &msgs); if (ret) { goto done; } if (count > 1) { DEBUG(SSSDBG_CRIT_FAILURE, "More than one result found.\n"); ret = EFAULT; goto done; } *_count = count; *_msgs = talloc_move(mem_ctx, &msgs); done: talloc_zfree(tmp_ctx); return ret; } /* =Custom Store (replaces-existing-data)================== */ int sysdb_store_custom(struct sss_domain_info *domain, const char *object_name, const char *subtree_name, struct sysdb_attrs *attrs) { TALLOC_CTX *tmp_ctx; const char *search_attrs[] = { "*", NULL }; size_t resp_count = 0; struct ldb_message **resp; struct ldb_message *msg; struct ldb_message_element *el; bool add_object = false; int ret; int i; if (object_name == NULL || subtree_name == NULL) { return EINVAL; } ret = ldb_transaction_start(domain->sysdb->ldb); if (ret) { return sysdb_error_to_errno(ret); } tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { ret = ENOMEM; goto done; } ret = sysdb_search_custom_by_name(tmp_ctx, domain, object_name, subtree_name, search_attrs, &resp_count, &resp); if (ret != EOK && ret != ENOENT) { goto done; } if (ret == ENOENT) { add_object = true; } msg = ldb_msg_new(tmp_ctx); if (msg == NULL) { ret = ENOMEM; goto done; } msg->dn = sysdb_custom_dn(tmp_ctx, domain, object_name, subtree_name); if (!msg->dn) { DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_custom_dn failed.\n"); ret = ENOMEM; goto done; } msg->elements = talloc_array(msg, struct ldb_message_element, attrs->num); if (!msg->elements) { ret = ENOMEM; goto done; } for (i = 0; i < attrs->num; i++) { msg->elements[i] = attrs->a[i]; if (add_object) { msg->elements[i].flags = LDB_FLAG_MOD_ADD; } else { el = ldb_msg_find_element(resp[0], attrs->a[i].name); if (el == NULL) { msg->elements[i].flags = LDB_FLAG_MOD_ADD; } else { msg->elements[i].flags = LDB_FLAG_MOD_REPLACE; } } } msg->num_elements = attrs->num; if (add_object) { ret = ldb_add(domain->sysdb->ldb, msg); } else { ret = ldb_modify(domain->sysdb->ldb, msg); } if (ret != LDB_SUCCESS) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to store custom entry: %s(%d)[%s]\n", ldb_strerror(ret), ret, ldb_errstring(domain->sysdb->ldb)); ret = sysdb_error_to_errno(ret); } done: if (ret) { DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, strerror(ret)); ldb_transaction_cancel(domain->sysdb->ldb); } else { ret = ldb_transaction_commit(domain->sysdb->ldb); ret = sysdb_error_to_errno(ret); } talloc_zfree(tmp_ctx); return ret; } /* = Custom Delete======================================= */ int sysdb_delete_custom(struct sss_domain_info *domain, const char *object_name, const char *subtree_name) { TALLOC_CTX *tmp_ctx; struct ldb_dn *dn; int ret; if (object_name == NULL || subtree_name == NULL) { return EINVAL; } tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } dn = sysdb_custom_dn(tmp_ctx, domain, object_name, subtree_name); if (dn == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_custom_dn failed.\n"); ret = ENOMEM; goto done; } ret = ldb_delete(domain->sysdb->ldb, dn); switch (ret) { case LDB_SUCCESS: case LDB_ERR_NO_SUCH_OBJECT: ret = EOK; break; default: DEBUG(SSSDBG_CRIT_FAILURE, "LDB Error: %s(%d)\nError Message: [%s]\n", ldb_strerror(ret), ret, ldb_errstring(domain->sysdb->ldb)); ret = sysdb_error_to_errno(ret); break; } done: talloc_zfree(tmp_ctx); return ret; } /* = ASQ search request ======================================== */ int sysdb_asq_search(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, struct ldb_dn *base_dn, const char *expression, const char *asq_attribute, const char **attrs, size_t *msgs_count, struct ldb_message ***msgs) { TALLOC_CTX *tmp_ctx; struct ldb_request *ldb_req; struct ldb_control **ctrl; struct ldb_asq_control *asq_control; struct ldb_result *res; int ret; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } ctrl = talloc_array(tmp_ctx, struct ldb_control *, 2); if (ctrl == NULL) { ret = ENOMEM; goto fail; } ctrl[0] = talloc(ctrl, struct ldb_control); if (ctrl[0] == NULL) { ret = ENOMEM; goto fail; } ctrl[1] = NULL; ctrl[0]->oid = LDB_CONTROL_ASQ_OID; ctrl[0]->critical = 1; asq_control = talloc(ctrl[0], struct ldb_asq_control); if (asq_control == NULL) { ret = ENOMEM; goto fail; } asq_control->request = 1; asq_control->source_attribute = talloc_strdup(asq_control, asq_attribute); if (asq_control->source_attribute == NULL) { ret = ENOMEM; goto fail; } asq_control->src_attr_len = strlen(asq_control->source_attribute); ctrl[0]->data = asq_control; res = talloc_zero(tmp_ctx, struct ldb_result); if (!res) { ret = ENOMEM; goto fail; } ret = ldb_build_search_req(&ldb_req, domain->sysdb->ldb, tmp_ctx, base_dn, LDB_SCOPE_BASE, expression, attrs, ctrl, res, ldb_search_default_callback, NULL); if (ret != LDB_SUCCESS) { ret = sysdb_error_to_errno(ret); goto fail; } ret = ldb_request(domain->sysdb->ldb, ldb_req); if (ret == LDB_SUCCESS) { ret = ldb_wait(ldb_req->handle, LDB_WAIT_ALL); } if (ret) { ret = sysdb_error_to_errno(ret); goto fail; } *msgs_count = res->count; *msgs = talloc_move(mem_ctx, &res->msgs); talloc_zfree(tmp_ctx); return EOK; fail: if (ret == ENOENT) { DEBUG(SSSDBG_TRACE_FUNC, "No such entry\n"); } else if (ret) { DEBUG(SSSDBG_OP_FAILURE, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(tmp_ctx); return ret; } /* =Search-Users-with-Custom-Filter====================================== */ static int sysdb_cache_search_users(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, struct ldb_context *ldb, const char *sub_filter, const char **attrs, size_t *msgs_count, struct ldb_message ***msgs) { TALLOC_CTX *tmp_ctx; struct ldb_dn *basedn; char *filter; int ret; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } basedn = sysdb_user_base_dn(tmp_ctx, domain); if (!basedn) { DEBUG(SSSDBG_OP_FAILURE, "Failed to build base dn\n"); ret = ENOMEM; goto fail; } filter = talloc_asprintf(tmp_ctx, "(&(%s)%s)", SYSDB_UC, sub_filter); if (!filter) { DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); ret = ENOMEM; goto fail; } DEBUG(SSSDBG_TRACE_INTERNAL, "Search users with filter: %s\n", filter); ret = sysdb_cache_search_entry(mem_ctx, ldb, basedn, LDB_SCOPE_SUBTREE, filter, attrs, msgs_count, msgs); if (ret) { goto fail; } talloc_zfree(tmp_ctx); return EOK; fail: if (ret == ENOENT) { DEBUG(SSSDBG_TRACE_INTERNAL, "No such entry\n"); } else if (ret) { DEBUG(SSSDBG_MINOR_FAILURE, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(tmp_ctx); return ret; } int sysdb_search_users(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *sub_filter, const char **attrs, size_t *msgs_count, struct ldb_message ***msgs) { errno_t ret; ret = sysdb_cache_search_users(mem_ctx, domain, domain->sysdb->ldb, sub_filter, attrs, msgs_count, msgs); if (ret != EOK) { return ret; } return sysdb_merge_msg_list_ts_attrs(domain->sysdb, *msgs_count, *msgs, attrs); } int sysdb_search_ts_users(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *sub_filter, const char **attrs, struct ldb_result *res) { size_t msgs_count; struct ldb_message **msgs; int ret; if (res == NULL) { return EINVAL; } ZERO_STRUCT(*res); if (domain->sysdb->ldb_ts == NULL) { return ENOENT; } ret = sysdb_cache_search_users(mem_ctx, domain, domain->sysdb->ldb_ts, sub_filter, attrs, &msgs_count, &msgs); if (ret == EOK) { res->count = (unsigned)msgs_count; res->msgs = msgs; } return ret; } /* =Delete-User-by-Name-OR-uid============================================ */ int sysdb_delete_user(struct sss_domain_info *domain, const char *name, uid_t uid) { TALLOC_CTX *tmp_ctx; const char *attrs[] = {SYSDB_GHOST, NULL}; size_t msg_count; char *filter; struct ldb_message **msgs; struct ldb_message *msg; int ret; int i; char *sanitized_name; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } if (name) { ret = sysdb_search_user_by_name(tmp_ctx, domain, name, NULL, &msg); } else { ret = sysdb_search_user_by_uid(tmp_ctx, domain, uid, NULL, &msg); } if (ret == EOK) { if (name && uid) { /* verify name/gid match */ const char *c_name; uint64_t c_uid; c_name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); c_uid = ldb_msg_find_attr_as_uint64(msg, SYSDB_UIDNUM, 0); if (c_name == NULL || c_uid == 0) { DEBUG(SSSDBG_OP_FAILURE, "Attribute is missing but this should never happen!\n"); ret = EFAULT; goto fail; } if (strcmp(name, c_name) || uid != c_uid) { /* this is not the entry we are looking for */ ret = EINVAL; goto fail; } } ret = sysdb_delete_entry(domain->sysdb, msg->dn, false); if (ret) { goto fail; } } else if (ret == ENOENT && name != NULL) { /* Perhaps a ghost user? */ ret = sss_filter_sanitize(tmp_ctx, name, &sanitized_name); if (ret != EOK) { goto fail; } filter = talloc_asprintf(tmp_ctx, "(%s=%s)", SYSDB_GHOST, sanitized_name); if (filter == NULL) { ret = ENOMEM; goto fail; } ret = sysdb_search_groups(tmp_ctx, domain, filter, attrs, &msg_count, &msgs); if (ret != EOK) { goto fail; } for (i = 0; i < msg_count; i++) { msg = ldb_msg_new(tmp_ctx); if (!msg) { ERROR_OUT(ret, ENOMEM, fail); } msg->dn = msgs[i]->dn; ret = sysdb_delete_string(msg, SYSDB_GHOST, name); if (ret) goto fail; ret = ldb_modify(domain->sysdb->ldb, msg); if (ret != LDB_SUCCESS) { DEBUG(SSSDBG_MINOR_FAILURE, "ldb_modify failed: [%s](%d)[%s]\n", ldb_strerror(ret), ret, ldb_errstring(domain->sysdb->ldb)); } ret = sysdb_error_to_errno(ret); if (ret != EOK) { goto fail; } talloc_zfree(msg); } } else { goto fail; } talloc_zfree(tmp_ctx); return EOK; fail: DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, strerror(ret)); talloc_zfree(tmp_ctx); return ret; } /* =Search-Groups-with-Custom-Filter===================================== */ static int sysdb_cache_search_groups(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, struct ldb_context *ldb, const char *sub_filter, const char **attrs, size_t *msgs_count, struct ldb_message ***msgs) { TALLOC_CTX *tmp_ctx; struct ldb_dn *basedn; char *filter; int ret; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } basedn = sysdb_group_base_dn(tmp_ctx, domain); if (!basedn) { DEBUG(SSSDBG_OP_FAILURE, "Failed to build base dn\n"); ret = ENOMEM; goto fail; } filter = talloc_asprintf(tmp_ctx, "(&(%s)%s)", SYSDB_GC, sub_filter); if (!filter) { DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); ret = ENOMEM; goto fail; } DEBUG(SSSDBG_TRACE_INTERNAL, "Search groups with filter: %s\n", filter); ret = sysdb_cache_search_entry(mem_ctx, ldb, basedn, LDB_SCOPE_SUBTREE, filter, attrs, msgs_count, msgs); if (ret) { goto fail; } talloc_zfree(tmp_ctx); return EOK; fail: if (ret == ENOENT) { DEBUG(SSSDBG_TRACE_INTERNAL, "No such entry\n"); } else if (ret) { DEBUG(SSSDBG_MINOR_FAILURE, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(tmp_ctx); return ret; } int sysdb_search_groups(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *sub_filter, const char **attrs, size_t *msgs_count, struct ldb_message ***msgs) { errno_t ret; ret = sysdb_cache_search_groups(mem_ctx, domain, domain->sysdb->ldb, sub_filter, attrs, msgs_count, msgs); if (ret != EOK) { return ret; } return sysdb_merge_msg_list_ts_attrs(domain->sysdb, *msgs_count, *msgs, attrs); } int sysdb_search_ts_groups(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *sub_filter, const char **attrs, struct ldb_result *res) { size_t msgs_count; struct ldb_message **msgs; int ret; if (res == NULL) { return EINVAL; } ZERO_STRUCT(*res); if (domain->sysdb->ldb_ts == NULL) { return ENOENT; } ret = sysdb_cache_search_groups(mem_ctx, domain, domain->sysdb->ldb_ts, sub_filter, attrs, &msgs_count, &msgs); if (ret == EOK) { res->count = (unsigned)msgs_count; res->msgs = msgs; } return ret; } /* =Delete-Group-by-Name-OR-gid=========================================== */ int sysdb_delete_group(struct sss_domain_info *domain, const char *name, gid_t gid) { TALLOC_CTX *tmp_ctx; struct ldb_message *msg; int ret; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } if (name) { ret = sysdb_search_group_by_name(tmp_ctx, domain, name, NULL, &msg); } else { ret = sysdb_search_group_by_gid(tmp_ctx, domain, gid, NULL, &msg); } if (ret) { goto fail; } if (name && gid) { /* verify name/gid match */ const char *c_name; uint64_t c_gid; c_name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); c_gid = ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0); if (c_name == NULL || c_gid == 0) { DEBUG(SSSDBG_OP_FAILURE, "Attribute is missing but this should never happen!\n"); ret = EFAULT; goto fail; } if (strcmp(name, c_name) || gid != c_gid) { /* this is not the entry we are looking for */ ret = EINVAL; goto fail; } } ret = sysdb_delete_entry(domain->sysdb, msg->dn, false); if (ret) { goto fail; } talloc_zfree(tmp_ctx); return EOK; fail: DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, strerror(ret)); talloc_zfree(tmp_ctx); return ret; } /* =Search-Netgroups-with-Custom-Filter===================================== */ int sysdb_search_netgroups(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *sub_filter, const char **attrs, size_t *msgs_count, struct ldb_message ***msgs) { TALLOC_CTX *tmp_ctx; struct ldb_dn *basedn; char *filter; int ret; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } basedn = ldb_dn_new_fmt(tmp_ctx, domain->sysdb->ldb, SYSDB_TMPL_NETGROUP_BASE, domain->name); if (!basedn) { DEBUG(SSSDBG_OP_FAILURE, "Failed to build base dn\n"); ret = ENOMEM; goto fail; } filter = talloc_asprintf(tmp_ctx, "(&(%s)%s)", SYSDB_NC, sub_filter); if (!filter) { DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); ret = ENOMEM; goto fail; } DEBUG(SSSDBG_TRACE_FUNC, "Search netgroups with filter: %s\n", filter); ret = sysdb_search_entry(mem_ctx, domain->sysdb, basedn, LDB_SCOPE_SUBTREE, filter, attrs, msgs_count, msgs); if (ret) { goto fail; } talloc_zfree(tmp_ctx); return EOK; fail: if (ret == ENOENT) { DEBUG(SSSDBG_TRACE_FUNC, "Entry not found\n"); } else { DEBUG(SSSDBG_OP_FAILURE, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(tmp_ctx); return ret; } /* =Delete-Netgroup-by-Name============================================== */ int sysdb_delete_netgroup(struct sss_domain_info *domain, const char *name) { TALLOC_CTX *tmp_ctx; struct ldb_message *msg; int ret; if (!name) return EINVAL; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } ret = sysdb_search_netgroup_by_name(tmp_ctx, domain, name, NULL, &msg); if (ret != EOK && ret != ENOENT) { DEBUG(SSSDBG_TRACE_FUNC, "sysdb_search_netgroup_by_name failed: %d (%s)\n", ret, strerror(ret)); goto done; } else if (ret == ENOENT) { DEBUG(SSSDBG_TRACE_FUNC, "Netgroup does not exist, nothing to delete\n"); ret = EOK; goto done; } ret = sysdb_delete_entry(domain->sysdb, msg->dn, false); if (ret != EOK) { goto done; } done: if (ret != EOK) { DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_free(tmp_ctx); return ret; } int sysdb_delete_by_sid(struct sysdb_ctx *sysdb, struct sss_domain_info *domain, const char *sid_str) { TALLOC_CTX *tmp_ctx; struct ldb_result *res; int ret; if (!sid_str) return EINVAL; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } ret = sysdb_search_object_by_sid(tmp_ctx, domain, sid_str, NULL, &res); if (ret == ENOENT) { /* No existing entry. Just quit. */ DEBUG(SSSDBG_TRACE_FUNC, "search by sid did not return any results.\n"); ret = EOK; goto done; } else if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "search by sid failed: %d (%s)\n", ret, strerror(ret)); goto done; } if (res->count > 1) { DEBUG(SSSDBG_FATAL_FAILURE, "getbysid call returned more than one " \ "result !?!\n"); ret = EIO; goto done; } ret = sysdb_delete_entry(sysdb, res->msgs[0]->dn, false); if (ret != EOK) { goto done; } done: if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_free(tmp_ctx); return ret; } /* ========= Authentication against cached password ============ */ errno_t check_failed_login_attempts(struct confdb_ctx *cdb, struct ldb_message *ldb_msg, uint32_t *failed_login_attempts, time_t *delayed_until) { int ret; int allowed_failed_login_attempts; int failed_login_delay; time_t last_failed_login; time_t end; TALLOC_CTX *tmp_ctx; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } *delayed_until = -1; *failed_login_attempts = ldb_msg_find_attr_as_uint(ldb_msg, SYSDB_FAILED_LOGIN_ATTEMPTS, 0); last_failed_login = (time_t) ldb_msg_find_attr_as_int64(ldb_msg, SYSDB_LAST_FAILED_LOGIN, 0); ret = confdb_get_int(cdb, CONFDB_PAM_CONF_ENTRY, CONFDB_PAM_FAILED_LOGIN_ATTEMPTS, CONFDB_DEFAULT_PAM_FAILED_LOGIN_ATTEMPTS, &allowed_failed_login_attempts); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to read the number of allowed failed login " "attempts.\n"); ret = ERR_INTERNAL; goto done; } ret = confdb_get_int(cdb, CONFDB_PAM_CONF_ENTRY, CONFDB_PAM_FAILED_LOGIN_DELAY, CONFDB_DEFAULT_PAM_FAILED_LOGIN_DELAY, &failed_login_delay); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to read the failed login delay.\n"); ret = ERR_INTERNAL; goto done; } DEBUG(SSSDBG_TRACE_ALL, "Failed login attempts [%d], allowed failed login attempts [%d], " "failed login delay [%d].\n", *failed_login_attempts, allowed_failed_login_attempts, failed_login_delay); if (allowed_failed_login_attempts) { if (*failed_login_attempts >= allowed_failed_login_attempts) { if (failed_login_delay) { end = last_failed_login + (failed_login_delay * 60); if (end < time(NULL)) { DEBUG(SSSDBG_TRACE_LIBS, "failed_login_delay has passed, " "resetting failed_login_attempts.\n"); *failed_login_attempts = 0; } else { DEBUG(SSSDBG_TRACE_LIBS, "login delayed until %lld.\n", (long long) end); *delayed_until = end; ret = ERR_AUTH_DENIED; goto done; } } else { DEBUG(SSSDBG_CONF_SETTINGS, "Too many failed logins.\n"); ret = ERR_AUTH_DENIED; goto done; } } } ret = EOK; done: talloc_free(tmp_ctx); return ret; } static errno_t check_for_combined_2fa_password(struct sss_domain_info *domain, struct ldb_message *ldb_msg, const char *password, const char *userhash) { unsigned int cached_authtok_type; unsigned int cached_fa2_len; char *short_pw; char *comphash; size_t pw_len; TALLOC_CTX *tmp_ctx; int ret; cached_authtok_type = ldb_msg_find_attr_as_uint(ldb_msg, SYSDB_CACHEDPWD_TYPE, SSS_AUTHTOK_TYPE_EMPTY); if (cached_authtok_type != SSS_AUTHTOK_TYPE_2FA) { DEBUG(SSSDBG_TRACE_LIBS, "Wrong authtok type.\n"); return EINVAL; } cached_fa2_len = ldb_msg_find_attr_as_uint(ldb_msg, SYSDB_CACHEDPWD_FA2_LEN, 0); if (cached_fa2_len == 0) { DEBUG(SSSDBG_TRACE_LIBS, "Second factor size not available.\n"); return EINVAL; } pw_len = strlen(password); if (pw_len < cached_fa2_len + domain->cache_credentials_min_ff_length) { DEBUG(SSSDBG_TRACE_LIBS, "Password too short.\n"); return EINVAL; } tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); return ENOMEM; } short_pw = talloc_strndup(tmp_ctx, password, (pw_len - cached_fa2_len)); if (short_pw == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); ret = ENOMEM; goto done; } ret = s3crypt_sha512(tmp_ctx, short_pw, userhash, &comphash); if (ret != EOK) { DEBUG(SSSDBG_CONF_SETTINGS, "Failed to create password hash.\n"); ret = ERR_INTERNAL; goto done; } if (strcmp(userhash, comphash) != 0) { DEBUG(SSSDBG_MINOR_FAILURE, "Hash of shorten password does not match.\n"); ret = ERR_AUTH_FAILED; goto done; } ret = EOK; done: talloc_free(tmp_ctx); return ret; } int sysdb_cache_auth(struct sss_domain_info *domain, const char *name, const char *password, struct confdb_ctx *cdb, bool just_check, time_t *_expire_date, time_t *_delayed_until) { TALLOC_CTX *tmp_ctx; const char *attrs[] = { SYSDB_NAME, SYSDB_CACHEDPWD, SYSDB_DISABLED, SYSDB_LAST_LOGIN, SYSDB_LAST_ONLINE_AUTH, "lastCachedPasswordChange", "accountExpires", SYSDB_FAILED_LOGIN_ATTEMPTS, SYSDB_LAST_FAILED_LOGIN, SYSDB_CACHEDPWD_TYPE, SYSDB_CACHEDPWD_FA2_LEN, NULL }; struct ldb_message *ldb_msg; const char *userhash; char *comphash; uint64_t lastLogin = 0; int cred_expiration; uint32_t failed_login_attempts = 0; struct sysdb_attrs *update_attrs; bool authentication_successful = false; time_t expire_date = -1; time_t delayed_until = -1; int ret; if (name == NULL || *name == '\0') { DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); return EINVAL; } if (cdb == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Missing config db context.\n"); return EINVAL; } if (domain->sysdb == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Missing sysdb db context.\n"); return EINVAL; } if (!domain->cache_credentials) { DEBUG(SSSDBG_MINOR_FAILURE, "Cached credentials not available.\n"); return EINVAL; } tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } ret = ldb_transaction_start(domain->sysdb->ldb); if (ret) { talloc_zfree(tmp_ctx); ret = sysdb_error_to_errno(ret); return ret; } ret = sysdb_search_user_by_name(tmp_ctx, domain, name, attrs, &ldb_msg); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_search_user_by_name failed [%d][%s].\n", ret, strerror(ret)); if (ret == ENOENT) ret = ERR_ACCOUNT_UNKNOWN; goto done; } /* Check offline_auth_cache_timeout */ lastLogin = ldb_msg_find_attr_as_uint64(ldb_msg, SYSDB_LAST_ONLINE_AUTH, 0); ret = confdb_get_int(cdb, CONFDB_PAM_CONF_ENTRY, CONFDB_PAM_CRED_TIMEOUT, 0, &cred_expiration); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to read expiration time of offline credentials.\n"); goto done; } DEBUG(SSSDBG_TRACE_ALL, "Offline credentials expiration is [%d] days.\n", cred_expiration); if (cred_expiration) { expire_date = lastLogin + (cred_expiration * 86400); if (expire_date < time(NULL)) { DEBUG(SSSDBG_CONF_SETTINGS, "Cached user entry is too old.\n"); expire_date = 0; ret = ERR_CACHED_CREDS_EXPIRED; goto done; } } else { expire_date = 0; } ret = check_failed_login_attempts(cdb, ldb_msg, &failed_login_attempts, &delayed_until); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to check login attempts\n"); goto done; } /* TODO: verify user account (disabled, expired ...) */ userhash = ldb_msg_find_attr_as_string(ldb_msg, SYSDB_CACHEDPWD, NULL); if (userhash == NULL || *userhash == '\0') { DEBUG(SSSDBG_CONF_SETTINGS, "Cached credentials not available.\n"); ret = ERR_NO_CACHED_CREDS; goto done; } ret = s3crypt_sha512(tmp_ctx, password, userhash, &comphash); if (ret) { DEBUG(SSSDBG_CONF_SETTINGS, "Failed to create password hash.\n"); ret = ERR_INTERNAL; goto done; } update_attrs = sysdb_new_attrs(tmp_ctx); if (update_attrs == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_new_attrs failed.\n"); ret = ENOMEM; goto done; } if (strcmp(userhash, comphash) == 0 || check_for_combined_2fa_password(domain, ldb_msg, password, userhash) == EOK) { /* TODO: probable good point for audit logging */ DEBUG(SSSDBG_CONF_SETTINGS, "Hashes do match!\n"); authentication_successful = true; if (just_check) { ret = EOK; goto done; } ret = sysdb_attrs_add_time_t(update_attrs, SYSDB_LAST_LOGIN, time(NULL)); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "sysdb_attrs_add_time_t failed, " "but authentication is successful.\n"); ret = EOK; goto done; } ret = sysdb_attrs_add_uint32(update_attrs, SYSDB_FAILED_LOGIN_ATTEMPTS, 0U); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "sysdb_attrs_add_uint32 failed, " "but authentication is successful.\n"); ret = EOK; goto done; } } else { DEBUG(SSSDBG_CONF_SETTINGS, "Authentication failed.\n"); authentication_successful = false; ret = sysdb_attrs_add_time_t(update_attrs, SYSDB_LAST_FAILED_LOGIN, time(NULL)); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "sysdb_attrs_add_time_t failed.\n"); goto done; } ret = sysdb_attrs_add_uint32(update_attrs, SYSDB_FAILED_LOGIN_ATTEMPTS, ++failed_login_attempts); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "sysdb_attrs_add_uint32 failed.\n"); goto done; } } ret = sysdb_set_user_attr(domain, name, update_attrs, LDB_FLAG_MOD_REPLACE); if (ret) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to update Login attempt information!\n"); } done: if (_expire_date != NULL) { *_expire_date = expire_date; } if (_delayed_until != NULL) { *_delayed_until = delayed_until; } if (ret) { ldb_transaction_cancel(domain->sysdb->ldb); } else { ret = ldb_transaction_commit(domain->sysdb->ldb); ret = sysdb_error_to_errno(ret); if (ret) { DEBUG(SSSDBG_OP_FAILURE, "Failed to commit transaction!\n"); } } if (authentication_successful) { ret = EOK; } else { if (ret == EOK) { ret = ERR_AUTH_FAILED; } } talloc_free(tmp_ctx); return ret; } static errno_t sysdb_update_members_ex(struct sss_domain_info *domain, const char *member, enum sysdb_member_type type, const char *const *add_groups, const char *const *del_groups, bool is_dn) { errno_t ret; errno_t sret; int i; bool in_transaction = false; TALLOC_CTX *tmp_ctx = talloc_new(NULL); if(!tmp_ctx) { return ENOMEM; } ret = sysdb_transaction_start(domain->sysdb); if (ret != EOK) { DEBUG(SSSDBG_FATAL_FAILURE, "Failed to start update transaction\n"); goto done; } in_transaction = true; if (add_groups) { /* Add the user to all add_groups */ for (i = 0; add_groups[i]; i++) { ret = sysdb_add_group_member(domain, add_groups[i], member, type, is_dn); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not add member [%s] to group [%s]. " "Skipping.\n", member, add_groups[i]); /* Continue on, we should try to finish the rest */ } } } if (del_groups) { /* Remove the user from all del_groups */ for (i = 0; del_groups[i]; i++) { ret = sysdb_remove_group_member(domain, del_groups[i], member, type, is_dn); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not remove member [%s] from group [%s]. " "Skipping\n", member, del_groups[i]); /* Continue on, we should try to finish the rest */ } } } ret = sysdb_transaction_commit(domain->sysdb); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); goto done; } in_transaction = false; done: if (in_transaction) { sret = sysdb_transaction_cancel(domain->sysdb); if (sret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); } } talloc_free(tmp_ctx); return ret; } errno_t sysdb_update_members(struct sss_domain_info *domain, const char *member, enum sysdb_member_type type, const char *const *add_groups, const char *const *del_groups) { return sysdb_update_members_ex(domain, member, type, add_groups, del_groups, false); } errno_t sysdb_update_members_dn(struct sss_domain_info *member_domain, const char *member, enum sysdb_member_type type, const char *const *add_groups, const char *const *del_groups) { return sysdb_update_members_ex(member_domain, member, type, add_groups, del_groups, true); } errno_t sysdb_remove_attrs(struct sss_domain_info *domain, const char *name, enum sysdb_member_type type, char **remove_attrs) { errno_t ret; errno_t sret = EOK; bool in_transaction = false; struct ldb_message *msg; int lret; size_t i; msg = ldb_msg_new(NULL); if (!msg) return ENOMEM; switch(type) { case SYSDB_MEMBER_USER: msg->dn = sysdb_user_dn(msg, domain, name); break; case SYSDB_MEMBER_GROUP: msg->dn = sysdb_group_dn(msg, domain, name); break; case SYSDB_MEMBER_NETGROUP: msg->dn = sysdb_netgroup_dn(msg, domain, name); break; case SYSDB_MEMBER_SERVICE: msg->dn = sysdb_svc_dn(domain->sysdb, msg, domain->name, name); break; } if (!msg->dn) { ret = ENOMEM; goto done; } ret = sysdb_transaction_start(domain->sysdb); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); goto done; } in_transaction = true; for (i = 0; remove_attrs[i]; i++) { /* SYSDB_MEMBEROF is exclusively handled by the memberof plugin */ if (strcasecmp(remove_attrs[i], SYSDB_MEMBEROF) == 0) { continue; } DEBUG(SSSDBG_TRACE_INTERNAL, "Removing attribute [%s] from [%s]\n", remove_attrs[i], name); lret = ldb_msg_add_empty(msg, remove_attrs[i], LDB_FLAG_MOD_DELETE, NULL); if (lret != LDB_SUCCESS) { ret = sysdb_error_to_errno(lret); goto done; } /* We need to do individual modifies so that we can * skip unknown attributes. Otherwise, any nonexistent * attribute in the sysdb will cause other removals to * fail. */ lret = ldb_modify(domain->sysdb->ldb, msg); if (lret != LDB_SUCCESS && lret != LDB_ERR_NO_SUCH_ATTRIBUTE) { DEBUG(SSSDBG_MINOR_FAILURE, "ldb_modify failed: [%s](%d)[%s]\n", ldb_strerror(lret), lret, ldb_errstring(domain->sysdb->ldb)); ret = sysdb_error_to_errno(lret); goto done; } /* Remove this attribute and move on to the next one */ ldb_msg_remove_attr(msg, remove_attrs[i]); } ret = sysdb_transaction_commit(domain->sysdb); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); goto done; } in_transaction = false; ret = EOK; done: if (in_transaction) { sret = sysdb_transaction_cancel(domain->sysdb); if (sret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); } } talloc_free(msg); return ret; } static errno_t sysdb_search_object_by_str_attr(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *filter_tmpl, const char *str, const char **attrs, struct ldb_result **_res) { TALLOC_CTX *tmp_ctx; const char *def_attrs[] = { SYSDB_NAME, SYSDB_UIDNUM, SYSDB_GIDNUM, ORIGINALAD_PREFIX SYSDB_NAME, SYSDB_DEFAULT_ATTRS, NULL }; struct ldb_dn *basedn; int ret; struct ldb_result *res = NULL; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { return ENOMEM; } basedn = ldb_dn_new_fmt(tmp_ctx, domain->sysdb->ldb, SYSDB_DOM_BASE, domain->name); if (basedn == NULL) { DEBUG(SSSDBG_OP_FAILURE, "ldb_dn_new_fmt failed.\n"); ret = ENOMEM; goto done; } ret = ldb_search(domain->sysdb->ldb, tmp_ctx, &res, basedn, LDB_SCOPE_SUBTREE, attrs?attrs:def_attrs, filter_tmpl, str); if (ret != EOK) { ret = sysdb_error_to_errno(ret); DEBUG(SSSDBG_OP_FAILURE, "ldb_search failed.\n"); goto done; } if (res->count > 1) { DEBUG(SSSDBG_CRIT_FAILURE, "Search for [%s] with filter [%s] " \ "returned more than one object.\n", str, filter_tmpl); ret = EINVAL; goto done; } else if (res->count == 0) { ret = ENOENT; goto done; } /* Merge in the timestamps from the fast ts db */ ret = sysdb_merge_res_ts_attrs(domain->sysdb, res, attrs); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Cannot merge timestamp cache values\n"); /* non-fatal */ } *_res = talloc_steal(mem_ctx, res); done: if (ret == ENOENT) { DEBUG(SSSDBG_TRACE_FUNC, "No such entry.\n"); } else if (ret) { DEBUG(SSSDBG_OP_FAILURE, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(tmp_ctx); return ret; } errno_t sysdb_search_object_by_sid(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *sid_str, const char **attrs, struct ldb_result **res) { return sysdb_search_object_by_str_attr(mem_ctx, domain, SYSDB_SID_FILTER, sid_str, attrs, res); } errno_t sysdb_search_object_by_uuid(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *uuid_str, const char **attrs, struct ldb_result **res) { return sysdb_search_object_by_str_attr(mem_ctx, domain, SYSDB_UUID_FILTER, uuid_str, attrs, res); } errno_t sysdb_search_object_by_cert(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *cert, const char **attrs, struct ldb_result **res) { int ret; char *user_filter; ret = sss_cert_derb64_to_ldap_filter(mem_ctx, cert, SYSDB_USER_CERT, &user_filter); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sss_cert_derb64_to_ldap_filter failed.\n"); return ret; } ret = sysdb_search_object_by_str_attr(mem_ctx, domain, SYSDB_USER_CERT_FILTER, user_filter, attrs, res); talloc_free(user_filter); return ret; } errno_t sysdb_search_user_by_cert(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *cert, struct ldb_result **res) { const char *user_attrs[] = SYSDB_PW_ATTRS; return sysdb_search_object_by_cert(mem_ctx, domain, cert, user_attrs, res); } errno_t sysdb_remove_cert(struct sss_domain_info *domain, const char *cert) { struct ldb_message_element el = { 0, SYSDB_USER_CERT, 0, NULL }; struct sysdb_attrs del_attrs = { 1, &el }; const char *attrs[] = {SYSDB_NAME, NULL}; struct ldb_result *res = NULL; unsigned int i; errno_t ret; ret = sysdb_search_object_by_cert(NULL, domain, cert, attrs, &res); if (ret == ENOENT || res == NULL) { ret = EOK; goto done; } else if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Unable to lookup object by cert " "[%d]: %s\n", ret, sss_strerror(ret)); goto done; } /* Certificate may be found on more objects, remove it from all. * If object contains more then one certificate, we still remove the * whole attribute since it will be downloaded again. */ for (i = 0; i < res->count; i++) { ret = sysdb_set_entry_attr(domain->sysdb, res->msgs[0]->dn, &del_attrs, SYSDB_MOD_DEL); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to remove certificate " "[%d]: %s\n", ret, sss_strerror(ret)); goto done; } ret = sysdb_mark_entry_as_expired_ldb_dn(domain, res->msgs[0]->dn); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Unable to expire object " "[%d]: %s\n", ret, sss_strerror(ret)); continue; } } done: talloc_free(res); return ret; } errno_t sysdb_get_sids_of_members(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, const char *group_name, const char ***_sids, const char ***_dns, size_t *_n) { errno_t ret; size_t i, m_count; TALLOC_CTX *tmp_ctx; struct ldb_message *msg; struct ldb_message **members; const char *attrs[] = { SYSDB_SID_STR, NULL }; const char **sids = NULL, **dns = NULL; size_t n = 0; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } ret = sysdb_search_group_by_name(tmp_ctx, dom, group_name, NULL, &msg); if (ret != EOK) { goto done; } /* Get sid_str attribute of all elemets pointed to by group members */ ret = sysdb_asq_search(tmp_ctx, dom, msg->dn, NULL, SYSDB_MEMBER, attrs, &m_count, &members); if (ret != EOK) { goto done; } sids = talloc_array(tmp_ctx, const char*, m_count); if (sids == NULL) { ret = ENOMEM; goto done; } dns = talloc_array(tmp_ctx, const char*, m_count); if (dns == NULL) { ret = ENOMEM; goto done; } for (i=0; i < m_count; i++) { const char *sidstr; sidstr = ldb_msg_find_attr_as_string(members[i], SYSDB_SID_STR, NULL); if (sidstr != NULL) { sids[n] = talloc_steal(sids, sidstr); dns[n] = talloc_steal(dns, ldb_dn_get_linearized(members[i]->dn)); if (dns[n] == NULL) { ret = ENOMEM; goto done; } n++; } } if (n == 0) { ret = ENOENT; goto done; } *_n = n; *_sids = talloc_steal(mem_ctx, sids); *_dns = talloc_steal(mem_ctx, dns); ret = EOK; done: if (ret == ENOENT) { DEBUG(SSSDBG_TRACE_FUNC, "No such entry\n"); } else if (ret) { DEBUG(SSSDBG_OP_FAILURE, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_free(tmp_ctx); return ret; } errno_t sysdb_handle_original_uuid(const char *orig_name, struct sysdb_attrs *src_attrs, const char *src_name, struct sysdb_attrs *dest_attrs, const char *dest_name) { int ret; struct ldb_message_element *el; char guid_str_buf[GUID_STR_BUF_SIZE]; if (orig_name == NULL) { /* This provider doesn't handle UUIDs */ return ENOENT; } if (src_attrs == NULL || src_name == NULL || dest_attrs == NULL || dest_name == NULL) { return EINVAL; } ret = sysdb_attrs_get_el_ext(src_attrs, src_name, false, &el); if (ret != EOK) { if (ret != ENOENT) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_el failed.\n"); } return ret; } if (el->num_values != 1) { DEBUG(SSSDBG_MINOR_FAILURE, "Found more than one UUID value, using the first.\n"); } /* Check if we got a binary AD objectGUID */ if (el->values[0].length == GUID_BIN_LENGTH && strcasecmp(orig_name, "objectGUID") == 0) { ret = guid_blob_to_string_buf(el->values[0].data, guid_str_buf, GUID_STR_BUF_SIZE); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "guid_blob_to_string_buf failed.\n"); return ret; } ret = sysdb_attrs_add_string(dest_attrs, dest_name, guid_str_buf); } else { ret = sysdb_attrs_add_string(dest_attrs, dest_name, (const char *)el->values[0].data); } if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_string failed.\n"); return ret;; } return EOK; } /* Mark entry as expired */ errno_t sysdb_mark_entry_as_expired_ldb_dn(struct sss_domain_info *dom, struct ldb_dn *ldbdn) { struct ldb_message *msg; errno_t ret; TALLOC_CTX *tmp_ctx; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } msg = ldb_msg_new(tmp_ctx); if (msg == NULL) { ret = ENOMEM; goto done; } msg->dn = ldbdn; ret = ldb_msg_add_empty(msg, SYSDB_CACHE_EXPIRE, LDB_FLAG_MOD_REPLACE, NULL); if (ret != LDB_SUCCESS) { ret = sysdb_error_to_errno(ret); goto done; } ret = ldb_msg_add_string(msg, SYSDB_CACHE_EXPIRE, "1"); if (ret != LDB_SUCCESS) { ret = sysdb_error_to_errno(ret); goto done; } ret = ldb_modify(dom->sysdb->ldb, msg); if (ret != LDB_SUCCESS) { ret = sysdb_error_to_errno(ret); goto done; } ret = EOK; done: talloc_free(tmp_ctx); return ret; } errno_t sysdb_mark_entry_as_expired_ldb_val(struct sss_domain_info *dom, struct ldb_val *dn_val) { struct ldb_dn *ldbdn; errno_t ret; TALLOC_CTX *tmp_ctx; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } ldbdn = ldb_dn_from_ldb_val(tmp_ctx, dom->sysdb->ldb, dn_val); if (ldbdn == NULL) { ret = ENOMEM; goto done; } ret = sysdb_mark_entry_as_expired_ldb_dn(dom, ldbdn); done: talloc_free(tmp_ctx); return ret; }