Esempio n. 1
0
/* 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;
	struct iaddr addr;
	struct data_string d1;
	struct option_cache *oc;
	int s1, s2;
	int result = 0;
	isc_result_t rcode1 = ISC_R_SUCCESS, rcode2 = 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;

	if (ddns_update_style != 2)
		return 0;

	if (lease != NULL) {
		scope = &(lease->scope);
		addr = lease->ip_addr;
	} else if (lease6 != NULL) {
		scope = &(lease6->scope);
		memcpy(addr.iabuf, lease6->addr.s6_addr, 16);
		addr.len = 16;
	} else {
		log_fatal("Impossible condition at %s:%d.", MDL);
		/* Silence compiler warnings. */
		return 0;
	}

	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;
		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)) {
		return 0;
	}

	/* 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))
			return 0;
	}

	/*
	 * 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, try to delete
			   the old A record. */
			if (!ddns_removals(lease, lease6))
				goto out;
			/* If the delete succeeded, go install the new
			   record. */
			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)
		goto out;

	if (ddns_fwd_name.len > 255) {
		log_error ("client provided fqdn: too long");
		goto out;
	}

	/*
	 * Compute the RR TTL.
	 */
	ddns_ttl = DEFAULT_DDNS_TTL;
	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);
		}
	}

	/*
	 * 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 (addr.len == 4) {
		char buf[17];
		/* XXX: WOW this is gross. */
		rev_name_len = snprintf(buf, sizeof(buf), "%u.%u.%u.%u.",
					addr.iabuf[3] & 0xff,
					addr.iabuf[2] & 0xff,
					addr.iabuf[1] & 0xff,
					addr.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 (addr.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", addr.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)) {
		server_updates_ptr = 0;
	}

	if (s1) {
		i=buffer_allocate(&ddns_rev_name.buffer, rev_name_len, MDL);
		if(!i){
			log_error("buffer_allocate failed!\n");
		}
		if (ddns_rev_name.buffer != NULL) {
			ddns_rev_name.data = ddns_rev_name.buffer->data;

			if (addr.len == 4) {
				ddns_rev_name.len =
				    sprintf((char *)ddns_rev_name.buffer->data,
					    "%u.%u.%u.%u.", 
					    addr.iabuf[3] & 0xff,
					    addr.iabuf[2] & 0xff,
					    addr.iabuf[1] & 0xff,
					    addr.iabuf[0] & 0xff);

				/*
				 * d1.data may be opaque, garbage bytes, from
				 * user (mis)configuration.
				 */
				data_string_append(&ddns_rev_name, &d1);
				ddns_rev_name.buffer->data[ddns_rev_name.len] =
					'\0';
			} else if (addr.len == 16) {
				char *p = (char *)&ddns_rev_name.buffer->data;
				unsigned char *a = addr.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.");
				ddns_rev_name.len =
				    strlen((const char *)ddns_rev_name.data);
			}

			ddns_rev_name.terminated = 1;
		}
Esempio n. 2
0
int ddns_removals (struct lease *lease)
{
	struct data_string ddns_fwd_name;
	struct data_string ddns_rev_name;
	struct data_string ddns_dhcid;
	isc_result_t rcode;
	struct binding *binding;
	int result = 0;
	int client_updated = 0;

	/* No scope implies that DDNS has not been performed for this lease. */
	if (!lease -> scope)
		return 0;

	if (ddns_update_style != 2)
		return 0;

	/*
	 * Look up stored names.
	 */
	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));

	/*
	 * Start the resolver, if necessary.
	 */
	if (!resolver_inited) {
		minires_ninit (&resolver_state);
		resolver_inited = 1;
		resolver_state.retrans = 1;
		resolver_state.retry = 1;
	}

	/* We need the fwd name whether we are deleting both records or just
	   the PTR record, so if it's not there, we can't proceed. */
	if (!find_bound_string (&ddns_fwd_name,
				lease -> scope, "ddns-fwd-name")) {
		/* If there's no ddns-fwd-name, look for the client fqdn,
		   in case the client did the update. */
		if (!find_bound_string (&ddns_fwd_name,
					lease -> scope, "ddns-client-fqdn"))
			goto try_rev;
		client_updated = 1;
		goto try_rev;
	}

	/* If the ddns-txt binding isn't there, 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. */
	if (!find_bound_string (&ddns_dhcid, lease -> scope, "ddns-txt")) {
		result = 1;
		goto try_rev;
	}

	/*
	 * Perform removals.
	 */
	if (ddns_fwd_name.len)
		rcode = ddns_remove_a (&ddns_fwd_name,
				       lease -> ip_addr, &ddns_dhcid);
	else
		rcode = ISC_R_SUCCESS;

	if (rcode == ISC_R_SUCCESS) {
		result = 1;
		unset (lease -> scope, "ddns-fwd-name");
		unset (lease -> scope, "ddns-txt");
	      try_rev:
		if (find_bound_string (&ddns_rev_name,
				       lease -> scope, "ddns-rev-name")) {
			if (ddns_remove_ptr(&ddns_rev_name) == NOERROR) {
				unset (lease -> scope, "ddns-rev-name");
				if (client_updated)
					unset (lease -> scope,
					       "ddns-client-fqdn");
				/* XXX this is to compensate for a bug in
				   XXX 3.0rc8, and should be removed before
				   XXX 3.0pl1. */
				else if (!ddns_fwd_name.len)
					unset (lease -> scope, "ddns-text");
			} else
				result = 0;
		}
	}

	data_string_forget (&ddns_fwd_name, MDL);
	data_string_forget (&ddns_rev_name, MDL);
	data_string_forget (&ddns_dhcid, MDL);

	return result;
}
Esempio n. 3
0
void 
dhcpleasequery(struct packet *packet, int ms_nulltp) {
	char msgbuf[256];
	char dbg_info[128];
	struct iaddr cip;
	struct iaddr gip;
	struct data_string uid;
	struct hardware h;
	struct lease *tmp_lease;
	struct lease *lease;
	int want_associated_ip;
	int assoc_ip_cnt;
	u_int32_t assoc_ips[40];  /* XXXSK: arbitrary maximum number of IPs */
	const int nassoc_ips = sizeof(assoc_ips) / sizeof(assoc_ips[0]);

	unsigned char dhcpMsgType;
	const char *dhcp_msg_type_name;
	struct subnet *subnet;
	struct group *relay_group;
	struct option_state *options;
	struct option_cache *oc;
	int allow_leasequery;
	int ignorep;
	u_int32_t lease_duration;
	u_int32_t time_renewal;
	u_int32_t time_rebinding;
	u_int32_t time_expiry;
	u_int32_t client_last_transaction_time;
	struct sockaddr_in to;
	struct in_addr siaddr;
	struct data_string prl;
	struct data_string *prl_ptr;

	int i;
	struct interface_info *interface;

	/* INSIST(packet != NULL); */

	/*
	 * Prepare log information.
	 */
	snprintf(msgbuf, sizeof(msgbuf), 
		"DHCPLEASEQUERY from %s", inet_ntoa(packet->raw->giaddr));

	/* 
	 * We can't reply if there is no giaddr field.
	 */
	if (!packet->raw->giaddr.s_addr) {
		log_info("%s: missing giaddr, ciaddr is %s, no reply sent", 
			 msgbuf, inet_ntoa(packet->raw->ciaddr));
		return;
	}

	/* 
	 * Initially we use the 'giaddr' subnet options scope to determine if
	 * the giaddr-identified relay agent is permitted to perform a
	 * leasequery.  The subnet is not required, and may be omitted, in
	 * which case we are essentially interrogating the root options class
	 * to find a globally permit.
	 */
	gip.len = sizeof(packet->raw->giaddr);
	memcpy(gip.iabuf, &packet->raw->giaddr, sizeof(packet->raw->giaddr));

	subnet = NULL;
	find_subnet(&subnet, gip, MDL);
	if (subnet != NULL)
		relay_group = subnet->group;
	else
		relay_group = root_group;

	subnet_dereference(&subnet, MDL);

	options = NULL;
	if (!option_state_allocate(&options, MDL)) {
		log_error("No memory for option state.");
		log_info("%s: out of memory, no reply sent", msgbuf);
		return;
	}

	execute_statements_in_scope(NULL, packet, NULL, NULL, packet->options,
				    options, &global_scope, relay_group,
				    NULL, NULL);

	for (i=packet->class_count-1; i>=0; i--) {
		execute_statements_in_scope(NULL, packet, NULL, NULL,
					    packet->options, options,
					    &global_scope,
					    packet->classes[i]->group,
					    relay_group, NULL);
	}

	/* 
	 * Because LEASEQUERY has some privacy concerns, default to deny.
	 */
	allow_leasequery = 0;

	/*
	 * See if we are authorized to do LEASEQUERY.
	 */
	oc = lookup_option(&server_universe, options, SV_LEASEQUERY);
	if (oc != NULL) {
		allow_leasequery = evaluate_boolean_option_cache(&ignorep,
					 packet, NULL, NULL, packet->options,
					 options, &global_scope, oc, MDL);
	}

	if (!allow_leasequery) {
		log_info("%s: LEASEQUERY not allowed, query ignored", msgbuf);
		option_state_dereference(&options, MDL);
		return;
	}


	/* 
	 * Copy out the client IP address.
	 */
	cip.len = sizeof(packet->raw->ciaddr);
	memcpy(cip.iabuf, &packet->raw->ciaddr, sizeof(packet->raw->ciaddr));

	/* 
	 * If the client IP address is valid (not all zero), then we 
	 * are looking for information about that IP address.
	 */
	assoc_ip_cnt = 0;
	lease = tmp_lease = NULL;
	if (memcmp(cip.iabuf, "\0\0\0", 4)) {

		want_associated_ip = 0;

		snprintf(dbg_info, sizeof(dbg_info), "IP %s", piaddr(cip));
		find_lease_by_ip_addr(&lease, cip, MDL);


	} else {

		want_associated_ip = 1;

		/*
		 * If the client IP address is all zero, then we will
		 * either look up by the client identifier (if we have
		 * one), or by the MAC address.
		 */

		memset(&uid, 0, sizeof(uid));
		if (get_option(&uid, 
			       &dhcp_universe,
			       packet,
			       NULL,
			       NULL,
			       packet->options,
			       NULL,
			       packet->options, 
			       &global_scope,
			       DHO_DHCP_CLIENT_IDENTIFIER,
			       MDL)) {

			snprintf(dbg_info, 
				 sizeof(dbg_info), 
				 "client-id %s",
				 print_hex_1(uid.len, uid.data, 60));

			find_lease_by_uid(&tmp_lease, uid.data, uid.len, MDL);
			data_string_forget(&uid, MDL);
			get_newest_lease(&lease, tmp_lease, next_uid);
			assoc_ip_cnt = get_associated_ips(tmp_lease,
							  next_uid, 
							  lease,
							  assoc_ips, 
							  nassoc_ips);

		} else {

			if (packet->raw->hlen+1 > sizeof(h.hbuf)) {
				log_info("%s: hardware length too long, "
					 "no reply sent", msgbuf);
				option_state_dereference(&options, MDL);
				return;
			}

			h.hlen = packet->raw->hlen + 1;
			h.hbuf[0] = packet->raw->htype;
			memcpy(&h.hbuf[1], 
			       packet->raw->chaddr, 
			       packet->raw->hlen);

			snprintf(dbg_info, 
				 sizeof(dbg_info), 
				 "MAC address %s",
				 print_hw_addr(h.hbuf[0], 
					       h.hlen - 1, 
					       &h.hbuf[1]));

			find_lease_by_hw_addr(&tmp_lease, h.hbuf, h.hlen, MDL);
			get_newest_lease(&lease, tmp_lease, next_hw);
			assoc_ip_cnt = get_associated_ips(tmp_lease,
							  next_hw, 
							  lease,
							  assoc_ips, 
							  nassoc_ips);

		}

		lease_dereference(&tmp_lease, MDL);

		if (lease != NULL) {
			memcpy(&packet->raw->ciaddr, 
			       lease->ip_addr.iabuf,
			       sizeof(packet->raw->ciaddr));
		}

		/*
		 * Log if we have too many IP addresses associated
		 * with this client.
		 */
		if (want_associated_ip && (assoc_ip_cnt > nassoc_ips)) {
			log_info("%d IP addresses associated with %s, "
				 "only %d sent in reply.",
				 assoc_ip_cnt, dbg_info, nassoc_ips);
		}
	}

	/*
	 * We now know the query target too, so can report this in 
	 * our log message.
	 */
	snprintf(msgbuf, sizeof(msgbuf), 
		"DHCPLEASEQUERY from %s for %s",
		inet_ntoa(packet->raw->giaddr), dbg_info);

	/*
	 * Figure our our return type.
	 */
	if (lease == NULL) {
		dhcpMsgType = DHCPLEASEUNKNOWN;
		dhcp_msg_type_name = "DHCPLEASEUNKNOWN";
	} else {
		if (lease->binding_state == FTS_ACTIVE) {
			dhcpMsgType = DHCPLEASEACTIVE;
			dhcp_msg_type_name = "DHCPLEASEACTIVE";
		} else {
			dhcpMsgType = DHCPLEASEUNASSIGNED;
			dhcp_msg_type_name = "DHCPLEASEUNASSIGNED";
		}
	}

	/* 
	 * Set options that only make sense if we have an active lease.
	 */

	if (dhcpMsgType == DHCPLEASEACTIVE)
	{
		/*
		 * RFC 4388 uses the PRL to request options for the agent to
		 * receive that are "about" the client.  It is confusing
		 * because in some cases it wants to know what was sent to
		 * the client (lease times, adjusted), and in others it wants
		 * to know information the client sent.  You're supposed to
		 * know this on a case-by-case basis.
		 *
		 * "Name servers", "domain name", and the like from the relay
		 * agent's scope seems less than useful.  Our options are to
		 * restart the option cache from the lease's best point of view
		 * (execute statements from the lease pool's group), or to
		 * simply restart the option cache from empty.
		 *
		 * I think restarting the option cache from empty best
		 * approaches RFC 4388's intent; specific options are included.
		 */
		option_state_dereference(&options, MDL);

		if (!option_state_allocate(&options, MDL)) {
			log_error("%s: out of memory, no reply sent", msgbuf);
			lease_dereference(&lease, MDL);
			return;
		}

		/* 
		 * Set the hardware address fields.
		 */

		packet->raw->hlen = lease->hardware_addr.hlen - 1;
		packet->raw->htype = lease->hardware_addr.hbuf[0];
		memcpy(packet->raw->chaddr, 
		       &lease->hardware_addr.hbuf[1], 
		       sizeof(packet->raw->chaddr));

		/*
		 * Set client identifier option.
		 */
		if (lease->uid_len > 0) {
			if (!add_option(options,
					DHO_DHCP_CLIENT_IDENTIFIER,
					lease->uid,
					lease->uid_len)) {
				option_state_dereference(&options, MDL);
				lease_dereference(&lease, MDL);
				log_info("%s: out of memory, no reply sent",
					 msgbuf);
				return;
			}
		}


		/*
		 * Calculate T1 and T2, the times when the client
		 * tries to extend its lease on its networking
		 * address.
		 * These seem to be hard-coded in ISC DHCP, to 0.5 and
		 * 0.875 of the lease time.
		 */

		lease_duration = lease->ends - lease->starts;
		time_renewal = lease->starts + 
			(lease_duration / 2);
		time_rebinding = lease->starts + 
			(lease_duration / 2) +
			(lease_duration / 4) +
			(lease_duration / 8);

		if (time_renewal > cur_time) {
			time_renewal = htonl(time_renewal - cur_time);

			if (!add_option(options, 
					DHO_DHCP_RENEWAL_TIME,
					&time_renewal, 
					sizeof(time_renewal))) {
				option_state_dereference(&options, MDL);
				lease_dereference(&lease, MDL);
				log_info("%s: out of memory, no reply sent",
					 msgbuf);
				return;
			}
		}

		if (time_rebinding > cur_time) {
			time_rebinding = htonl(time_rebinding - cur_time);

			if (!add_option(options, 
					DHO_DHCP_REBINDING_TIME,
					&time_rebinding, 
					sizeof(time_rebinding))) {
				option_state_dereference(&options, MDL);
				lease_dereference(&lease, MDL);
				log_info("%s: out of memory, no reply sent",
					 msgbuf);
				return;
			}
		}

		if (lease->ends > cur_time) {
			time_expiry = htonl(lease->ends - cur_time);

			if (!add_option(options, 
					DHO_DHCP_LEASE_TIME,
					&time_expiry, 
					sizeof(time_expiry))) {
				option_state_dereference(&options, MDL);
				lease_dereference(&lease, MDL);
				log_info("%s: out of memory, no reply sent",
					 msgbuf);
				return;
			}
		}

		/* Supply the Vendor-Class-Identifier. */
		if (lease->scope != NULL) {
			struct data_string vendor_class;

			memset(&vendor_class, 0, sizeof(vendor_class));

			if (find_bound_string(&vendor_class, lease->scope,
					      "vendor-class-identifier")) {
				if (!add_option(options,
						DHO_VENDOR_CLASS_IDENTIFIER,
						(void *)vendor_class.data,
						vendor_class.len)) {
					option_state_dereference(&options,
								 MDL);
					lease_dereference(&lease, MDL);
					log_error("%s: error adding vendor "
						  "class identifier, no reply "
						  "sent", msgbuf);
					data_string_forget(&vendor_class, MDL);
					return;
				}
				data_string_forget(&vendor_class, MDL);
			}
		}

		/*
		 * Set the relay agent info.
		 *
		 * Note that because agent info is appended without regard
		 * to the PRL in cons_options(), this will be sent as the
		 * last option in the packet whether it is listed on PRL or
		 * not.
		 */

		if (lease->agent_options != NULL) {
			int idx = agent_universe.index;
			struct option_chain_head **tmp1 = 
				(struct option_chain_head **)
				&(options->universes[idx]);
				struct option_chain_head *tmp2 = 
				(struct option_chain_head *)
				lease->agent_options;

			option_chain_head_reference(tmp1, tmp2, MDL);
		}

		/* 
	 	 * Set the client last transaction time.
		 * We check to make sure we have a timestamp. For
		 * lease files that were saved before running a 
		 * timestamp-aware version of the server, this may
		 * not be set.
	 	 */

		if (lease->cltt != MIN_TIME) {
			if (cur_time > lease->cltt) {
				client_last_transaction_time = 
					htonl(cur_time - lease->cltt);
			} else {
				client_last_transaction_time = htonl(0);
			}
			if (!add_option(options, 
					DHO_CLIENT_LAST_TRANSACTION_TIME,
					&client_last_transaction_time,
		     			sizeof(client_last_transaction_time))) {
				option_state_dereference(&options, MDL);
				lease_dereference(&lease, MDL);
				log_info("%s: out of memory, no reply sent",
					 msgbuf);
				return;
			}
		}

		/*
	 	 * Set associated IPs, if requested and there are some.
	 	 */
		if (want_associated_ip && (assoc_ip_cnt > 0)) {
			if (!add_option(options, 
					DHO_ASSOCIATED_IP,
					assoc_ips,
					assoc_ip_cnt * sizeof(assoc_ips[0]))) {
				option_state_dereference(&options, MDL);
				lease_dereference(&lease, MDL);
				log_info("%s: out of memory, no reply sent",
					 msgbuf);
				return;
			}
		}
	}

	/* 
	 * Set the message type.
	 */

	packet->raw->op = BOOTREPLY;

	/*
	 * Set DHCP message type.
	 */
	if (!add_option(options, 
		        DHO_DHCP_MESSAGE_TYPE,
		        &dhcpMsgType, 
			sizeof(dhcpMsgType))) {
		option_state_dereference(&options, MDL);
		lease_dereference(&lease, MDL);
		log_info("%s: error adding option, no reply sent", msgbuf);
		return;
	}

	/*
	 * Log the message we've received.
	 */
	log_info("%s", msgbuf);

	/*
	 * Figure out which address to use to send from.
	 */
	get_server_source_address(&siaddr, options, options, packet);

	/* 
	 * Set up the option buffer.
	 */

	memset(&prl, 0, sizeof(prl));
	oc = lookup_option(&dhcp_universe, options, 
			   DHO_DHCP_PARAMETER_REQUEST_LIST);
	if (oc != NULL) {
		evaluate_option_cache(&prl, 
				      packet, 
				      NULL,
				      NULL,
				      packet->options,
				      options,
				      &global_scope,
				      oc,
				      MDL);
	}
	if (prl.len > 0) {
		prl_ptr = &prl;
	} else {
		prl_ptr = NULL;
	}

	packet->packet_length = cons_options(packet, 
					     packet->raw, 
					     lease,
					     NULL,
					     0,
					     packet->options,
					     options,
					     &global_scope,
					     0,
					     0,
					     0, 
					     prl_ptr,
					     NULL);

	data_string_forget(&prl, MDL);	/* SK: safe, even if empty */
	option_state_dereference(&options, MDL);
	lease_dereference(&lease, MDL);

	to.sin_family = AF_INET;
#ifdef HAVE_SA_LEN
	to.sin_len = sizeof(to);
#endif
	memset(to.sin_zero, 0, sizeof(to.sin_zero));

	/* 
	 * Leasequery packets are be sent to the gateway address.
	 */
	to.sin_addr = packet->raw->giaddr;
	if (packet->raw->giaddr.s_addr != htonl(INADDR_LOOPBACK)) {
		to.sin_port = local_port;
	} else {
		to.sin_port = remote_port; /* XXXSK: For debugging. */
	}

	/* 
	 * The fallback_interface lets us send with a real IP
	 * address. The packet interface sends from all-zeros.
	 */
	if (fallback_interface != NULL) {
		interface = fallback_interface;
	} else {
		interface = packet->interface;
	}

	/*
	 * Report what we're sending.
	 */
	log_info("%s to %s for %s (%d associated IPs)",
		dhcp_msg_type_name, 
		inet_ntoa(to.sin_addr), dbg_info, assoc_ip_cnt);

	send_packet(interface,
		    NULL,
		    packet->raw, 
		    packet->packet_length,
		    siaddr,
		    &to,
		    NULL);
}
Esempio n. 4
0
int ddns_updates (struct packet *packet,
		  struct lease *lease, struct lease *old,
		  struct lease_state *state)
{
	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;
	unsigned len;
	struct data_string d1;
	struct option_cache *oc;
	int s1, s2;
	int result = 0;
	isc_result_t rcode1 = ISC_R_SUCCESS, rcode2 = 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;

	if (ddns_update_style != 2)
		return 0;

	/* Can only cope with IPv4 addrs at the moment. */
	if (lease -> ip_addr . len != 4)
		return 0;

	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, state->options,
				 SV_CLIENT_UPDATES)) ||
	    evaluate_boolean_option_cache(&client_ignorep, packet, lease, NULL,
					  packet->options, state->options,
					  &lease->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,
						   (struct client_state *)0,
						   packet -> options,
						   state -> options,
						   &lease -> 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,
					    (struct client_state *)0,
					    packet -> options,
					    state -> options,
					    &lease -> scope, oc, MDL))
			goto noclient;
		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, state -> options,
				 SV_DO_FORWARD_UPDATES)) &&
	    !evaluate_boolean_option_cache (&ignorep, packet, lease,
					    (struct client_state *)0,
					    packet -> options,
					    state -> options,
					    &lease -> scope, oc, MDL)) {
		return 0;
	}

	/* 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. */
	if (lease -> flags & STATIC_LEASE) {
		if (!(oc = lookup_option (&server_universe, state -> options,
					  SV_UPDATE_STATIC_LEASES)) ||
		    !evaluate_boolean_option_cache (&ignorep, packet, lease,
						    (struct client_state *)0,
						    packet -> options,
						    state -> options,
						    &lease -> scope, oc, MDL))
			return 0;
	}

	/*
	 * Compute the name for the A record.
	 */
	oc = lookup_option (&server_universe, state -> options,
			    SV_DDNS_HOST_NAME);
	if (oc)
		s1 = evaluate_option_cache (&ddns_hostname, packet, lease,
					    (struct client_state *)0,
					    packet -> options,
					    state -> options,
					    &lease -> scope, oc, MDL);
	else
		s1 = 0;

	oc = lookup_option (&server_universe, state -> options,
			    SV_DDNS_DOMAIN_NAME);
	if (oc)
		s2 = evaluate_option_cache (&ddns_domainname, packet, lease,
					    (struct client_state *)0,
					    packet -> options,
					    state -> options,
					    &lease -> 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,
			       lease -> 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, try to delete
			   the old A record. */
			if (!ddns_removals (lease))
				goto out;
			/* If the delete succeeded, go install the new
			   record. */
			goto in;
		}

		/* See if there's a DHCID on the lease. */
		if (!find_bound_string (&ddns_dhcid,
					lease -> scope, "ddns-txt")) {
			/* 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 ((struct binding_value **)0,
						    (struct packet *)0, lease,
						    (struct client_state *)0,
						    (struct option_state *)0,
						    (struct option_state *)0,
						    &lease -> 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, state -> options,
					  SV_UPDATE_OPTIMIZATION)) ||
		    evaluate_boolean_option_cache (&ignorep, packet, lease,
						   (struct client_state *)0,
						   packet -> options,
						   state -> options,
						   &lease -> 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, lease->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,
					   state -> options,
					   SV_UPDATE_OPTIMIZATION)) ||
		     evaluate_boolean_option_cache (&ignorep, packet, lease,
						    (struct client_state *)0,
						    packet -> options,
						    state -> options,
						    &lease -> 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)
		goto out;

	if (ddns_fwd_name.len > 255) {
		log_error ("client provided fqdn: too long");
		goto out;
	}

	/*
	 * Compute the RR TTL.
	 */
	ddns_ttl = DEFAULT_DDNS_TTL;
	if ((oc = lookup_option (&server_universe, state -> options,
				 SV_DDNS_TTL))) {
		if (evaluate_option_cache (&d1, packet, lease,
					   (struct client_state *)0,
					   packet -> options,
					   state -> options,
					   &lease -> scope, oc, MDL)) {
			if (d1.len == sizeof (u_int32_t))
				ddns_ttl = getULong (d1.data);
			data_string_forget (&d1, MDL);
		}
	}

	/* CC: see if we are configured NOT to do reverse ptr updates
        */
	if ((oc = lookup_option (&server_universe, state -> options,
				 SV_DO_REVERSE_UPDATES)) &&
	    !evaluate_boolean_option_cache (&ignorep, packet, lease,
					    (struct client_state *)0,
					    packet -> options,
					    state -> options,
					    &lease -> scope, oc, MDL)) {
		server_updates_ptr = 0;
	}

	/*
	 * Compute the reverse IP name.
	 */
	oc = lookup_option (&server_universe, state -> options,
			    SV_DDNS_REV_DOMAIN_NAME);
	if (oc)
		s1 = evaluate_option_cache (&d1, packet, lease,
					    (struct client_state *)0,
					    packet -> options,
					    state -> options,
					    &lease -> scope, oc, MDL);
	else
		s1 = 0;

	if (s1 && (d1.len > 238)) {
		log_error ("ddns_update: Calculated rev domain name too long.");
		s1 = 0;
		data_string_forget (&d1, MDL);
	}

	if (oc && s1) {
		/* Buffer length:
		   XXX.XXX.XXX.XXX.<ddns-rev-domain-name>\0 */
		buffer_allocate (&ddns_rev_name.buffer,
				 d1.len + 17, MDL);
		if (ddns_rev_name.buffer) {
			ddns_rev_name.data = ddns_rev_name.buffer -> data;

			/* %Audit% Cannot exceed 17 bytes. %2004.06.17,Safe% */
			sprintf ((char *)ddns_rev_name.buffer -> data,
				  "%u.%u.%u.%u.",
				  lease -> ip_addr . iabuf[3] & 0xff,
				  lease -> ip_addr . iabuf[2] & 0xff,
				  lease -> ip_addr . iabuf[1] & 0xff,
				  lease -> ip_addr . iabuf[0] & 0xff);

			ddns_rev_name.len =
				strlen ((const char *)ddns_rev_name.data);
			data_string_append (&ddns_rev_name, &d1);
			ddns_rev_name.buffer -> data [ddns_rev_name.len] ='\0';
			ddns_rev_name.terminated = 1;
		}
		
		data_string_forget (&d1, MDL);
	}

	/*
	 * If we are updating the A record, compute the DHCID value.
	 */
	if (server_updates_a) {
		if (lease -> uid && lease -> uid_len)
			result = get_dhcid (&ddns_dhcid,
					    DHO_DHCP_CLIENT_IDENTIFIER,
					    lease -> uid, lease -> uid_len);
		else
			result = get_dhcid (&ddns_dhcid, 0,
					    lease -> hardware_addr.hbuf,
					    lease -> hardware_addr.hlen);
		if (!result)
			goto badfqdn;
	}

	/*
	 * Start the resolver, if necessary.
	 */
	if (!resolver_inited) {
		minires_ninit (&resolver_state);
		resolver_inited = 1;
		resolver_state.retrans = 1;
		resolver_state.retry = 1;
	}

	/*
	 * Perform updates.
	 */
	if (ddns_fwd_name.len && ddns_dhcid.len) {
		unsigned conflict;

		oc = lookup_option(&server_universe, state->options,
				   SV_DDNS_CONFLICT_DETECT);
		if (!oc ||
		    evaluate_boolean_option_cache(&ignorep, packet, lease,
						  NULL, packet->options,
						  state->options,
						  &lease->scope, oc, MDL))
			conflict = 1;
		else
			conflict = 0;

		rcode1 = ddns_update_a (&ddns_fwd_name, lease -> ip_addr,
					&ddns_dhcid, ddns_ttl, 0, conflict);
	}

	if (rcode1 == ISC_R_SUCCESS && server_updates_ptr) {
		if (ddns_fwd_name.len && ddns_rev_name.len)
			rcode2 = ddns_update_ptr (&ddns_fwd_name,
						  &ddns_rev_name, ddns_ttl);
	} else
		rcode2 = rcode1;

	if (rcode1 == ISC_R_SUCCESS &&
	    (server_updates_a || rcode2 == ISC_R_SUCCESS)) {
		bind_ds_value (&lease -> scope, 
			       (server_updates_a
				? "ddns-fwd-name" : "ddns-client-fqdn"),
			       &ddns_fwd_name);
		if (server_updates_a)
			bind_ds_value (&lease -> scope, "ddns-txt",
				       &ddns_dhcid);
	}

	if (rcode2 == ISC_R_SUCCESS && server_updates_ptr) {
		bind_ds_value (&lease -> scope, "ddns-rev-name",
			       &ddns_rev_name);
	}

      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, state->options,
  				 SV_FQDN_REPLY)) &&
  	    !evaluate_boolean_option_cache (&ignorep, packet, lease,
  					    (struct client_state *)0,
  					    packet->options,
  					    state->options,
  					    &lease->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, state->options,
					   &lease->scope, oc, MDL)) {
		oc = lookup_option(&fqdn_universe, packet->options, FQDN_FQDN);
		if (oc && evaluate_option_cache(&d1, packet, lease, NULL,
						packet->options, state->options,
						&global_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, state->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, state->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,
							state->options,
							&lease->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, state->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, state->options,
						bp, &bp->data[3], 1,
						FQDN_RCODE1, 0))
				goto badfqdn;

			bp->data[4] = 255;
			if (!save_option_buffer(&fqdn_universe, state->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, state->options,
						bp, &bp->data[5], 1,
						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, state -> options,
					 bp, &bp -> data [0], 1,
					 FQDN_SERVER_UPDATE, 0))
			goto badfqdn;
		bp -> data [1] = server_updates_a;
		if (!save_option_buffer (&fqdn_universe, state -> 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,
						  state->options,
						  &lease->scope, oc, MDL))
			bp -> data [2] = 1;
		else
			bp -> data [2] = 0;
		if (!save_option_buffer (&fqdn_universe, state -> options,
					 bp, &bp -> data [2], 1,
					 FQDN_ENCODED, 0))
			goto badfqdn;
		bp -> data [3] = isc_rcode_to_ns (rcode1);
		if (!save_option_buffer (&fqdn_universe, state -> options,
					 bp, &bp -> data [3], 1,
					 FQDN_RCODE1, 0))
			goto badfqdn;
		bp -> data [4] = isc_rcode_to_ns (rcode2);
		if (!save_option_buffer (&fqdn_universe, state -> 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, state -> options,
					     bp, &bp -> data [5],
					     ddns_fwd_name.len,
					     FQDN_FQDN, 0))
			goto badfqdn;
		}
	}

      badfqdn:
      out:
	/*
	 * Final cleanup.
	 */
	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;
}
Esempio n. 5
0
/* 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(old->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);
	}