/* SSSD Authors: Samuel Cabrero Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. 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 #include "db/sysdb.h" #include "db/sysdb_private.h" #include "db/sysdb_iphosts.h" static errno_t sysdb_host_update(struct sysdb_ctx *sysdb, struct ldb_dn *dn, const char **aliases, const char **addresses); static errno_t sysdb_host_remove_alias(struct sysdb_ctx *sysdb, struct ldb_dn *dn, const char *alias); errno_t sysdb_gethostbyname(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *name, struct ldb_result **_res) { errno_t ret; TALLOC_CTX *tmp_ctx; static const char *attrs[] = { SYSDB_NAME, SYSDB_NAME_ALIAS, SYSDB_IP_HOST_ATTR_ADDRESS, SYSDB_DEFAULT_ATTRS, NULL, }; char *sanitized_name; char *subfilter; struct ldb_result *res = NULL; struct ldb_message **msgs; size_t msgs_count; DEBUG(SSSDBG_TRACE_FUNC, "Searching host by name [%s] in domain [%s]\n", name, domain->name); tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } ret = sss_filter_sanitize(tmp_ctx, name, &sanitized_name); if (ret != EOK) { goto done; } subfilter = talloc_asprintf(tmp_ctx, SYSDB_IP_HOST_BYNAME_SUBFILTER, sanitized_name, sanitized_name); if (subfilter == NULL) { ret = ENOMEM; goto done; } ret = sysdb_search_hosts(mem_ctx, domain, subfilter, attrs, &msgs_count, &msgs); if (ret == EOK) { res = talloc_zero(mem_ctx, struct ldb_result); if (res == NULL) { ret = ENOMEM; goto done; } res->count = msgs_count; res->msgs = talloc_steal(res, msgs); } *_res = res; done: talloc_free(tmp_ctx); return ret; } errno_t sysdb_gethostbyaddr(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *address, struct ldb_result **_res) { errno_t ret; TALLOC_CTX *tmp_ctx; static const char *attrs[] = { SYSDB_NAME, SYSDB_NAME_ALIAS, SYSDB_IP_HOST_ATTR_ADDRESS, NULL, }; char *sanitized_address; char *subfilter; struct ldb_result *res = NULL; struct ldb_message **msgs; size_t msgs_count; char *canonical_address; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } /* IP addresses are stored in canonical form, canonicalize the given * address before search */ ret = sss_canonicalize_ip_address(tmp_ctx, address, &canonical_address); if (ret != EOK) { goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Searching host by address [%s] in domain [%s]\n", canonical_address, domain->name); ret = sss_filter_sanitize(tmp_ctx, canonical_address, &sanitized_address); if (ret != EOK) { goto done; } subfilter = talloc_asprintf(tmp_ctx, SYSDB_IP_HOST_BYADDR_SUBFILTER, sanitized_address); if (subfilter == NULL) { ret = ENOMEM; goto done; } ret = sysdb_search_hosts(mem_ctx, domain, subfilter, attrs, &msgs_count, &msgs); if (ret == EOK) { res = talloc_zero(mem_ctx, struct ldb_result); if (res == NULL) { ret = ENOMEM; goto done; } res->count = msgs_count; res->msgs = talloc_steal(res, msgs); } *_res = res; done: talloc_free(tmp_ctx); return ret; } errno_t sysdb_store_host(struct sss_domain_info *domain, const char *primary_name, const char **aliases, const char **addresses, struct sysdb_attrs *extra_attrs, char **remove_attrs, uint64_t cache_timeout, time_t now) { errno_t ret; errno_t sret; TALLOC_CTX *tmp_ctx; bool in_transaction = false; struct ldb_result *res = NULL; const char *name; unsigned int i, j; struct ldb_dn *update_dn = NULL; struct sysdb_attrs *attrs; struct sysdb_ctx *sysdb; size_t len; DEBUG(SSSDBG_TRACE_FUNC, "Storing host [%s] into cache, domain [%s]\n", primary_name, domain->name); tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } sysdb = domain->sysdb; ret = sysdb_transaction_start(sysdb); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); goto done; } in_transaction = true; /* Check that the addresses are unique. If the address appears for any * host other than the one matching the primary_name, we need to * remove them so that gethostbyaddr() can work properly. * Last entry saved to the cache should always "win". */ len = talloc_array_length(addresses); for (i = 0; i < len && addresses[i] != NULL; i++) { ret = sysdb_gethostbyaddr(tmp_ctx, domain, addresses[i], &res); if (ret != EOK && ret != ENOENT) { goto done; } else if (ret != ENOENT) { if (res->count != 1) { /* Somehow the cache has multiple entries with the same * address. This is corrupted. We'll delete them all to * sort it out. */ for (j = 0; j < res->count; j++) { DEBUG(SSSDBG_CRIT_FAILURE, "Corrupt cache entry [%s] detected. Deleting\n", ldb_dn_canonical_string(tmp_ctx, res->msgs[j]->dn)); ret = sysdb_delete_entry(sysdb, res->msgs[j]->dn, true); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Could not delete corrupt cache entry [%s]\n", ldb_dn_canonical_string(tmp_ctx, res->msgs[j]->dn)); goto done; } } } else { /* Check whether this is the same name as we're currently * saving to the cache. */ name = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_NAME, NULL); if (name == NULL || strcmp(name, primary_name) != 0) { if (name == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "A host with no name?\n"); /* Corrupted */ } /* Either this is a corrupt entry or it's another host * claiming ownership of this address. In order to account * for address reassignments, we need to delete the old * entry. */ DEBUG(SSSDBG_TRACE_FUNC, "Corrupt or replaced cache entry [%s] detected. " "Deleting\n", ldb_dn_canonical_string(tmp_ctx, res->msgs[0]->dn)); ret = sysdb_delete_entry(sysdb, res->msgs[0]->dn, true); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Could not delete cache entry [%s]\n", ldb_dn_canonical_string(tmp_ctx, res->msgs[0]->dn)); } } } } talloc_zfree(res); } /* Ok, addresses should now be unique. Now look the host up by name * to determine if we need to update existing entries or modify aliases. */ ret = sysdb_gethostbyname(tmp_ctx, domain, primary_name, &res); if (ret != EOK && ret != ENOENT) { goto done; } else if (ret != ENOENT) { /* Found entries */ for (i = 0; i < res->count; i++) { /* Check whether this is the same name as we're currently * saving to the cache. */ name = ldb_msg_find_attr_as_string(res->msgs[i], SYSDB_NAME, NULL); if (name == NULL) { /* Corrupted */ DEBUG(SSSDBG_CRIT_FAILURE, "A host with no name?\n"); DEBUG(SSSDBG_TRACE_FUNC, "Corrupt cache entry [%s] detected. Deleting\n", ldb_dn_canonical_string(tmp_ctx, res->msgs[i]->dn)); ret = sysdb_delete_entry(sysdb, res->msgs[i]->dn, true); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Could not delete corrupt cache entry [%s]\n", ldb_dn_canonical_string(tmp_ctx, res->msgs[i]->dn)); goto done; } } else if (strcmp(name, primary_name) == 0) { /* This is the same host name, so we need * to update this entry with the values * provided. */ if (update_dn) { DEBUG(SSSDBG_CRIT_FAILURE, "Two existing hosts with the same name: [%s]? " "Deleting both.\n", primary_name); /* Delete the entry from the previous pass */ ret = sysdb_delete_entry(sysdb, update_dn, true); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Could not delete cache entry [%s]\n", ldb_dn_canonical_string(tmp_ctx, update_dn)); goto done; } /* Delete the new entry as well */ ret = sysdb_delete_entry(sysdb, res->msgs[i]->dn, true); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Could not delete cache entry [%s]\n", ldb_dn_canonical_string(tmp_ctx, res->msgs[i]->dn)); goto done; } update_dn = NULL; } else { update_dn = talloc_steal(tmp_ctx, res->msgs[i]->dn); } } else { /* Another host is claiming this name as an alias. * In order to account for aliases being promoted to * primary names, we need to make sure to remove the * old alias entry. */ ret = sysdb_host_remove_alias(sysdb, res->msgs[i]->dn, primary_name); if (ret != EOK) { goto done; } } } talloc_zfree(res); } if (update_dn) { /* Update the existing entry */ ret = sysdb_host_update(sysdb, update_dn, aliases, addresses); } else { /* Add a new entry */ ret = sysdb_host_add(tmp_ctx, domain, primary_name, aliases, addresses, &update_dn); } if (ret != EOK) { goto done; } if (extra_attrs == NULL) { attrs = sysdb_new_attrs(tmp_ctx); if (attrs == NULL) { ret = ENOMEM; goto done; } } else { attrs = extra_attrs; } 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_entry_attr(sysdb, update_dn, attrs, SYSDB_MOD_REP); if (ret != EOK) { goto done; } if (remove_attrs) { ret = sysdb_remove_attrs(domain, primary_name, SYSDB_MEMBER_HOST, remove_attrs); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Could not remove missing attributes: [%s]\n", strerror(ret)); goto done; } } ret = sysdb_transaction_commit(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(sysdb); if (sret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); } } talloc_free(tmp_ctx); return ret; } struct ldb_dn *sysdb_host_dn(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *name) { errno_t ret; char *clean_name; struct ldb_dn *dn; ret = sysdb_dn_sanitize(NULL, name, &clean_name); if (ret != EOK) { return NULL; } dn = ldb_dn_new_fmt(mem_ctx, domain->sysdb->ldb, SYSDB_TMPL_IP_HOST, clean_name, domain->name); talloc_free(clean_name); return dn; } errno_t sysdb_host_add(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, const char *primary_name, const char **aliases, const char **addresses, struct ldb_dn **dn) { errno_t ret; int lret; TALLOC_CTX *tmp_ctx; struct ldb_message *msg; unsigned long i; size_t len; DEBUG(SSSDBG_TRACE_FUNC, "Adding host [%s] to domain [%s]\n", primary_name, domain->name); tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } msg = ldb_msg_new(tmp_ctx); if (msg == NULL) { ret = ENOMEM; goto done; } /* host dn */ msg->dn = sysdb_host_dn(msg, domain, primary_name); if (msg->dn == NULL) { ret = ENOMEM; goto done; } /* Objectclass */ ret = sysdb_add_string(msg, SYSDB_OBJECTCLASS, SYSDB_HOST_CLASS); if (ret != EOK) { goto done; } /* Set the primary name */ ret = sysdb_add_string(msg, SYSDB_NAME, primary_name); if (ret != EOK) { goto done; } /* If this iphost has any aliases, include them */ if (aliases != NULL && aliases[0] != NULL) { /* Set the name aliases */ lret = ldb_msg_add_empty(msg, SYSDB_NAME_ALIAS, LDB_FLAG_MOD_ADD, NULL); if (lret != LDB_SUCCESS) { ret = sysdb_error_to_errno(lret); goto done; } len = talloc_array_length(aliases); for (i = 0; i < len && aliases[i] != NULL ; i++) { lret = ldb_msg_add_string(msg, SYSDB_NAME_ALIAS, aliases[i]); if (lret != LDB_SUCCESS) { ret = sysdb_error_to_errno(lret); goto done; } } } /* Set the addresses */ lret = ldb_msg_add_empty(msg, SYSDB_IP_HOST_ATTR_ADDRESS, LDB_FLAG_MOD_ADD, NULL); if (lret != LDB_SUCCESS) { ret = sysdb_error_to_errno(lret); goto done; } len = talloc_array_length(addresses); for (i = 0; i < len && addresses[i] != NULL; i++) { char *addr = NULL; ret = sss_canonicalize_ip_address(msg, addresses[i], &addr); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to canonicalize address [%s]: %s\n", addresses[i], sss_strerror(ret)); goto done; } lret = ldb_msg_add_string(msg, SYSDB_IP_HOST_ATTR_ADDRESS, addr); if (lret != LDB_SUCCESS) { ret = sysdb_error_to_errno(lret); goto done; } } /* creation time */ ret = sysdb_add_ulong(msg, SYSDB_CREATE_TIME, (unsigned long)time(NULL)); if (ret != 0) { goto done; } lret = ldb_add(domain->sysdb->ldb, msg); ret = sysdb_error_to_errno(lret); if (ret == EOK && dn != NULL) { *dn = talloc_steal(mem_ctx, msg->dn); } done: if (ret != 0) { DEBUG(SSSDBG_TRACE_INTERNAL, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_free(tmp_ctx); return ret; } static errno_t sysdb_host_update(struct sysdb_ctx *sysdb, struct ldb_dn *dn, const char **aliases, const char **addresses) { errno_t ret; struct ldb_message *msg; int lret; unsigned int i; size_t len; if (dn == NULL || addresses[0] == NULL) { return EINVAL; } msg = ldb_msg_new(NULL); if (msg == NULL) { ret = ENOMEM; goto done; } msg->dn = dn; if (aliases != NULL && aliases[0] != NULL) { /* Update the aliases */ lret = ldb_msg_add_empty(msg, SYSDB_NAME_ALIAS, SYSDB_MOD_REP, NULL); if (lret != LDB_SUCCESS) { ret = ENOMEM; goto done; } len = talloc_array_length(aliases); for (i = 0; i < len && aliases[i] != NULL; i++) { lret = ldb_msg_add_string(msg, SYSDB_NAME_ALIAS, aliases[i]); if (lret != LDB_SUCCESS) { ret = EINVAL; goto done; } } } /* Update the addresses */ lret = ldb_msg_add_empty(msg, SYSDB_IP_HOST_ATTR_ADDRESS, SYSDB_MOD_REP, NULL); if (lret != LDB_SUCCESS) { ret = ENOMEM; goto done; } len = talloc_array_length(addresses); for (i = 0; i < len && addresses[i] != NULL; i++) { lret = ldb_msg_add_string(msg, SYSDB_IP_HOST_ATTR_ADDRESS, addresses[i]); if (lret != LDB_SUCCESS) { ret = EINVAL; goto done; } } lret = ldb_modify(sysdb->ldb, msg); if (lret != LDB_SUCCESS) { DEBUG(SSSDBG_MINOR_FAILURE, "ldb_modify failed: [%s](%d)[%s]\n", ldb_strerror(lret), lret, ldb_errstring(sysdb->ldb)); } ret = sysdb_error_to_errno(lret); done: if (ret) { DEBUG(SSSDBG_TRACE_INTERNAL, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_free(msg); return ret; } errno_t sysdb_host_remove_alias(struct sysdb_ctx *sysdb, struct ldb_dn *dn, const char *alias) { errno_t ret; struct ldb_message *msg; int lret; msg = ldb_msg_new(NULL); if (msg == NULL) { ret = ENOMEM; goto done; } msg->dn = dn; ret = sysdb_delete_string(msg, SYSDB_NAME_ALIAS, alias); if (ret != EOK) { goto done; } lret = ldb_modify(sysdb->ldb, msg); if (lret != LDB_SUCCESS) { DEBUG(SSSDBG_MINOR_FAILURE, "ldb_modify failed: [%s](%d)[%s]\n", ldb_strerror(lret), lret, ldb_errstring(sysdb->ldb)); } ret = sysdb_error_to_errno(lret); done: if (ret) { DEBUG(SSSDBG_TRACE_INTERNAL, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_zfree(msg); return ret; } errno_t sysdb_host_delete(struct sss_domain_info *domain, const char *name, const char *address) { errno_t ret, sret; TALLOC_CTX *tmp_ctx; struct ldb_result *res; unsigned int i; bool in_transaction = false; struct sysdb_ctx *sysdb = domain->sysdb; DEBUG(SSSDBG_TRACE_FUNC, "Deleting host [%s] - [%s] from domain [%s]\n", name, address, domain->name); tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } ret = sysdb_transaction_start(sysdb); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); goto done; } in_transaction = true; if (name != NULL) { ret = sysdb_gethostbyname(tmp_ctx, domain, name, &res); if (ret != EOK && ret != ENOENT) { goto done; } if (ret == ENOENT) { /* Doesn't exist in the DB. Nothing to do */ ret = EOK; goto done; } } else if (address != NULL) { ret = sysdb_gethostbyaddr(tmp_ctx, domain, address, &res); if (ret != EOK && ret != ENOENT) { goto done; } if (ret == ENOENT) { /* Doesn't exist in the DB. Nothing to do */ ret = EOK; goto done; } } else { DEBUG(SSSDBG_CRIT_FAILURE, "Host name or address needed\n"); ret = EINVAL; goto done; } /* There should only be one matching entry, * but if there are multiple, we should delete * them all to de-corrupt the DB. */ for (i = 0; i < res->count; i++) { ret = sysdb_delete_entry(sysdb, res->msgs[i]->dn, false); if (ret != EOK) { goto done; } } ret = sysdb_transaction_commit(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(sysdb); if (sret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); } } if (ret != EOK && ret != ENOENT) { DEBUG(SSSDBG_TRACE_INTERNAL, "Error: %d (%s)\n", ret, strerror(ret)); } talloc_free(tmp_ctx); return ret; } errno_t sysdb_search_hosts(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; DEBUG(SSSDBG_TRACE_FUNC, "Searching hosts with subfilter [%s] in " "domain [%s]\n", sub_filter, domain->name); tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Failed to allocate memory\n"); return ENOMEM; } basedn = ldb_dn_new_fmt(tmp_ctx, domain->sysdb->ldb, SYSDB_TMPL_IP_HOST_BASE, domain->name); if (basedn == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Failed to build base dn\n"); ret = ENOMEM; goto fail; } filter = talloc_asprintf(tmp_ctx, "(&(%s)%s)", SYSDB_IP_HOST_CLASS_FILTER, sub_filter); if (filter == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); ret = ENOMEM; goto fail; } DEBUG(SSSDBG_TRACE_INTERNAL, "Searching hosts with filter [%s] in " "domain [%s]\n", filter, domain->name); ret = sysdb_search_entry(mem_ctx, domain->sysdb, basedn, LDB_SCOPE_SUBTREE, filter, attrs, msgs_count, msgs); if (ret) { goto fail; } talloc_free(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_free(tmp_ctx); return ret; } errno_t sysdb_enumhostent(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain, struct ldb_result **_res) { errno_t ret; TALLOC_CTX *tmp_ctx; static const char *attrs[] = { SYSDB_NAME, SYSDB_NAME_ALIAS, SYSDB_IP_HOST_ATTR_ADDRESS, NULL, }; struct ldb_result *res = NULL; struct ldb_message **msgs; size_t msgs_count; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } ret = sysdb_search_hosts(mem_ctx, domain, "", attrs, &msgs_count, &msgs); if (ret == EOK) { res = talloc_zero(mem_ctx, struct ldb_result); if (res == NULL) { ret = ENOMEM; goto done; } res->count = msgs_count; res->msgs = talloc_steal(res, msgs); } *_res = res; done: talloc_free(tmp_ctx); return ret; }