From 827c8d676e3a871010e63eda82997a2f729a347c Mon Sep 17 00:00:00 2001 From: Petr Spacek Date: Jan 24 2014 12:56:26 +0000 Subject: Add support for write-back to LDAP after a DNS update. Current code doesn't try to do re-synchronization after an error during update. The error is logged and SERVFAIL is returned to the client but no re-synchronization is scheduled. Some hardening against race conditions will be required. Some data could be potentially lost if events in syncrepl queue are not processed fast enough. Signed-off-by: Petr Spacek --- diff --git a/src/ldap_driver.c b/src/ldap_driver.c index 806389c..14520bb 100644 --- a/src/ldap_driver.c +++ b/src/ldap_driver.c @@ -82,6 +82,21 @@ ldapdb_get_rbtdb(dns_db_t *db) { return ldapdb->rbtdb; } +/** + * Get full DNS name from the node. + * + * @warning + * The code silently expects that "node" came from RBTDB and thus + * assumption dns_dbnode_t (from RBTDB) == dns_rbtnode_t is correct. + * + * This should work as long as we use only RBTDB and nothing else. + */ +static isc_result_t +ldapdb_name_fromnode(dns_dbnode_t *node, dns_name_t *name) { + dns_rbtnode_t *rbtnode = (dns_rbtnode_t *) node; + return dns_rbt_fullnamefromnode(rbtnode, name); +} + /* * Functions. * @@ -403,48 +418,160 @@ allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, return dns_db_allrdatasets(ldapdb->rbtdb, node, version, now, iteratorp); } -/* !!! Write back to the LDAP is missing. - * Only in-memory RBTDB will be modified. */ +/* TODO: Add 'tainted' flag to the LDAP instance if something went wrong. */ static isc_result_t addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, isc_stdtime_t now, dns_rdataset_t *rdataset, unsigned int options, dns_rdataset_t *addedrdataset) { ldapdb_t *ldapdb = (ldapdb_t *) db; + dns_fixedname_t fname; + dns_rdatalist_t *rdlist = NULL; + isc_result_t result; REQUIRE(VALID_LDAPDB(ldapdb)); - return dns_db_addrdataset(ldapdb->rbtdb, node, version, now, - rdataset, options, addedrdataset); + dns_fixedname_init(&fname); + + CHECK(dns_db_addrdataset(ldapdb->rbtdb, node, version, now, + rdataset, options, addedrdataset)); + + CHECK(ldapdb_name_fromnode(node, dns_fixedname_name(&fname))); + result = dns_rdatalist_fromrdataset(rdataset, &rdlist); + INSIST(result == ISC_R_SUCCESS); + CHECK(write_to_ldap(dns_fixedname_name(&fname), ldapdb->ldap_inst, rdlist)); + +cleanup: + return result; + } -/* !!! Write back to the LDAP is missing. - * Only in-memory RBTDB will be modified. */ +static isc_result_t +node_isempty(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + isc_stdtime_t now, isc_boolean_t *isempty) { + dns_rdatasetiter_t *rds_iter = NULL; + dns_fixedname_t fname; + char buff[DNS_NAME_FORMATSIZE]; + isc_result_t result; + + dns_fixedname_init(&fname); + + CHECK(ldapdb_name_fromnode(node, dns_fixedname_name(&fname))); + + result = dns_db_allrdatasets(db, node, version, now, &rds_iter); + if (result == ISC_R_NOTFOUND) { + *isempty = ISC_TRUE; + } else if (result == ISC_R_SUCCESS) { + result = dns_rdatasetiter_first(rds_iter); + if (result == ISC_R_NOMORE) { + *isempty = ISC_TRUE; + result = ISC_R_SUCCESS; + } else if (result == ISC_R_SUCCESS) { + *isempty = ISC_FALSE; + result = ISC_R_SUCCESS; + } else if (result != ISC_R_SUCCESS) { + dns_name_format(dns_fixedname_name(&fname), + buff, DNS_NAME_FORMATSIZE); + log_error_r("dns_rdatasetiter_first() failed during " + "node_isempty() for name '%s'", buff); + } + dns_rdatasetiter_destroy(&rds_iter); + } else { + dns_name_format(dns_fixedname_name(&fname), + buff, DNS_NAME_FORMATSIZE); + log_error_r("dns_db_allrdatasets() failed during " + "node_isempty() for name '%s'", buff); + } + +cleanup: + return result; +} + +/* TODO: Add 'tainted' flag to the LDAP instance if something went wrong. */ static isc_result_t subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, dns_rdataset_t *rdataset, unsigned int options, dns_rdataset_t *newrdataset) { ldapdb_t *ldapdb = (ldapdb_t *) db; + dns_fixedname_t fname; + dns_rdatalist_t *rdlist = NULL; + isc_boolean_t empty_node = ISC_FALSE; + isc_result_t substract_result; + isc_result_t result; REQUIRE(VALID_LDAPDB(ldapdb)); - return dns_db_subtractrdataset(ldapdb->rbtdb, node, version, rdataset, - options, newrdataset); + dns_fixedname_init(&fname); + + result = dns_db_subtractrdataset(ldapdb->rbtdb, node, version, + rdataset, options, newrdataset); + /* DNS_R_NXRRSET mean that whole RRset was deleted. */ + if (result != ISC_R_SUCCESS && result != DNS_R_NXRRSET) + goto cleanup; + + substract_result = result; + /* TODO: Could it create some race-condition? What about unprocessed + * changes in synrepl queue? */ + if (substract_result == DNS_R_NXRRSET) { + CHECK(node_isempty(ldapdb->rbtdb, node, version, 0, + &empty_node)); + } + + result = dns_rdatalist_fromrdataset(rdataset, &rdlist); + INSIST(result == ISC_R_SUCCESS); + CHECK(ldapdb_name_fromnode(node, dns_fixedname_name(&fname))); + CHECK(remove_values_from_ldap(dns_fixedname_name(&fname), ldapdb->ldap_inst, + rdlist, empty_node)); + +cleanup: + if (result == ISC_R_SUCCESS) + result = substract_result; + return result; } -/* !!! Write back to the LDAP is missing. - * Only in-memory RBTDB will be modified. */ +/* This function is usually not called for non-cache DBs, so we don't need to + * care about performance. + * TODO: Add 'tainted' flag to the LDAP instance if something went wrong. */ static isc_result_t deleterdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, dns_rdatatype_t type, dns_rdatatype_t covers) { ldapdb_t *ldapdb = (ldapdb_t *) db; + dns_fixedname_t fname; + isc_boolean_t empty_node; + char attr_name[LDAP_ATTR_FORMATSIZE]; + dns_rdatalist_t fake_rdlist; /* required by remove_values_from_ldap */ + isc_result_t result; REQUIRE(VALID_LDAPDB(ldapdb)); - return dns_db_deleterdataset(ldapdb->rbtdb, node, version, type, - covers); + dns_fixedname_init(&fname); + dns_rdatalist_init(&fake_rdlist); + + result = dns_db_deleterdataset(ldapdb->rbtdb, node, version, type, + covers); + /* DNS_R_UNCHANGED mean that there was no RRset with given type. */ + if (result != ISC_R_SUCCESS) + goto cleanup; + + /* TODO: Could it create some race-condition? What about unprocessed + * changes in synrepl queue? */ + CHECK(node_isempty(ldapdb->rbtdb, node, version, 0, &empty_node)); + CHECK(ldapdb_name_fromnode(node, dns_fixedname_name(&fname))); + + if (empty_node == ISC_TRUE) { + CHECK(remove_entry_from_ldap(dns_fixedname_name(&fname), + ldapdb->ldap_inst)); + } else { + CHECK(rdatatype_to_ldap_attribute(type, attr_name, + LDAP_ATTR_FORMATSIZE)); + CHECK(remove_attr_from_ldap(dns_fixedname_name(&fname), + ldapdb->ldap_inst, attr_name)); + } + +cleanup: + return result; } static isc_boolean_t diff --git a/src/ldap_helper.c b/src/ldap_helper.c index f55a616..fd1c43b 100644 --- a/src/ldap_helper.c +++ b/src/ldap_helper.c @@ -3181,13 +3181,77 @@ write_to_ldap(dns_name_t *owner, ldap_instance_t *ldap_inst, dns_rdatalist_t *rd } isc_result_t -remove_from_ldap(dns_name_t *owner, ldap_instance_t *ldap_inst, +remove_values_from_ldap(dns_name_t *owner, ldap_instance_t *ldap_inst, dns_rdatalist_t *rdlist, isc_boolean_t delete_node) { return modify_ldap_common(owner, ldap_inst, rdlist, LDAP_MOD_DELETE, delete_node); } +isc_result_t +remove_attr_from_ldap(dns_name_t *owner, ldap_instance_t *ldap_inst, + const char *attr) { + LDAPMod *change[2] = { NULL }; + ld_string_t *dn = NULL; + isc_result_t result; + + CHECK(str_new(ldap_inst->mctx, &dn)); + + CHECK(ldap_mod_create(ldap_inst->mctx, &change[0])); + change[0]->mod_op = LDAP_MOD_DELETE; + CHECK(isc_string_copy(change[0]->mod_type, LDAP_ATTR_FORMATSIZE, attr)); + change[0]->mod_vals.modv_strvals = NULL; /* delete all values from given attribute */ + + CHECK(dnsname_to_dn(ldap_inst->zone_register, + owner, dn)); + CHECK(ldap_modify_do(ldap_inst, str_buf(dn), change, ISC_FALSE)); + +cleanup: + ldap_mod_free(ldap_inst->mctx, &change[0]); + str_destroy(&dn); + return result; +} + + +isc_result_t +remove_entry_from_ldap(dns_name_t *owner, ldap_instance_t *ldap_inst) { + ldap_connection_t *ldap_conn = NULL; + ld_string_t *dn = NULL; + int ret; + isc_result_t result; + + CHECK(str_new(ldap_inst->mctx, &dn)); + CHECK(dnsname_to_dn(ldap_inst->zone_register, owner, dn)); + log_debug(2, "deleting whole node: '%s'", str_buf(dn)); + + CHECK(ldap_pool_getconnection(ldap_inst->pool, &ldap_conn)); + if (ldap_conn->handle == NULL) { + /* + * handle can be NULL when the first connection to LDAP wasn't + * successful + * TODO: handle this case inside ldap_pool_getconnection()? + */ + CHECK(ldap_connect(ldap_inst, ldap_conn, ISC_FALSE)); + } + ret = ldap_delete_ext_s(ldap_conn->handle, str_buf(dn), NULL, NULL); + result = (ret == LDAP_SUCCESS) ? ISC_R_SUCCESS : ISC_R_FAILURE; + if (ret == LDAP_SUCCESS) + goto cleanup; + + LDAP_OPT_CHECK(ldap_get_option(ldap_conn->handle, LDAP_OPT_RESULT_CODE, + &ret), "remove_entry_from_ldap failed to obtain " + "ldap error code"); + + if (result != ISC_R_SUCCESS) + log_ldap_error(ldap_conn->handle, "while deleting entry '%s'", + str_buf(dn)); +cleanup: + ldap_pool_putconnection(ldap_inst->pool, &ldap_conn); + str_destroy(&dn); + return result; +} + + static isc_result_t ldap_pool_create(isc_mem_t *mctx, unsigned int connections, ldap_pool_t **poolp) { diff --git a/src/ldap_helper.h b/src/ldap_helper.h index 06e80fd..7f85daf 100644 --- a/src/ldap_helper.h +++ b/src/ldap_helper.h @@ -86,9 +86,16 @@ ldap_delete_zone2(ldap_instance_t *inst, dns_name_t *name, isc_boolean_t lock, /* Functions for writing to LDAP. */ isc_result_t write_to_ldap(dns_name_t *owner, ldap_instance_t *ldap_inst, dns_rdatalist_t *rdlist) ATTR_NONNULLS; -isc_result_t remove_from_ldap(dns_name_t *owner, ldap_instance_t *ldap_inst, +isc_result_t remove_values_from_ldap(dns_name_t *owner, ldap_instance_t *ldap_inst, dns_rdatalist_t *rdlist, isc_boolean_t delete_node) ATTR_NONNULLS; +isc_result_t +remove_attr_from_ldap(dns_name_t *owner, ldap_instance_t *ldap_inst, + const char *attr) ATTR_NONNULLS; + +isc_result_t +remove_entry_from_ldap(dns_name_t *owner, ldap_instance_t *ldap_inst) ATTR_NONNULLS; + settings_set_t * ldap_instance_getsettings_local(ldap_instance_t *ldap_inst) ATTR_NONNULLS; #endif /* !_LD_LDAP_HELPER_H_ */