Hello, Clean up PTR record synchronization code and make it more robust.
PTR record synchronization was split to smaller functions. Input validation, error handling and logging was improved significantly. -- Petr^2 Spacek
From f121d8ee2ba441bc361300e2b781603372e85ed8 Mon Sep 17 00:00:00 2001 From: Petr Spacek <pspa...@redhat.com> Date: Tue, 16 Apr 2013 16:10:09 +0200 Subject: [PATCH] Clean up PTR record synchronization code and make it more robust. PTR record synchronization was split to smaller functions. Input validation, error handling and logging was improved significantly. Signed-off-by: Petr Spacek <pspa...@redhat.com> --- src/ldap_helper.c | 507 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 342 insertions(+), 165 deletions(-) diff --git a/src/ldap_helper.c b/src/ldap_helper.c index 21dbed1b81af74cd3c8f28e3a78db7a878cfdeb4..155f04a4e4a7b904bfbd9ee534a6732560cb0a79 100644 --- a/src/ldap_helper.c +++ b/src/ldap_helper.c @@ -2830,35 +2830,360 @@ cleanup: #undef SET_LDAP_MOD } + +#define SYNCPTR_PREF "PTR record synchronization " +#define SYNCPTR_FMTPRE SYNCPTR_PREF "(%s) for A/AAAA '%s' " +#define SYNCPTR_FMTPOST ldap_modop_str(mod_op), a_name_str + +static const char * +ldap_modop_str(unsigned int mod_op) { + static const char add[] = "addition"; + static const char del[] = "deletion"; + + switch (mod_op) { + case LDAP_MOD_ADD: + return add; + + case LDAP_MOD_DELETE: + return del; + + default: + INSIST("unsupported LDAP mod_op" == NULL); + return NULL; + } +} + +static void +append_trailing_dot(char *str, unsigned int size) { + unsigned int length = strlen(str); + if (str[length] != '.') { + REQUIRE(length + 1 < size); + str[length] = '.'; + str[length+1] = '\0'; + } +} + +static isc_result_t +ldap_find_ptr(ldap_instance_t *ldap_inst, const char *ip_str, + dns_name_t *ptr_name, ld_string_t *ptr_dn, + dns_name_t *zone_name) { + isc_result_t result; + isc_mem_t *mctx = ldap_inst->mctx; + + in_addr_t ip; + + /* Get string with IP address from change request + * and convert it to in_addr structure. */ + if ((ip = inet_addr(ip_str)) == 0) { + log_bug(SYNCPTR_PREF " could not convert IP address " + "from string '%s'", ip_str); + CLEANUP_WITH(ISC_R_UNEXPECTED); + } + + /* Use internal net address representation. */ + isc_netaddr_t isc_ip; + /* Only copy data to isc_ip stucture. */ + isc_netaddr_fromin(&isc_ip,(struct in_addr *) &ip); + + /* + * Convert IP address to PTR record. + * + * @example + * 192.168.0.1 -> 1.0.168.192.in-addr.arpa + * + * @todo Check if it works for IPv6 correctly. + */ + CHECK(dns_byaddr_createptrname2(&isc_ip, 0, ptr_name)); + + /* Get LDAP entry indentifier. */ + CHECK(dnsname_to_dn(ldap_inst->zone_register, ptr_name, ptr_dn)); + + /* + * @example + * owner_dn_ptr = "idnsName=100.0.168, idnsname=192.in-addr.arpa,cn=dns,$SUFFIX" + * owner_zone_dn_ptr = "idnsname=192.in-addr.arpa,cn=dns,$SUFFIX" + */ + char *owner_zone_dn_ptr = strstr(str_buf(ptr_dn),", ") + 1; + + /* Get attribute "idnsAllowDynUpdate" for reverse zone or use default. */ + CHECK(dn_to_dnsname(mctx, owner_zone_dn_ptr, zone_name, NULL)); + +cleanup: + return result; +} + +/** + * Check if PTR record's value in LDAP == name of the modified A/AAAA record. + * Update will be refused if the PTR name contains multiple PTR records or + * if the value in LDAP != expected name. + * + * @param[in] a_name Name of modified A/AAAA record. + * @param[in] a_name_str Name of modified A/AAAA record as NUL terminated string. + * @param[in] ptr_name Name of PTR record generated from IP address in A/AAAA. + * @param[in] mod_op LDAP_MOD_DELETE if A/AAAA record is being deleted + * or LDAP_MOD_ADD if A/AAAA record is being added. + * + * @retval ISC_R_IGNORE A and PTR records match, no change is required. + * @retval ISC_R_SUCCESS Prerequisites fulfilled, update is allowed. + * @retval other Errors + * + * @code + * ** A record deletion ** + * ; nsupdate command: + * update delete www.example.com. IN A 192.0.2.1 + * + * ; PTR update will be allowed if the zone contains following data: + * www.example.com. A 192.0.2.1 + * 1.2.0.192.in-addr.arpa. PTR www.example.com. + + * ; PTR update will not be allowed if the zone contains following data: + * www.example.com. A 192.0.2.1 + * 1.2.0.192.in-addr.arpa. PTR mail.example.com. + * @endcode + * + * @code + * ** A record addition ** + * ; nsupdate command: + * update add www.example.com. 3600 IN A 192.0.2.1 + * + * ; PTR update will be allowed if the zone does not contain A and PTR record. + * + * ; PTR update will not be allowed if the zone contains following data: + * 1.2.0.192.in-addr.arpa. PTR mail.example.com. + * @endcode + */ +static isc_result_t +ldap_sync_ptr_validate(ldap_instance_t *ldap_inst, dns_name_t *a_name, + const char *a_name_str, dns_name_t *ptr_name, + int mod_op) { + isc_result_t result; + isc_mem_t *mctx = ldap_inst->mctx; + + char ptr_name_str[DNS_NAME_FORMATSIZE+1]; + isc_boolean_t ptr_found; + dns_rdata_ptr_t ptr_rdata; + char ptr_rdata_str[DNS_NAME_FORMATSIZE+1]; + isc_boolean_t ptr_a_equal; + + ldapdb_rdatalist_t ldap_rdlist; + dns_rdatalist_t *ptr_rdlist = NULL; + + ISC_LIST_INIT(ldap_rdlist); + + REQUIRE(mod_op == LDAP_MOD_DELETE || mod_op == LDAP_MOD_ADD); + REQUIRE(a_name_str != NULL); + + /* Find PTR entry in LDAP. */ + ptr_found = ISC_FALSE; + result = ldapdb_rdatalist_get(mctx, ldap_inst, ptr_name, + NULL, &ldap_rdlist); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + log_error_r(SYNCPTR_FMTPRE "failed in ldapdb_rdatalist_get()", + SYNCPTR_FMTPOST); + goto cleanup; + } + + /* Find the value of PTR entry. */ + if (result == ISC_R_SUCCESS) { + result = ldapdb_rdatalist_findrdatatype(&ldap_rdlist, + dns_rdatatype_ptr, + &ptr_rdlist); + if (result == ISC_R_SUCCESS && HEAD(ptr_rdlist->rdata) != NULL) { + if (HEAD(ptr_rdlist->rdata) != TAIL(ptr_rdlist->rdata)) { + dns_name_format(ptr_name, ptr_name_str, + DNS_NAME_FORMATSIZE); + append_trailing_dot(ptr_name_str, + sizeof(ptr_name_str)); + log_error(SYNCPTR_FMTPRE + "failed: multiple PTR records under " + "name '%s' are not supported", + SYNCPTR_FMTPOST, ptr_name_str); + CLEANUP_WITH(ISC_R_NOTIMPLEMENTED); + } + dns_rdata_tostruct(HEAD(ptr_rdlist->rdata), &ptr_rdata, + NULL); + + ptr_found = ISC_TRUE; + + /* Compare PTR value with name of the A/AAAA record. */ + if (dns_name_isabsolute(a_name) && + dns_name_isabsolute(&ptr_rdata.ptr) && + dns_name_equal(&ptr_rdata.ptr, a_name)) { + ptr_a_equal = ISC_TRUE; + } else { + ptr_a_equal = ISC_FALSE; + dns_name_format(ptr_name, ptr_name_str, + DNS_NAME_FORMATSIZE); + append_trailing_dot(ptr_name_str, + sizeof(ptr_name_str)); + dns_name_format(&ptr_rdata.ptr, ptr_rdata_str, + DNS_NAME_FORMATSIZE); + append_trailing_dot(ptr_rdata_str, + sizeof(ptr_rdata_str)); + } + } + } + + if (mod_op == LDAP_MOD_DELETE) { + if (ptr_found == ISC_FALSE) { + log_debug(3, SYNCPTR_FMTPRE "skipped: no PTR records " + "found", SYNCPTR_FMTPOST); + CLEANUP_WITH(ISC_R_IGNORE); + + } else if (ptr_a_equal == ISC_FALSE) { + log_error(SYNCPTR_FMTPRE "failed: " + "existing PTR record '%s' contains unexpected " + "value '%s' (value '%s' expected)", + SYNCPTR_FMTPOST, ptr_name_str, ptr_rdata_str, + a_name_str); + CLEANUP_WITH(ISC_R_UNEXPECTEDTOKEN); + } + + } else if (mod_op == LDAP_MOD_ADD && ptr_found == ISC_TRUE) { + if (ptr_a_equal == ISC_TRUE) { + log_debug(3, SYNCPTR_FMTPRE "skipped: PTR record with" + "desired value is already present", + SYNCPTR_FMTPOST); + CLEANUP_WITH(ISC_R_IGNORE); + + } else { + log_error(SYNCPTR_FMTPRE "failed: " + "existing PTR record '%s' contains unexpected " + "value '%s' (value '%s' or no value expected)", + SYNCPTR_FMTPOST, ptr_name_str, ptr_rdata_str, + a_name_str); + CLEANUP_WITH(DNS_R_SINGLETON); + } + } + + result = ISC_R_SUCCESS; + +cleanup: + ldapdb_rdatalist_destroy(mctx, &ldap_rdlist); + + return result; +} + +static isc_result_t +ldap_sync_ptr(ldap_instance_t *ldap_inst, dns_name_t *a_name, + const char *ip_str, int mod_op, isc_boolean_t delete_node) { + isc_result_t result; + isc_mem_t *mctx = ldap_inst->mctx; + + char **vals = NULL; + + char a_name_str[DNS_NAME_FORMATSIZE+1]; + + ld_string_t *ptr_dn = NULL; + struct dns_fixedname ptr_name; + LDAPMod *change[2] = { NULL }; + + dns_name_t zone_name; + ldap_cache_t *zone_cache = NULL; + settings_set_t *zone_settings = NULL; + isc_boolean_t zone_dyn_update; + + dns_name_init(&zone_name, NULL); + dns_fixedname_init(&ptr_name); + CHECK(str_new(mctx, &ptr_dn)); + + /** + * Get string representation of PTR record value. + * @code + * a_name_str = "host.example.com." + * @endcode + */ + dns_name_format(a_name, a_name_str, DNS_NAME_FORMATSIZE); + append_trailing_dot(a_name_str, sizeof(a_name_str)); + + result = ldap_find_ptr(ldap_inst, ip_str, dns_fixedname_name(&ptr_name), + ptr_dn, &zone_name); + if (result != ISC_R_SUCCESS) { + log_error_r(SYNCPTR_FMTPRE "refused: " + "unable to find active reverse zone " + "for IP address '%s'", SYNCPTR_FMTPOST, ip_str); + CLEANUP_WITH(ISC_R_NOTFOUND); + } + + CHECK(zr_get_zone_settings(ldap_inst->zone_register, &zone_name, + &zone_settings)); + CHECK(setting_get_bool("dyn_update", zone_settings, &zone_dyn_update)); + if (!zone_dyn_update) { + char zone_name_str[DNS_NAME_FORMATSIZE]; + dns_name_format(&zone_name, zone_name_str, DNS_NAME_FORMATSIZE); + log_error(SYNCPTR_FMTPRE "refused: " + "IP address '%s' belongs to reverse zone '%s' " + "and dynamic updates are not allowed for that zone", + SYNCPTR_FMTPOST, ip_str, zone_name_str); + CLEANUP_WITH(ISC_R_NOPERM); + } + + result = ldap_sync_ptr_validate(ldap_inst, a_name, a_name_str, + dns_fixedname_name(&ptr_name), mod_op); + if (result == ISC_R_IGNORE) + CLEANUP_WITH(ISC_R_SUCCESS); + else if (result != ISC_R_SUCCESS) + CLEANUP_WITH(DNS_R_SERVFAIL); + + /* Fill the LDAPMod change structure up. */ + CHECKED_MEM_GET_PTR(mctx, change[0]); + ZERO_PTR(change[0]); + + /* Do the same action what has been done with A/AAAA record. */ + change[0]->mod_op = mod_op; + char *attr_name; + const char *attr_name_c; + CHECK(rdatatype_to_ldap_attribute(dns_rdatatype_ptr, &attr_name_c)); + + DE_CONST(attr_name_c, attr_name); + change[0]->mod_type = attr_name; + + CHECKED_MEM_ALLOCATE(mctx, vals, 2 * sizeof(char *)); + memset(vals, 0, 2 * sizeof(char *)); + change[0]->mod_values = vals; + + CHECKED_MEM_ALLOCATE(mctx, vals[0], strlen(a_name_str) + 1); + memcpy(vals[0], a_name_str, strlen(a_name_str) + 1); + + /* Modify PTR record. */ + CHECK(ldap_modify_do(ldap_inst, str_buf(ptr_dn), + change, delete_node)); + CHECK(zr_get_zone_cache(ldap_inst->zone_register, + dns_fixedname_name(&ptr_name), &zone_cache)); + CHECK(discard_from_cache(zone_cache, dns_fixedname_name(&ptr_name))); + +cleanup: + if (dns_name_dynamic(&zone_name)) + dns_name_free(&zone_name, mctx); + str_destroy(&ptr_dn); + if (change[0] != NULL) free_ldapmod(mctx, &change[0]); + + return result; +} +#undef SYNCPTR_PREF +#undef SYNCPTR_FMTPRE +#undef SYNCPTR_FMTPOST + static isc_result_t modify_ldap_common(dns_name_t *owner, ldap_instance_t *ldap_inst, dns_rdatalist_t *rdlist, int mod_op, isc_boolean_t delete_node) { isc_result_t result; isc_mem_t *mctx = ldap_inst->mctx; ld_string_t *owner_dn = NULL; LDAPMod *change[3] = { NULL }; - LDAPMod *change_ptr = NULL; ldap_cache_t *cache = NULL; - isc_boolean_t zone_dyn_update; isc_boolean_t zone_sync_ptr; - ld_string_t *owner_dn_ptr = NULL; - ld_string_t *str_ptr = NULL; - ldapdb_rdatalist_t rdlist_search; - dns_rdatalist_t *rdlist_ptr = NULL; char **vals = NULL; dns_name_t zone_name; - struct dns_fixedname ptr_name; char *zone_dn = NULL; settings_set_t *zone_settings = NULL; /* * Find parent zone entry and check if Dynamic Update is allowed. * @todo Try the cache first and improve split: SOA records are problematic. */ - ISC_LIST_INIT(rdlist_search); dns_name_init(&zone_name, NULL); - dns_fixedname_init(&ptr_name); CHECK(str_new(mctx, &owner_dn)); CHECK(dnsname_to_dn(ldap_inst->zone_register, owner, owner_dn)); @@ -2876,7 +3201,8 @@ modify_ldap_common(dns_name_t *owner, ldap_instance_t *ldap_inst, &zone_settings); if (result != ISC_R_SUCCESS) { if (result == ISC_R_NOTFOUND) - log_debug(3, "active zone '%s' not found", zone_dn); + log_debug(3, "update refused: " + "active zone '%s' not found", zone_dn); CLEANUP_WITH(DNS_R_NOTAUTH); } @@ -2898,7 +3224,7 @@ modify_ldap_common(dns_name_t *owner, ldap_instance_t *ldap_inst, /* for now always replace the ttl on add */ CHECK(ldap_rdttl_to_ldapmod(mctx, rdlist, &change[1])); } - + CHECK(ldap_modify_do(ldap_inst, str_buf(owner_dn), change, delete_node)); /* Keep the PTR of corresponding A/AAAA record synchronized. */ @@ -2910,169 +3236,20 @@ modify_ldap_common(dns_name_t *owner, ldap_instance_t *ldap_inst, CHECK(setting_get_bool("sync_ptr", zone_settings, &zone_sync_ptr)); if (!zone_sync_ptr) { - log_debug(3, "sync PTR is not allowed in zone '%s'", zone_dn); + log_debug(3, "sync PTR is disabled for zone '%s'", zone_dn); CLEANUP_WITH(ISC_R_SUCCESS); } - log_debug(3, "sync PTR is allowed for zone '%s'", zone_dn); + log_debug(3, "sync PTR is enabled for zone '%s'", zone_dn); - /* Get string with IP address from change request - * and convert it to in_addr structure. */ - in_addr_t ip; - if ((ip = inet_addr(change[0]->mod_values[0])) == 0) { - log_bug("Could not convert IP address from string '%s'.", - change[0]->mod_values[0]); - result = ISC_R_FAILURE; - goto cleanup; - } - - /* Use internal net address representation. */ - isc_netaddr_t isc_ip; - /* Only copy data to isc_ip stucture. */ - isc_netaddr_fromin(&isc_ip,(struct in_addr *) &ip); - - /* - * Convert IP address to PTR record. - * - * @example - * 192.168.0.1 -> 1.0.168.192.in-addr.arpa - * - * @todo Check if it works for IPv6 correctly. - */ - CHECK(dns_byaddr_createptrname2(&isc_ip, 0, dns_fixedname_name(&ptr_name))); - - /* Find PTR entry in LDAP. */ - result = ldapdb_rdatalist_get(mctx, ldap_inst, dns_fixedname_name(&ptr_name), - NULL, &rdlist_search); - - /* Check the value of PTR entry. */ - if (mod_op == LDAP_MOD_DELETE && result == ISC_R_SUCCESS) { - result = ldapdb_rdatalist_findrdatatype(&rdlist_search, - dns_rdatatype_ptr, &rdlist_ptr); - } - - if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { - log_error_r("can not synchronize PTR record, ldapdb_rdatalist_get"); - CLEANUP_WITH(ISC_R_FAILURE); /* Synchronization required: report error. */ - } - - /* - * Do not overwrite old record and delete only existing record. - */ - if ((result == ISC_R_SUCCESS && mod_op == LDAP_MOD_ADD) || - (result == ISC_R_NOTFOUND && mod_op == LDAP_MOD_DELETE)) { - log_debug(2,"Can not synchronize PTR record for A/AAAA one (%s) - record %s.", - str_buf(owner_dn), - ((mod_op == LDAP_MOD_ADD)?"already exists":"not found")); - result = ISC_R_SUCCESS; /* @todo: More checks for ADD operation. */ - goto cleanup; - } - - /* Get LDAP entry indentifier. */ - CHECK(str_new(mctx, &owner_dn_ptr)); - CHECK(dnsname_to_dn(ldap_inst->zone_register, dns_fixedname_name(&ptr_name), - owner_dn_ptr)); - - /* - * @example - * owner_dn_ptr = "idnsName=100.0.168, idnsname=192.in-addr.arpa,cn=dns,$SUFFIX" - * owner_zone_dn_ptr = "idnsname=192.in-addr.arpa,cn=dns,$SUFFIX" - */ - char *owner_zone_dn_ptr = strstr(str_buf(owner_dn_ptr),", ") + 1; - - /* Get attribute "idnsAllowDynUpdate" for reverse zone or use default. */ - dns_name_free(&zone_name, mctx); - dns_name_init(&zone_name, NULL); - CHECK(dn_to_dnsname(mctx, owner_zone_dn_ptr, &zone_name, NULL)); - - zone_settings = NULL; - result = zr_get_zone_settings(ldap_inst->zone_register, &zone_name, - &zone_settings); - if (result != ISC_R_SUCCESS) { - if (result == ISC_R_NOTFOUND) - log_debug(3, "active zone '%s' not found", zone_dn); - CLEANUP_WITH(DNS_R_SERVFAIL); - } - - CHECK(setting_get_bool("dyn_update", zone_settings, &zone_dyn_update)); - if (!zone_dyn_update) { - log_error("dynamic update is not allowed in zone " - "'%s'", zone_dn); - CLEANUP_WITH(ISC_R_NOPERM); - } - - /* - * Get string representation of PTR record value. - * - * @example str_ptr = "host.example.com." - */ - CHECK(str_new(mctx, &str_ptr)); - CHECK(dn_to_text(str_buf(owner_dn), str_ptr, NULL)); - - /* - * Delete only when PTR record's value == A/AAAA record's key. - * - * @example - * - * www.example.com. A 192.168.0.100 - * ; PTR record can be synchronized. - * 100.0.168.192.in-addr.arpa. PTR www.example.com. - * ; PTR record can NOT be synchronized. - * 100.0.168.192.in-addr.arpa. PTR not.valid.com. - * - */ - if (mod_op == LDAP_MOD_DELETE) { - CHECK(ldap_rdata_to_char_array(mctx, HEAD(rdlist_ptr->rdata), &vals)); - if (vals != NULL && vals[0] != NULL && strcmp(vals[0], str_buf(str_ptr)) != 0) { - log_debug(3,"Cannot delete PTR record, unexpected value found " - "(expected \"%s\")\n", str_buf(str_ptr)); - result = ISC_R_FAILURE; - goto cleanup; - } - } - - /* Fill the LDAPMod change structure up. */ - char **vals = NULL; - CHECKED_MEM_GET_PTR(mctx, change_ptr); - ZERO_PTR(change_ptr); - - /* Do the same action what has been done with A/AAAA record. */ - change_ptr->mod_op = mod_op; - char *attr_name; - const char *attr_name_c; - CHECK(rdatatype_to_ldap_attribute(dns_rdatatype_ptr, &attr_name_c)); - - DE_CONST(attr_name_c, attr_name); - change_ptr->mod_type = attr_name; - - CHECKED_MEM_ALLOCATE(mctx, vals, 2 * sizeof(char *)); - memset(vals, 0, 2 * sizeof(char *)); - change_ptr->mod_values = vals; - - CHECKED_MEM_ALLOCATE(mctx, vals[0], str_len(str_ptr) + 1); - memcpy(vals[0], str_buf(str_ptr), str_len(str_ptr) + 1); - - /* Switch pointers and free the old memory. */ - free_ldapmod(mctx, &change[0]); - change[0] = change_ptr; - change_ptr = NULL; - - /* Modify PTR record. */ - CHECK(ldap_modify_do(ldap_inst, str_buf(owner_dn_ptr), - change, delete_node)); - cache = NULL; - CHECK(zr_get_zone_cache(ldap_inst->zone_register, - dns_fixedname_name(&ptr_name), &cache)); - CHECK(discard_from_cache(cache, dns_fixedname_name(&ptr_name))); + result = ldap_sync_ptr(ldap_inst, owner, change[0]->mod_values[0], + mod_op, delete_node); } cleanup: - str_destroy(&owner_dn_ptr); str_destroy(&owner_dn); - str_destroy(&str_ptr); + free_ldapmod(mctx, &change[0]); free_ldapmod(mctx, &change[1]); - if (change_ptr != NULL) free_ldapmod(mctx, &change_ptr); - ldapdb_rdatalist_destroy(mctx, &rdlist_search); free_char_array(mctx, &vals); dns_name_free(&zone_name, mctx); -- 1.7.11.7
_______________________________________________ Freeipa-devel mailing list Freeipa-devel@redhat.com https://www.redhat.com/mailman/listinfo/freeipa-devel