diff options
Diffstat (limited to 'server/ddns.c')
-rw-r--r-- | server/ddns.c | 1764 |
1 files changed, 1764 insertions, 0 deletions
diff --git a/server/ddns.c b/server/ddns.c new file mode 100644 index 0000000..b1683c3 --- /dev/null +++ b/server/ddns.c @@ -0,0 +1,1764 @@ +/* ddns.c + + Dynamic DNS updates. */ + +/* + * Copyright (c) 2009-2011 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004-2007 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2000-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + * This software has been donated to Internet Systems Consortium + * by Damien Neil of Nominum, Inc. + * + * To learn more about Internet Systems Consortium, see + * ``https://www.isc.org/''. To learn more about Nominum, Inc., see + * ``http://www.nominum.com''. + */ + +#include "dhcpd.h" +#include "dst/md5.h" +#include <dns/result.h> + +#ifdef NSUPDATE + +static void ddns_fwd_srv_connector(struct lease *lease, + struct iasubopt *lease6, + struct binding_scope **inscope, + dhcp_ddns_cb_t *ddns_cb, + isc_result_t eresult); + +/* DN: No way of checking that there is enough space in a data_string's + buffer. Be certain to allocate enough! + TL: This is why the expression evaluation code allocates a *new* + data_string. :') */ +static void data_string_append (struct data_string *ds1, + struct data_string *ds2) +{ + memcpy (ds1 -> buffer -> data + ds1 -> len, + ds2 -> data, + ds2 -> len); + ds1 -> len += ds2 -> len; +} + + +/* Determine what, if any, forward and reverse updates need to be + * performed, and carry them through. + */ +int +ddns_updates(struct packet *packet, struct lease *lease, struct lease *old, + struct iasubopt *lease6, struct iasubopt *old6, + struct option_state *options) +{ + unsigned long ddns_ttl = DEFAULT_DDNS_TTL; + struct data_string ddns_hostname; + struct data_string ddns_domainname; + struct data_string old_ddns_fwd_name; + struct data_string ddns_fwd_name; + //struct data_string ddns_rev_name; + struct data_string ddns_dhcid; + struct binding_scope **scope = NULL; + //struct iaddr addr; + struct data_string d1; + struct option_cache *oc; + int s1, s2; + int result = 0; + isc_result_t rcode1 = ISC_R_SUCCESS; + int server_updates_a = 1; + //int server_updates_ptr = 1; + struct buffer *bp = (struct buffer *)0; + int ignorep = 0, client_ignorep = 0; + int rev_name_len; + int i; + + dhcp_ddns_cb_t *ddns_cb; + int do_remove = 0; + + if (ddns_update_style != 2) + return 0; + + /* + * sigh, I want to cancel any previous udpates before we do anything + * else but this means we need to deal with the lease vs lease6 + * question twice. + * If there is a ddns request already outstanding cancel it. + */ + + if (lease != NULL) { + if ((old != NULL) && (old->ddns_cb != NULL)) { + ddns_cancel(old->ddns_cb); + old->ddns_cb = NULL; + } + } else if (lease6 != NULL) { + if ((old6 != NULL) && (old6->ddns_cb != NULL)) { + ddns_cancel(old6->ddns_cb); + old6->ddns_cb = NULL; + } + } else { + log_fatal("Impossible condition at %s:%d.", MDL); + /* Silence compiler warnings. */ + result = 0; + return(0); + } + + /* allocate our control block */ + ddns_cb = ddns_cb_alloc(MDL); + if (ddns_cb == NULL) { + return(0); + } + /* assume that we shall update both the A and ptr records */ + ddns_cb->flags = DDNS_UPDATE_ADDR | DDNS_UPDATE_PTR; + + if (lease != NULL) { + scope = &(lease->scope); + ddns_cb->address = lease->ip_addr; + } else if (lease6 != NULL) { + scope = &(lease6->scope); + memcpy(ddns_cb->address.iabuf, lease6->addr.s6_addr, 16); + ddns_cb->address.len = 16; + } + + memset (&d1, 0, sizeof(d1)); + memset (&ddns_hostname, 0, sizeof (ddns_hostname)); + memset (&ddns_domainname, 0, sizeof (ddns_domainname)); + memset (&old_ddns_fwd_name, 0, sizeof (ddns_fwd_name)); + memset (&ddns_fwd_name, 0, sizeof (ddns_fwd_name)); + //memset (&ddns_rev_name, 0, sizeof (ddns_rev_name)); + memset (&ddns_dhcid, 0, sizeof (ddns_dhcid)); + + /* If we are allowed to accept the client's update of its own A + record, see if the client wants to update its own A record. */ + if (!(oc = lookup_option(&server_universe, options, + SV_CLIENT_UPDATES)) || + evaluate_boolean_option_cache(&client_ignorep, packet, lease, NULL, + packet->options, options, scope, + oc, MDL)) { + /* If there's no fqdn.no-client-update or if it's + nonzero, don't try to use the client-supplied + XXX */ + if (!(oc = lookup_option (&fqdn_universe, packet -> options, + FQDN_SERVER_UPDATE)) || + evaluate_boolean_option_cache(&ignorep, packet, lease, + NULL, packet->options, + options, scope, oc, MDL)) + goto noclient; + /* Win98 and Win2k will happily claim to be willing to + update an unqualified domain name. */ + if (!(oc = lookup_option (&fqdn_universe, packet -> options, + FQDN_DOMAINNAME))) + goto noclient; + if (!(oc = lookup_option (&fqdn_universe, packet -> options, + FQDN_FQDN)) || + !evaluate_option_cache(&ddns_fwd_name, packet, lease, + NULL, packet->options, + options, scope, oc, MDL)) + goto noclient; + ddns_cb->flags &= ~DDNS_UPDATE_ADDR; + server_updates_a = 0; + goto client_updates; + } + noclient: + /* If do-forward-updates is disabled, this basically means don't + do an update unless the client is participating, so if we get + here and do-forward-updates is disabled, we can stop. */ + if ((oc = lookup_option (&server_universe, options, + SV_DO_FORWARD_UPDATES)) && + !evaluate_boolean_option_cache(&ignorep, packet, lease, + NULL, packet->options, + options, scope, oc, MDL)) { + goto out; + } + + /* If it's a static lease, then don't do the DNS update unless we're + specifically configured to do so. If the client asked to do its + own update and we allowed that, we don't do this test. */ + /* XXX: note that we cannot detect static DHCPv6 leases. */ + if ((lease != NULL) && (lease->flags & STATIC_LEASE)) { + if (!(oc = lookup_option(&server_universe, options, + SV_UPDATE_STATIC_LEASES)) || + !evaluate_boolean_option_cache(&ignorep, packet, lease, + NULL, packet->options, + options, scope, oc, MDL)) + goto out; + } + + /* + * Compute the name for the A record. + */ + oc = lookup_option(&server_universe, options, SV_DDNS_HOST_NAME); + if (oc) + s1 = evaluate_option_cache(&ddns_hostname, packet, lease, + NULL, packet->options, + options, scope, oc, MDL); + else + s1 = 0; + + oc = lookup_option(&server_universe, options, SV_DDNS_DOMAIN_NAME); + if (oc) + s2 = evaluate_option_cache(&ddns_domainname, packet, lease, + NULL, packet->options, + options, scope, oc, MDL); + else + s2 = 0; + + if (s1 && s2) { + if (ddns_hostname.len + ddns_domainname.len > 253) { + log_error ("ddns_update: host.domain name too long"); + + goto out; + } + + buffer_allocate (&ddns_fwd_name.buffer, + ddns_hostname.len + ddns_domainname.len + 2, + MDL); + if (ddns_fwd_name.buffer) { + ddns_fwd_name.data = ddns_fwd_name.buffer->data; + data_string_append (&ddns_fwd_name, &ddns_hostname); + ddns_fwd_name.buffer->data[ddns_fwd_name.len] = '.'; + ddns_fwd_name.len++; + data_string_append (&ddns_fwd_name, &ddns_domainname); + ddns_fwd_name.buffer->data[ddns_fwd_name.len] ='\0'; + ddns_fwd_name.terminated = 1; + } + } + client_updates: + + /* See if there's a name already stored on the lease. */ + if (find_bound_string(&old_ddns_fwd_name, *scope, "ddns-fwd-name")) { + /* If there is, see if it's different. */ + if (old_ddns_fwd_name.len != ddns_fwd_name.len || + memcmp (old_ddns_fwd_name.data, ddns_fwd_name.data, + old_ddns_fwd_name.len)) { + /* + * If the name is different, mark the old record + * for deletion and continue getting the new info. + */ + do_remove = 1; + goto in; + } + + /* See if there's a DHCID on the lease, and if not + * then potentially look for 'on events' for ad-hoc ddns. + */ + if (!find_bound_string(&ddns_dhcid, *scope, "ddns-txt") && + (old != NULL)) { + /* If there's no DHCID, the update was probably + done with the old-style ad-hoc DDNS updates. + So if the expiry and release events look like + they're the same, run them. This should delete + the old DDNS data. */ + if (old -> on_expiry == old -> on_release) { + execute_statements(NULL, NULL, lease, NULL, + NULL, NULL, scope, + old->on_expiry); + if (old -> on_expiry) + executable_statement_dereference + (&old -> on_expiry, MDL); + if (old -> on_release) + executable_statement_dereference + (&old -> on_release, MDL); + /* Now, install the DDNS data the new way. */ + goto in; + } + } else + data_string_forget(&ddns_dhcid, MDL); + + /* See if the administrator wants to do updates even + in cases where the update already appears to have been + done. */ + if (!(oc = lookup_option(&server_universe, options, + SV_UPDATE_OPTIMIZATION)) || + evaluate_boolean_option_cache(&ignorep, packet, lease, + NULL, packet->options, + options, scope, oc, MDL)) { + result = 1; + goto noerror; + } + /* If there's no "ddns-fwd-name" on the lease record, see if + * there's a ddns-client-fqdn indicating a previous client + * update (if it changes, we need to adjust the PTR). + */ + } else if (find_bound_string(&old_ddns_fwd_name, *scope, + "ddns-client-fqdn")) { + /* If the name is not different, no need to update + the PTR record. */ + if (old_ddns_fwd_name.len == ddns_fwd_name.len && + !memcmp (old_ddns_fwd_name.data, ddns_fwd_name.data, + old_ddns_fwd_name.len) && + (!(oc = lookup_option(&server_universe, options, + SV_UPDATE_OPTIMIZATION)) || + evaluate_boolean_option_cache(&ignorep, packet, lease, + NULL, packet->options, + options, scope, oc, MDL))) { + goto noerror; + } + } + in: + + /* If we don't have a name that the client has been assigned, we + can just skip all this. */ + + if ((!ddns_fwd_name.len) || (ddns_fwd_name.len > 255)) { + if (ddns_fwd_name.len > 255) { + log_error ("client provided fqdn: too long"); + } + + /* If desired do the removals */ + if (do_remove != 0) { + (void) ddns_removals(lease, lease6, NULL); + } + goto out; + } + + /* + * Compute the RR TTL. + * + * We have two ways of computing the TTL. + * The old behavior was to allow for the customer to set up + * the option or to default things. For v4 this was 1/2 + * of the lease time, for v6 this was DEFAULT_DDNS_TTL. + * The new behavior continues to allow the customer to set + * up an option but the defaults are a little different. + * We now use 1/2 of the (preferred) lease time for both + * v4 and v6 and cap them at a maximum value. + * If the customer chooses to use an experession that references + * part of the lease the v6 value will be the default as there + * isn't a lease available for v6. + */ + + ddns_ttl = DEFAULT_DDNS_TTL; + if (lease != NULL) { + if (lease->ends <= cur_time) { + ddns_ttl = 0; + } else { + ddns_ttl = (lease->ends - cur_time)/2; + } + } +#ifndef USE_OLD_DDNS_TTL + else if (lease6 != NULL) { + ddns_ttl = lease6->prefer/2; + } + + if (ddns_ttl > MAX_DEFAULT_DDNS_TTL) { + ddns_ttl = MAX_DEFAULT_DDNS_TTL; + } +#endif + + if ((oc = lookup_option(&server_universe, options, SV_DDNS_TTL))) { + if (evaluate_option_cache(&d1, packet, lease, NULL, + packet->options, options, + scope, oc, MDL)) { + if (d1.len == sizeof (u_int32_t)) + ddns_ttl = getULong (d1.data); + data_string_forget (&d1, MDL); + } + } + + ddns_cb->ttl = ddns_ttl; + + /* + * Compute the reverse IP name, starting with the domain name. + */ + oc = lookup_option(&server_universe, options, SV_DDNS_REV_DOMAIN_NAME); + if (oc) + s1 = evaluate_option_cache(&d1, packet, lease, NULL, + packet->options, options, + scope, oc, MDL); + else + s1 = 0; + + /* + * Figure out the length of the part of the name that depends + * on the address. + */ + if (ddns_cb->address.len == 4) { + char buf[17]; + /* XXX: WOW this is gross. */ + rev_name_len = snprintf(buf, sizeof(buf), "%u.%u.%u.%u.", + ddns_cb->address.iabuf[3] & 0xff, + ddns_cb->address.iabuf[2] & 0xff, + ddns_cb->address.iabuf[1] & 0xff, + ddns_cb->address.iabuf[0] & 0xff) + 1; + + if (s1) { + rev_name_len += d1.len; + + if (rev_name_len > 255) { + log_error("ddns_update: Calculated rev domain " + "name too long."); + s1 = 0; + data_string_forget(&d1, MDL); + } + } + } else if (ddns_cb->address.len == 16) { + /* + * IPv6 reverse names are always the same length, with + * 32 hex characters separated by dots. + */ + rev_name_len = sizeof("0.1.2.3.4.5.6.7." + "8.9.a.b.c.d.e.f." + "0.1.2.3.4.5.6.7." + "8.9.a.b.c.d.e.f." + "ip6.arpa."); + + /* Set s1 to make sure we gate into updates. */ + s1 = 1; + } else { + log_fatal("invalid address length %d", ddns_cb->address.len); + /* Silence compiler warnings. */ + return 0; + } + + /* See if we are configured NOT to do reverse ptr updates */ + if ((oc = lookup_option(&server_universe, options, + SV_DO_REVERSE_UPDATES)) && + !evaluate_boolean_option_cache(&ignorep, packet, lease, NULL, + packet->options, options, + scope, oc, MDL)) { + ddns_cb->flags &= ~DDNS_UPDATE_PTR; + } + + if (s1) { + buffer_allocate(&ddns_cb->rev_name.buffer, rev_name_len, MDL); + if (ddns_cb->rev_name.buffer != NULL) { + struct data_string *rname = &ddns_cb->rev_name; + rname->data = rname->buffer->data; + + if (ddns_cb->address.len == 4) { + rname->len = + sprintf((char *)rname->buffer->data, + "%u.%u.%u.%u.", + ddns_cb->address.iabuf[3] & 0xff, + ddns_cb->address.iabuf[2] & 0xff, + ddns_cb->address.iabuf[1] & 0xff, + ddns_cb->address.iabuf[0] & 0xff); + + /* + * d1.data may be opaque, garbage bytes, from + * user (mis)configuration. + */ + data_string_append(rname, &d1); + rname->buffer->data[rname->len] = '\0'; + } else if (ddns_cb->address.len == 16) { + char *p = (char *)&rname->buffer->data; + unsigned char *a = ddns_cb->address.iabuf + 15; + for (i=0; i<16; i++) { + sprintf(p, "%x.%x.", + (*a & 0xF), ((*a >> 4) & 0xF)); + p += 4; + a -= 1; + } + strcat(p, "ip6.arpa."); + rname->len = strlen((const char *)rname->data); + } + + rname->terminated = 1; + } + + if (d1.data != NULL) + data_string_forget(&d1, MDL); + } + + /* + * If we are updating the A record, compute the DHCID value. + */ + if ((ddns_cb->flags & DDNS_UPDATE_ADDR) != 0) { + if (lease6 != NULL) + result = get_dhcid(&ddns_cb->dhcid, 2, + lease6->ia->iaid_duid.data, + lease6->ia->iaid_duid.len); + else if ((lease != NULL) && (lease->uid != NULL) && + (lease->uid_len != 0)) + result = get_dhcid (&ddns_cb->dhcid, + DHO_DHCP_CLIENT_IDENTIFIER, + lease -> uid, lease -> uid_len); + else if (lease != NULL) + result = get_dhcid (&ddns_cb->dhcid, 0, + lease -> hardware_addr.hbuf, + lease -> hardware_addr.hlen); + else + log_fatal("Impossible condition at %s:%d.", MDL); + + if (!result) + goto badfqdn; + } + + /* + * Perform updates. + */ + + data_string_copy(&ddns_cb->fwd_name, &ddns_fwd_name, MDL); + + if (ddns_cb->flags && DDNS_UPDATE_ADDR) { + oc = lookup_option(&server_universe, options, + SV_DDNS_CONFLICT_DETECT); + if (oc && + !evaluate_boolean_option_cache(&ignorep, packet, lease, + NULL, packet->options, + options, scope, oc, MDL)) + ddns_cb->flags |= DDNS_CONFLICT_OVERRIDE; + + } + + /* + * Previously if we failed during the removal operations + * we skipped the fqdn option processing. I'm not sure + * if we want to continue with that if we fail before sending + * the ddns messages. Currently we don't. + */ + if (do_remove) { + rcode1 = ddns_removals(lease, lease6, ddns_cb); + } + else { + ddns_fwd_srv_connector(lease, lease6, scope, ddns_cb, + ISC_R_SUCCESS); + } + ddns_cb = NULL; + + noerror: + /* + * If fqdn-reply option is disabled in dhcpd.conf, then don't + * send the client an FQDN option at all, even if one was requested. + * (WinXP clients allegedly misbehave if the option is present, + * refusing to handle PTR updates themselves). + */ + if ((oc = lookup_option (&server_universe, options, SV_FQDN_REPLY)) && + !evaluate_boolean_option_cache(&ignorep, packet, lease, NULL, + packet->options, options, + scope, oc, MDL)) { + goto badfqdn; + + /* If we're ignoring client updates, then we tell a sort of 'white + * lie'. We've already updated the name the server wants (per the + * config written by the server admin). Now let the client do as + * it pleases with the name they supplied (if any). + * + * We only form an FQDN option this way if the client supplied an + * FQDN option that had FQDN_SERVER_UPDATE set false. + */ + } else if (client_ignorep && + (oc = lookup_option(&fqdn_universe, packet->options, + FQDN_SERVER_UPDATE)) && + !evaluate_boolean_option_cache(&ignorep, packet, lease, NULL, + packet->options, options, + scope, oc, MDL)) { + oc = lookup_option(&fqdn_universe, packet->options, FQDN_FQDN); + if (oc && evaluate_option_cache(&d1, packet, lease, NULL, + packet->options, options, + scope, oc, MDL)) { + if (d1.len == 0 || + !buffer_allocate(&bp, d1.len + 5, MDL)) + goto badfqdn; + + /* Server pretends it is not updating. */ + bp->data[0] = 0; + if (!save_option_buffer(&fqdn_universe, options, + bp, &bp->data[0], 1, + FQDN_SERVER_UPDATE, 0)) + goto badfqdn; + + /* Client is encouraged to update. */ + bp->data[1] = 0; + if (!save_option_buffer(&fqdn_universe, options, + bp, &bp->data[1], 1, + FQDN_NO_CLIENT_UPDATE, 0)) + goto badfqdn; + + /* Use the encoding of client's FQDN option. */ + oc = lookup_option(&fqdn_universe, packet->options, + FQDN_ENCODED); + if (oc && + evaluate_boolean_option_cache(&ignorep, packet, + lease, NULL, + packet->options, + options, scope, + oc, MDL)) + bp->data[2] = 1; /* FQDN is encoded. */ + else + bp->data[2] = 0; /* FQDN is not encoded. */ + + if (!save_option_buffer(&fqdn_universe, options, + bp, &bp->data[2], 1, + FQDN_ENCODED, 0)) + goto badfqdn; + + /* Current FQDN drafts indicate 255 is mandatory. */ + bp->data[3] = 255; + if (!save_option_buffer(&fqdn_universe, options, + bp, &bp->data[3], 1, + FQDN_RCODE1, 0)) + goto badfqdn; + + bp->data[4] = 255; + if (!save_option_buffer(&fqdn_universe, options, + bp, &bp->data[4], 1, + FQDN_RCODE2, 0)) + goto badfqdn; + + /* Copy in the FQDN supplied by the client. Note well + * that the format of this option in the cache is going + * to be in text format. If the fqdn supplied by the + * client is encoded, it is decoded into the option + * cache when parsed out of the packet. It will be + * re-encoded when the option is assembled to be + * transmitted if the client elects that encoding. + */ + memcpy(&bp->data[5], d1.data, d1.len); + if (!save_option_buffer(&fqdn_universe, options, + bp, &bp->data[5], d1.len, + FQDN_FQDN, 0)) + goto badfqdn; + + data_string_forget(&d1, MDL); + } + /* Set up the outgoing FQDN option if there was an incoming + * FQDN option. If there's a valid FQDN option, there MUST + * be an FQDN_SERVER_UPDATES suboption, it's part of the fixed + * length head of the option contents, so we test the latter + * to detect the presence of the former. + */ + } else if ((oc = lookup_option(&fqdn_universe, packet->options, + FQDN_ENCODED)) && + buffer_allocate(&bp, ddns_fwd_name.len + 5, MDL)) { + bp -> data [0] = server_updates_a; + if (!save_option_buffer(&fqdn_universe, options, + bp, &bp->data [0], 1, + FQDN_SERVER_UPDATE, 0)) + goto badfqdn; + bp -> data [1] = server_updates_a; + if (!save_option_buffer(&fqdn_universe, options, + bp, &bp->data [1], 1, + FQDN_NO_CLIENT_UPDATE, 0)) + goto badfqdn; + + /* Do the same encoding the client did. */ + if (evaluate_boolean_option_cache(&ignorep, packet, lease, + NULL, packet->options, + options, scope, oc, MDL)) + bp -> data [2] = 1; + else + bp -> data [2] = 0; + if (!save_option_buffer(&fqdn_universe, options, + bp, &bp->data [2], 1, + FQDN_ENCODED, 0)) + goto badfqdn; + bp -> data [3] = 255;//isc_rcode_to_ns (rcode1); + if (!save_option_buffer(&fqdn_universe, options, + bp, &bp->data [3], 1, + FQDN_RCODE1, 0)) + goto badfqdn; + bp -> data [4] = 255;//isc_rcode_to_ns (rcode2); + if (!save_option_buffer(&fqdn_universe, options, + bp, &bp->data [4], 1, + FQDN_RCODE2, 0)) + goto badfqdn; + if (ddns_fwd_name.len) { + memcpy (&bp -> data [5], + ddns_fwd_name.data, ddns_fwd_name.len); + if (!save_option_buffer(&fqdn_universe, options, + bp, &bp->data [5], + ddns_fwd_name.len, + FQDN_FQDN, 0)) + goto badfqdn; + } + } + + badfqdn: + out: + /* + * Final cleanup. + */ + if (ddns_cb != NULL) { + ddns_cb_free(ddns_cb, MDL); + } + + data_string_forget(&d1, MDL); + data_string_forget(&ddns_hostname, MDL); + data_string_forget(&ddns_domainname, MDL); + data_string_forget(&old_ddns_fwd_name, MDL); + data_string_forget(&ddns_fwd_name, MDL); + //data_string_forget(&ddns_rev_name, MDL); + //data_string_forget(&ddns_dhcid, MDL); + if (bp) + buffer_dereference(&bp, MDL); + + return result; +} + +/* + * Utility function to update text strings within a lease. + * + * The first issue is to find the proper scope. Sometimes we shall be + * called with a pointer to the scope in other cases we need to find + * the proper lease and then get the scope. Once we have the scope we update + * the proper strings, as indicated by the state value in the control block. + * Lastly, if we needed to find the scope we write it out, if we used a + * scope that was passed as an argument we don't write it, assuming that + * our caller (or his ...) will do the write. + */ + +isc_result_t +ddns_update_lease_text(dhcp_ddns_cb_t *ddns_cb, + struct binding_scope **inscope) +{ + struct binding_scope **scope = NULL; + struct lease *lease = NULL; + struct iasubopt *lease6 = NULL; + struct ipv6_pool *pool = NULL; + struct in6_addr addr; + struct data_string lease_dhcid; + + if (inscope != NULL) { + scope = inscope; + } else if (ddns_cb->address.len == 4) { + if (find_lease_by_ip_addr(&lease, ddns_cb->address, MDL) != 0){ + scope = &(lease->scope); + } + } else if (ddns_cb->address.len == 16) { + memcpy(&addr, &ddns_cb->address.iabuf, 16); + if ((find_ipv6_pool(&pool, D6O_IA_TA, &addr) == + ISC_R_SUCCESS) || + (find_ipv6_pool(&pool, D6O_IA_NA, &addr) == + ISC_R_SUCCESS)) { + if (iasubopt_hash_lookup(&lease6, pool->leases, + &addr, 16, MDL)) { + scope = &(lease6->scope); + } + ipv6_pool_dereference(&pool, MDL); + } + } else { + log_fatal("Impossible condition at %s:%d.", MDL); + } + + if (scope == NULL) { + /* If necessary get rid of the lease */ + if (lease) { + lease_dereference(&lease, MDL); + } + else if (lease6) { + iasubopt_dereference(&lease6, MDL); + } + + return(ISC_R_FAILURE); + } + + /* We now have a scope and can proceed to update it */ + switch(ddns_cb->state) { + case DDNS_STATE_REM_PTR: + unset(*scope, "ddns-rev-name"); + if ((ddns_cb->flags & DDNS_CLIENT_DID_UPDATE) != 0) { + unset(*scope, "ddns-client-fqdn"); + } + break; + + case DDNS_STATE_ADD_PTR: + case DDNS_STATE_CLEANUP: + bind_ds_value(scope, "ddns-rev-name", &ddns_cb->rev_name); + if ((ddns_cb->flags & DDNS_UPDATE_ADDR) == 0) { + bind_ds_value(scope, "ddns-client-fqdn", + &ddns_cb->fwd_name); + } + break; + + case DDNS_STATE_ADD_FW_YXDHCID: + case DDNS_STATE_ADD_FW_NXDOMAIN: + bind_ds_value(scope, "ddns-fwd-name", &ddns_cb->fwd_name); + + /* convert from dns version to lease version of dhcid */ + memset(&lease_dhcid, 0, sizeof(lease_dhcid)); + dhcid_tolease(&ddns_cb->dhcid, &lease_dhcid); + bind_ds_value(scope, "ddns-txt", &lease_dhcid); + data_string_forget(&lease_dhcid, MDL); + + break; + + case DDNS_STATE_REM_FW_NXRR: + case DDNS_STATE_REM_FW_YXDHCID: + unset(*scope, "ddns-fwd-name"); + unset(*scope, "ddns-txt"); + break; + } + + /* If necessary write it out and get rid of the lease */ + if (lease) { + write_lease(lease); + lease_dereference(&lease, MDL); + } else if (lease6) { + write_ia(lease6->ia); + iasubopt_dereference(&lease6, MDL); + } + + return(ISC_R_SUCCESS); +} + +/* + * This function should be called when update_lease_ptr function fails. + * It does inform user about the condition, provides some hints how to + * resolve this and dies gracefully. This can happend in at least three + * cases (all are configuration mistakes): + * a) IPv4: user have duplicate fixed-address entries (the same + * address is defined twice). We may have found wrong lease. + * b) IPv6: user have overlapping pools (we tried to find + * a lease in a wrong pool) + * c) IPv6: user have duplicate fixed-address6 entires (the same + * address is defined twice). We may have found wrong lease. + * + * Comment: while it would be possible to recover from both cases + * by forcibly searching for leases in *all* following pools, that would + * only hide the real problem - a misconfiguration. Proper solution + * is to log the problem, die and let the user fix his config file. + */ +void +update_lease_failed(struct lease *lease, + struct iasubopt *lease6, + dhcp_ddns_cb_t *ddns_cb, + dhcp_ddns_cb_t *ddns_cb_set, + const char * file, int line) +{ + char lease_address[MAX_ADDRESS_STRING_LEN + 64]; + char reason[128]; /* likely reason */ + + sprintf(reason, "unknown"); + sprintf(lease_address, "unknown"); + + /* let's pretend that everything is ok, so we can continue for + for information gathering purposes */ + + if (ddns_cb != NULL) { + strncpy(lease_address, piaddr(ddns_cb->address), + MAX_ADDRESS_STRING_LEN); + + if (ddns_cb->address.len == 4) { + sprintf(reason, "duplicate IPv4 fixed-address entry"); + } else if (ddns_cb->address.len == 16) { + sprintf(reason, "duplicate IPv6 fixed-address6 entry " + "or overlapping pools"); + } else { + /* + * Should not happen. We have non-IPv4, non-IPv6 + * address. Something is very wrong here. + */ + sprintf(reason, "corrupted ddns_cb structure (address " + "length is %d)", ddns_cb->address.len); + } + } + + log_error("Failed to properly update internal lease structure with " + "DDNS"); + log_error("control block structures. Tried to update lease for" + "%s address, ddns_cb=%p.", lease_address, ddns_cb); + + log_error("%s", ""); + log_error("This condition can occur, if DHCP server configuration is " + "inconsistent."); + log_error("In particular, please do check that your configuration:"); + log_error("a) does not have overlapping pools (especially containing"); + log_error(" %s address).", lease_address); + log_error("b) there are no duplicate fixed-address or fixed-address6"); + log_error("entries for the %s address.", lease_address); + log_error("%s", ""); + log_error("Possible reason for this failure: %s", reason); + + log_fatal("%s(%d): Failed to update lease database with DDNS info for " + "address %s. Lease database inconsistent. Unable to recover." + " Terminating.", file, line, lease_address); +} + +/* + * utility function to update found lease. It does extra checks + * that we are indeed updating the right lease. It may happen + * that user have duplicate fixed-address entries, so we attempt + * to update wrong lease. See also safe_lease6_update. + */ + +void +safe_lease_update(struct lease *lease, + struct iasubopt *lease6, + dhcp_ddns_cb_t *oldcb, + dhcp_ddns_cb_t *newcb, + const char *file, int line) +{ + char addrbuf[MAX_ADDRESS_STRING_LEN]; + + if (lease != NULL) { + if ( (lease->ddns_cb == NULL) && (newcb == NULL) ) { + /* + * Trying to clean up pointer that is already null. We + * are most likely trying to update wrong lease here. + */ + + /* + * this error message pops out during every DNS Update + * for fixed leases. It is enabled only for debugging + * DNS Updates. Investigation pending. + */ +#if defined (DEBUG_DNS_UPDATES) + log_error("%s(%d): Invalid lease update. Tried to " + "clear already NULL DDNS control block " + "pointer for lease %s.", + file, line, piaddr(lease->ip_addr) ); +#endif + +#if defined (DNS_UPDATES_MEMORY_CHECKS) + update_lease_failed(lease, NULL, oldcb, newcb, file, + line); +#endif + /* + * May not reach this: update_lease_failed calls + * log_fatal. + */ + return; + } + + if ( (lease->ddns_cb != NULL) && (lease->ddns_cb != oldcb) ) { + /* + * There is existing cb structure, but it differs from + * what we expected to see there. Most likely we are + * trying to update wrong lease. + */ + log_error("%s(%d): Failed to update internal lease " + "structure with DDNS control block. Existing" + " ddns_cb structure does not match " + "expectations.IPv4=%s, old ddns_cb=%p, tried" + "to update to new ddns_cb=%p", file, line, + piaddr(lease->ip_addr), oldcb, newcb); + +#if defined (DNS_UPDATES_MEMORY_CHECKS) + update_lease_failed(lease, NULL, oldcb, newcb, file, + line); +#endif + /* + * May not reach this: update_lease_failed calls + * log_fatal. + */ + return; + } + /* additional IPv4 specific checks may be added here */ + + /* update the lease */ + lease->ddns_cb = newcb; + } else if (lease6 != NULL) { + if ( (lease6->ddns_cb == NULL) && (newcb == NULL) ) { + inet_ntop(AF_INET6, &lease6->addr, addrbuf, + MAX_ADDRESS_STRING_LEN); + /* + * Trying to clean up pointer that is already null. We + * are most likely trying to update wrong lease here. + */ +#if defined (DEBUG_DNS_UPDATES) + log_error("%s(%d): Failed to update internal lease " + "structure. Tried to clear already NULL " + "DDNS control block pointer for lease %s.", + file, line, addrbuf); +#endif + +#if defined (DNS_UPDATES_MEMORY_CHECKS) + update_lease_failed(NULL, lease6, oldcb, newcb, file, + line); +#endif + + /* + * May not reach this: update_lease_failed calls + * log_fatal. + */ + return; + } + + if ( (lease6->ddns_cb != NULL) && (lease6->ddns_cb != oldcb) ) { + /* + * there is existing cb structure, but it differs from + * what we expected to see there. Most likely we are + * trying to update wrong lease. + */ + inet_ntop(AF_INET6, &lease6->addr, addrbuf, + MAX_ADDRESS_STRING_LEN); + + log_error("%s(%d): Failed to update internal lease " + "structure with DDNS control block. Existing" + " ddns_cb structure does not match " + "expectations.IPv6=%s, old ddns_cb=%p, tried" + "to update to new ddns_cb=%p", file, line, + addrbuf, oldcb, newcb); + +#if defined (DNS_UPDATES_MEMORY_CHECKS) + update_lease_failed(NULL, lease6, oldcb, newcb, file, + line); +#endif + /* + * May not reach this: update_lease_failed calls + * log_fatal. + */ + return; + } + /* additional IPv6 specific checks may be added here */ + + /* update the lease */ + lease6->ddns_cb = newcb; + + } else { + /* should never get here */ + log_fatal("Impossible condition at %s:%d (called from %s:%d).", + MDL, file, line); + } +} + +/* + * Utility function to update the pointer to the DDNS control block + * in a lease. + * SUCCESS - able to update the pointer + * FAILURE - lease didn't exist or sanity checks failed + * lease and lease6 may be empty in which case we attempt to find + * the lease from the ddns_cb information. + * ddns_cb is the control block to use if a lookup is necessary + * ddns_cb_set is the pointer to insert into the lease and may be NULL + * The last two arguments may look odd as they will be the same much of the + * time, but I need an argument to tell me if I'm setting or clearing in + * addition to the address information from the cb to look up the lease. + * using the same value twice allows me more flexibility. + */ + +isc_result_t +ddns_update_lease_ptr(struct lease *lease, + struct iasubopt *lease6, + dhcp_ddns_cb_t *ddns_cb, + dhcp_ddns_cb_t *ddns_cb_set, + const char * file, int line) +{ + char ddns_address[MAX_ADDRESS_STRING_LEN]; + sprintf(ddns_address, "uknown"); + if (ddns_cb) { + strncpy(ddns_address, piaddr(ddns_cb->address), + MAX_ADDRESS_STRING_LEN); + } +#if defined (DEBUG_DNS_UPDATES) + log_info("%s(%d): Updating lease_ptr for ddns_cp=%p (addr=%s)", + file, line, ddns_cb, ddns_address ); +#endif + + if (lease != NULL) { + safe_lease_update(lease, NULL, ddns_cb, ddns_cb_set, + file, line); + /* lease->ddns_cb = ddns_cb_set; */ + } else if (lease6 != NULL) { + safe_lease_update(NULL, lease6, ddns_cb, ddns_cb_set, + file, line); + /* lease6->ddns_cb = ddns_cb_set; */ + } else if (ddns_cb->address.len == 4) { + struct lease *find_lease = NULL; + if (find_lease_by_ip_addr(&find_lease, + ddns_cb->address, MDL) != 0) { +#if defined (DEBUG_DNS_UPDATES) + log_info("%s(%d): find_lease_by_ip_addr(%s) successful:" + "lease=%p", file, line, ddns_address, + find_lease); +#endif + + /* find_lease->ddns_cb = ddns_cb_set; */ + safe_lease_update(find_lease, NULL, ddns_cb, + ddns_cb_set, file, line); + lease_dereference(&find_lease, MDL); + } + else { +#if defined (DEBUG_DNS_UPDATES) + log_error("%s(%d): ddns_update_lease_ptr failed. " + "Lease for %s not found. (Is it static lease?)", + file, line, piaddr(ddns_cb->address)); +#endif + +#if defined (DNS_UPDATES_MEMORY_CHECKS) + update_lease_failed(NULL, NULL, ddns_cb, ddns_cb_set, + file, line); +#endif + /* + * may not reach this. update_lease_failed + * calls log_fatal. + */ + return(ISC_R_FAILURE); + + } + } else if (ddns_cb->address.len == 16) { + struct iasubopt *find_lease6 = NULL; + struct ipv6_pool *pool = NULL; + struct in6_addr addr; + char addrbuf[MAX_ADDRESS_STRING_LEN]; + + memcpy(&addr, &ddns_cb->address.iabuf, 16); + if ((find_ipv6_pool(&pool, D6O_IA_TA, &addr) != + ISC_R_SUCCESS) && + (find_ipv6_pool(&pool, D6O_IA_NA, &addr) != + ISC_R_SUCCESS)) { + inet_ntop(AF_INET6, &lease6->addr, addrbuf, + MAX_ADDRESS_STRING_LEN); + log_error("%s(%d): Pool for lease %s not found.", + file, line, addrbuf); +#if defined (DNS_UPDATES_MEMORY_CHECKS) + update_lease_failed(NULL, NULL, ddns_cb, ddns_cb_set, + file, line); +#endif + /* + * never reached. update_lease_failed + * calls log_fatal. + */ + return(ISC_R_FAILURE); + } + + if (iasubopt_hash_lookup(&find_lease6, pool->leases, + &addr, 16, MDL)) { + find_lease6->ddns_cb = ddns_cb_set; + iasubopt_dereference(&find_lease6, MDL); + } else { + inet_ntop(AF_INET6, &lease6->addr, addrbuf, + MAX_ADDRESS_STRING_LEN); + log_error("%s(%d): Lease %s not found within pool.", + file, line, addrbuf); +#if defined (DNS_UPDATES_MEMORY_CHECKS) + update_lease_failed(NULL, NULL, ddns_cb, ddns_cb_set, + file, line); +#endif + /* + * never reached. update_lease_failed + * calls log_fatal. + */ + return(ISC_R_FAILURE); + } + ipv6_pool_dereference(&pool, MDL); + } else { + /* shouldn't get here */ + log_fatal("Impossible condition at %s:%d, called from %s:%d.", + MDL, file, line); + } + + return(ISC_R_SUCCESS); +} + +void +ddns_ptr_add(dhcp_ddns_cb_t *ddns_cb, + isc_result_t eresult) +{ + if (eresult == ISC_R_SUCCESS) { + log_info("Added reverse map from %.*s to %.*s", + (int)ddns_cb->rev_name.len, + (const char *)ddns_cb->rev_name.data, + (int)ddns_cb->fwd_name.len, + (const char *)ddns_cb->fwd_name.data); + + ddns_update_lease_text(ddns_cb, NULL); + } else { + log_error("Unable to add reverse map from %.*s to %.*s: %s", + (int)ddns_cb->rev_name.len, + (const char *)ddns_cb->rev_name.data, + (int)ddns_cb->fwd_name.len, + (const char *)ddns_cb->fwd_name.data, + isc_result_totext (eresult)); + } + + ddns_update_lease_ptr(NULL, NULL, ddns_cb, NULL, MDL); + ddns_cb_free(ddns_cb, MDL); + /* + * A single DDNS operation may require several calls depending on + * the current state as the prerequisites for the first message + * may not succeed requiring a second operation and potentially + * a ptr operation after that. The commit_leases operation is + * invoked at the end of this set of operations in order to require + * a single write for all of the changes. We call commit_leases + * here rather than immediately after the call to update the lease + * text in order to save any previously written data. + */ + commit_leases(); + return; +} + +/* + * action routine when trying to remove a pointer + * this will be called after the ddns queries have completed + * if we succeeded in removing the pointer we go to the next step (if any) + * if not we cleanup and leave. + */ + +void +ddns_ptr_remove(dhcp_ddns_cb_t *ddns_cb, + isc_result_t eresult) +{ + isc_result_t result = eresult; + + switch(eresult) { + case ISC_R_SUCCESS: + log_info("Removed reverse map on %.*s", + (int)ddns_cb->rev_name.len, + (const char *)ddns_cb->rev_name.data); + /* fall through */ + case DNS_R_NXRRSET: + case DNS_R_NXDOMAIN: + /* No entry is the same as success. + * Remove the information from the lease and + * continue with any next step */ + ddns_update_lease_text(ddns_cb, NULL); + + /* trigger any add operation */ + result = ISC_R_SUCCESS; + break; + + default: + log_error("Can't remove reverse map on %.*s: %s", + (int)ddns_cb->rev_name.len, + (const char *)ddns_cb->rev_name.data, + isc_result_totext (eresult)); + break; + } + + ddns_update_lease_ptr(NULL, NULL, ddns_cb, NULL, MDL); + ddns_fwd_srv_connector(NULL, NULL, NULL, ddns_cb->next_op, result); + ddns_cb_free(ddns_cb, MDL); + return; +} + + +/* + * If the first query succeeds, the updater can conclude that it + * has added a new name whose only RRs are the A and DHCID RR records. + * The A RR update is now complete (and a client updater is finished, + * while a server might proceed to perform a PTR RR update). + * -- "Interaction between DHCP and DNS" + * + * If the second query succeeds, the updater can conclude that the current + * client was the last client associated with the domain name, and that + * the name now contains the updated A RR. The A RR update is now + * complete (and a client updater is finished, while a server would + * then proceed to perform a PTR RR update). + * -- "Interaction between DHCP and DNS" + * + * If the second query fails with NXRRSET, the updater must conclude + * that the client's desired name is in use by another host. At this + * juncture, the updater can decide (based on some administrative + * configuration outside of the scope of this document) whether to let + * the existing owner of the name keep that name, and to (possibly) + * perform some name disambiguation operation on behalf of the current + * client, or to replace the RRs on the name with RRs that represent + * the current client. If the configured policy allows replacement of + * existing records, the updater submits a query that deletes the + * existing A RR and the existing DHCID RR, adding A and DHCID RRs that + * represent the IP address and client-identity of the new client. + * -- "Interaction between DHCP and DNS" + */ + +void +ddns_fwd_srv_add2(dhcp_ddns_cb_t *ddns_cb, + isc_result_t eresult) +{ + isc_result_t result; + const char *logstr = NULL; + char ddns_address[MAX_ADDRESS_STRING_LEN]; + + /* Construct a printable form of the address for logging */ + strcpy(ddns_address, piaddr(ddns_cb->address)); + + switch(eresult) { + case ISC_R_SUCCESS: + log_info("Added new forward map from %.*s to %s", + (int)ddns_cb->fwd_name.len, + (const char *)ddns_cb->fwd_name.data, + ddns_address); + + ddns_update_lease_text(ddns_cb, NULL); + + if ((ddns_cb->flags & DDNS_UPDATE_PTR) != 0) { + /* if we have zone information get rid of it */ + if (ddns_cb->zone != NULL) { + ddns_cb_forget_zone(ddns_cb); + } + + ddns_cb->state = DDNS_STATE_ADD_PTR; + ddns_cb->cur_func = ddns_ptr_add; + + result = ddns_modify_ptr(ddns_cb); + if (result == ISC_R_SUCCESS) { + return; + } + } + break; + + case DNS_R_YXRRSET: + case DNS_R_YXDOMAIN: + logstr = "DHCID mismatch, belongs to another client."; + break; + + case DNS_R_NXRRSET: + case DNS_R_NXDOMAIN: + logstr = "Has an address record but no DHCID, not mine."; + break; + + default: + logstr = isc_result_totext(eresult); + break; + } + + if (logstr != NULL) { + log_error("Forward map from %.*s to %s FAILED: %s", + (int)ddns_cb->fwd_name.len, + (const char *)ddns_cb->fwd_name.data, + ddns_address, logstr); + } + + ddns_update_lease_ptr(NULL, NULL, ddns_cb, NULL, MDL); + ddns_cb_free(ddns_cb, MDL); + /* + * A single DDNS operation may require several calls depending on + * the current state as the prerequisites for the first message + * may not succeed requiring a second operation and potentially + * a ptr operation after that. The commit_leases operation is + * invoked at the end of this set of operations in order to require + * a single write for all of the changes. We call commit_leases + * here rather than immediately after the call to update the lease + * text in order to save any previously written data. + */ + commit_leases(); + return; +} + +void +ddns_fwd_srv_add1(dhcp_ddns_cb_t *ddns_cb, + isc_result_t eresult) +{ + isc_result_t result; + char ddns_address[MAX_ADDRESS_STRING_LEN]; + + /* Construct a printable form of the address for logging */ + strcpy(ddns_address, piaddr(ddns_cb->address)); + + switch(eresult) { + case ISC_R_SUCCESS: + log_info ("Added new forward map from %.*s to %s", + (int)ddns_cb->fwd_name.len, + (const char *)ddns_cb->fwd_name.data, + ddns_address); + + ddns_update_lease_text(ddns_cb, NULL); + + if ((ddns_cb->flags & DDNS_UPDATE_PTR) != 0) { + /* if we have zone information get rid of it */ + if (ddns_cb->zone != NULL) { + ddns_cb_forget_zone(ddns_cb); + } + + ddns_cb->state = DDNS_STATE_ADD_PTR; + ddns_cb->cur_func = ddns_ptr_add; + + result = ddns_modify_ptr(ddns_cb); + if (result == ISC_R_SUCCESS) { + return; + } + } + + break; + + case DNS_R_YXDOMAIN: + /* we can reuse the zone information */ + ddns_cb->state = DDNS_STATE_ADD_FW_YXDHCID; + ddns_cb->cur_func = ddns_fwd_srv_add2; + + result = ddns_modify_fwd(ddns_cb); + if (result == ISC_R_SUCCESS) { + return; + } + + break; + + default: + log_error ("Unable to add forward map from %.*s to %s: %s", + (int)ddns_cb->fwd_name.len, + (const char *)ddns_cb->fwd_name.data, + ddns_address, + isc_result_totext (eresult)); + break; + } + + ddns_update_lease_ptr(NULL, NULL, ddns_cb, NULL, MDL); + ddns_cb_free(ddns_cb, MDL); + /* + * A single DDNS operation may require several calls depending on + * the current state as the prerequisites for the first message + * may not succeed requiring a second operation and potentially + * a ptr operation after that. The commit_leases operation is + * invoked at the end of this set of operations in order to require + * a single write for all of the changes. We call commit_leases + * here rather than immediately after the call to update the lease + * text in order to save any previously written data. + */ + commit_leases(); + return; +} + +static void +ddns_fwd_srv_connector(struct lease *lease, + struct iasubopt *lease6, + struct binding_scope **inscope, + dhcp_ddns_cb_t *ddns_cb, + isc_result_t eresult) +{ + isc_result_t result = ISC_R_FAILURE; + + if (ddns_cb == NULL) { + /* nothing to do */ + return; + } + + if (eresult == ISC_R_SUCCESS) { + /* + * If we have updates dispatch as appropriate, + * if not do FQDN binding if desired. + */ + + if (ddns_cb->flags & DDNS_UPDATE_ADDR) { + ddns_cb->state = DDNS_STATE_ADD_FW_NXDOMAIN; + ddns_cb->cur_func = ddns_fwd_srv_add1; + result = ddns_modify_fwd(ddns_cb); + } else if ((ddns_cb->flags & DDNS_UPDATE_PTR) && + (ddns_cb->rev_name.len != 0)) { + ddns_cb->state = DDNS_STATE_ADD_PTR; + ddns_cb->cur_func = ddns_ptr_add; + result = ddns_modify_ptr(ddns_cb); + } else { + ddns_update_lease_text(ddns_cb, inscope); + } + } + + if (result == ISC_R_SUCCESS) { + ddns_update_lease_ptr(lease, lease6, ddns_cb, ddns_cb, MDL); + } else { + ddns_cb_free(ddns_cb, MDL); + } + + return; +} + +/* + * If the first query fails, the updater MUST NOT delete the DNS name. It + * may be that the host whose lease on the server has expired has moved + * to another network and obtained a lease from a different server, + * which has caused the client's A RR to be replaced. It may also be + * that some other client has been configured with a name that matches + * the name of the DHCP client, and the policy was that the last client + * to specify the name would get the name. In this case, the DHCID RR + * will no longer match the updater's notion of the client-identity of + * the host pointed to by the DNS name. + * -- "Interaction between DHCP and DNS" + */ + +void +ddns_fwd_srv_rem2(dhcp_ddns_cb_t *ddns_cb, + isc_result_t eresult) +{ + if (eresult == ISC_R_SUCCESS) { + ddns_update_lease_text(ddns_cb, NULL); + + /* Do the next operation */ + if ((ddns_cb->flags & DDNS_UPDATE_PTR) != 0) { + /* if we have zone information get rid of it */ + if (ddns_cb->zone != NULL) { + ddns_cb_forget_zone(ddns_cb); + } + + ddns_cb->state = DDNS_STATE_REM_PTR; + ddns_cb->cur_func = ddns_ptr_remove; + + eresult = ddns_modify_ptr(ddns_cb); + if (eresult == ISC_R_SUCCESS) { + return; + } + } + } + + ddns_update_lease_ptr(NULL, NULL, ddns_cb, NULL, MDL); + ddns_fwd_srv_connector(NULL, NULL, NULL, ddns_cb->next_op, eresult); + ddns_cb_free(ddns_cb, MDL); + return; +} + + +/* + * First action routine when trying to remove a fwd + * this will be called after the ddns queries have completed + * if we succeeded in removing the fwd we go to the next step (if any) + * if not we cleanup and leave. + */ + +void +ddns_fwd_srv_rem1(dhcp_ddns_cb_t *ddns_cb, + isc_result_t eresult) +{ + isc_result_t result = eresult; + char ddns_address[MAX_ADDRESS_STRING_LEN]; + + switch(eresult) { + case ISC_R_SUCCESS: + /* Construct a printable form of the address for logging */ + strcpy(ddns_address, piaddr(ddns_cb->address)); + log_info("Removed forward map from %.*s to %s", + (int)ddns_cb->fwd_name.len, + (const char*)ddns_cb->fwd_name.data, + ddns_address); + + /* Do the second step of the FWD removal */ + ddns_cb->state = DDNS_STATE_REM_FW_NXRR; + ddns_cb->cur_func = ddns_fwd_srv_rem2; + result = ddns_modify_fwd(ddns_cb); + if (result == ISC_R_SUCCESS) { + return; + } + break; + + case DNS_R_NXRRSET: + case DNS_R_NXDOMAIN: + ddns_update_lease_text(ddns_cb, NULL); + + /* Do the next operation */ + if ((ddns_cb->flags & DDNS_UPDATE_PTR) != 0) { + /* if we have zone information get rid of it */ + if (ddns_cb->zone != NULL) { + ddns_cb_forget_zone(ddns_cb); + } + + ddns_cb->state = DDNS_STATE_REM_PTR; + ddns_cb->cur_func = ddns_ptr_remove; + + result = ddns_modify_ptr(ddns_cb); + if (result == ISC_R_SUCCESS) { + return; + } + } + else { + /* Trigger the add operation */ + eresult = ISC_R_SUCCESS; + } + break; + + default: + break; + } + + ddns_update_lease_ptr(NULL, NULL, ddns_cb, NULL, MDL); + ddns_fwd_srv_connector(NULL, NULL, NULL, ddns_cb->next_op, eresult); + ddns_cb_free(ddns_cb, MDL); +} + + +/* + * Remove relevant entries from DNS. + * + * Return values: + * 0 - badness occurred and we weren't able to do what was wanted + * 1 - we were able to do stuff but it's in progress + * in both cases any additional block has been passed on to it's handler + */ + +int +ddns_removals(struct lease *lease, + struct iasubopt *lease6, + dhcp_ddns_cb_t *add_ddns_cb) +{ + isc_result_t rcode, execute_add = ISC_R_FAILURE; + struct binding_scope **scope = NULL; + int result = 0; + dhcp_ddns_cb_t *ddns_cb = NULL; + struct data_string leaseid; + + /* + * Cancel any outstanding requests. When called + * from within the DNS code we probably will have + * already done the cancel but if called from outside + * - for example as part of a lease expiry - we won't. + */ + if ((lease != NULL) && (lease->ddns_cb != NULL)) { + ddns_cancel(lease->ddns_cb); + lease->ddns_cb = NULL; + } else if ((lease6 != NULL) && (lease6->ddns_cb != NULL)) { + ddns_cancel(lease6->ddns_cb); + lease6->ddns_cb = NULL; + } else + goto cleanup; + + /* allocate our control block */ + ddns_cb = ddns_cb_alloc(MDL); + if (ddns_cb == NULL) { + goto cleanup; + } + + if (lease != NULL) { + scope = &(lease->scope); + ddns_cb->address = lease->ip_addr; + } else if (lease6 != NULL) { + scope = &(lease6->scope); + memcpy(&ddns_cb->address.iabuf, lease6->addr.s6_addr, 16); + ddns_cb->address.len = 16; + } else + goto cleanup; + + /* No scope implies that DDNS has not been performed for this lease. */ + if (*scope == NULL) + goto cleanup; + + if (ddns_update_style != 2) + goto cleanup; + + /* Assume that we are removing both records */ + ddns_cb->flags = DDNS_UPDATE_ADDR | DDNS_UPDATE_PTR; + + /* and that we want to do the add call */ + execute_add = ISC_R_SUCCESS; + + /* + * Look up stored names. + */ + + /* + * Find the fwd name and copy it to the control block. If we don't + * have it we can't delete the fwd record but we can still try to + * remove the ptr record and cleanup the lease information if the + * client did the fwd update. + */ + if (!find_bound_string(&ddns_cb->fwd_name, *scope, "ddns-fwd-name")) { + /* don't try and delete the A, or do the add */ + ddns_cb->flags &= ~DDNS_UPDATE_ADDR; + execute_add = ISC_R_FAILURE; + + /* Check if client did update */ + if (find_bound_string(&ddns_cb->fwd_name, *scope, + "ddns-client-fqdn")) { + ddns_cb->flags |= DDNS_CLIENT_DID_UPDATE; + } + } + + /* + * Find the ptr name and copy it to the control block. If we don't + * have it this isn't an interim or rfc3??? record so we can't delete + * the A record using this mechanism but we can delete the ptr record. + * In this case we will attempt to do any requested next step. + */ + memset(&leaseid, 0, sizeof(leaseid)); + if (!find_bound_string (&leaseid, *scope, "ddns-txt")) { + ddns_cb->flags &= ~DDNS_UPDATE_ADDR; + } else { + if (dhcid_fromlease(&ddns_cb->dhcid, &leaseid) != + ISC_R_SUCCESS) { + /* We couldn't convert the dhcid from the lease + * version to the dns version. We can't delete + * the A record but can continue to the ptr + */ + ddns_cb->flags &= ~DDNS_UPDATE_ADDR; + } + data_string_forget(&leaseid, MDL); + } + + /* + * Find the rev name and copy it to the control block. If we don't + * have it we can't get rid of it but we can try to remove the fwd + * pointer if desired. + */ + if (!find_bound_string(&ddns_cb->rev_name, *scope, "ddns-rev-name")) { + ddns_cb->flags &= ~DDNS_UPDATE_PTR; + } + + /* + * If we have a second control block for doing an add + * after the remove finished attach it to our control block. + */ + ddns_cb->next_op = add_ddns_cb; + + /* + * Now that we've collected the information we can try to process it. + * If necessary we call an appropriate routine to send a message and + * provide it with an action routine to run on the control block given + * the results of the message. We have three entry points from here, + * one for removing the A record, the next for removing the PTR and + * the third for doing any requested add. + */ + if ((ddns_cb->flags & DDNS_UPDATE_ADDR) != 0) { + if (ddns_cb->fwd_name.len != 0) { + ddns_cb->state = DDNS_STATE_REM_FW_YXDHCID; + ddns_cb->cur_func = ddns_fwd_srv_rem1; + + rcode = ddns_modify_fwd(ddns_cb); + if (rcode == ISC_R_SUCCESS) { + ddns_update_lease_ptr(lease, lease6, ddns_cb, + ddns_cb, MDL); + return(1); + } + + /* + * We weren't able to process the request tag the + * add so we won't execute it. + */ + execute_add = ISC_R_FAILURE; + goto cleanup; + } + else { + /*remove info from scope */ + unset(*scope, "ddns-fwd-name"); + unset(*scope, "ddns-txt"); + } + } + + if ((ddns_cb->flags & DDNS_UPDATE_PTR) != 0) { + ddns_cb->state = DDNS_STATE_REM_PTR; + ddns_cb->cur_func = ddns_ptr_remove; + + /* + * if execute add isn't success remove the control block so + * it won't be processed when the remove completes. We + * also arrange to clean it up and get rid of it. + */ + if (execute_add != ISC_R_SUCCESS) { + ddns_cb->next_op = NULL; + ddns_fwd_srv_connector(lease, lease6, scope, + add_ddns_cb, execute_add); + add_ddns_cb = NULL; + } + else { + result = 1; + } + + rcode = ddns_modify_ptr(ddns_cb); + if (rcode == ISC_R_SUCCESS) { + ddns_update_lease_ptr(lease, lease6, ddns_cb, ddns_cb, + MDL); + return(result); + } + + /* We weren't able to process the request tag the + * add so we won't execute it */ + execute_add = ISC_R_FAILURE; + goto cleanup; + } + + cleanup: + /* + * We've gotten here because we didn't need to send a message or + * we failed when trying to do so. We send the additional cb + * off to handle sending and/or cleanup and cleanup anything + * we allocated here. + */ + ddns_fwd_srv_connector(lease, lease6, scope, add_ddns_cb, execute_add); + if (ddns_cb != NULL) + ddns_cb_free(ddns_cb, MDL); + + return(result); +} + +#endif /* NSUPDATE */ |