3 Dynamic DNS updates. */
6 * Copyright (c) 2004-2005 by Internet Systems Consortium, Inc. ("ISC")
7 * Copyright (c) 2000-2003 by Internet Software Consortium
9 * Permission to use, copy, modify, and distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
13 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
19 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 * Internet Systems Consortium, Inc.
23 * Redwood City, CA 94063
27 * This software has been donated to Internet Systems Consortium
28 * by Damien Neil of Nominum, Inc.
30 * To learn more about Internet Systems Consortium, see
31 * ``http://www.isc.org/''. To learn more about Nominum, Inc., see
32 * ``http://www.nominum.com''.
36 static char copyright
[] =
37 "$Id$ Copyright (c) 2004-2005 Internet Systems Consortium. All rights reserved.\n";
42 #include "minires/minires.h"
46 /* DN: No way of checking that there is enough space in a data_string's
47 buffer. Be certain to allocate enough!
48 TL: This is why the expression evaluation code allocates a *new*
50 static void data_string_append (struct data_string
*ds1
,
51 struct data_string
*ds2
)
53 memcpy (ds1
-> buffer
-> data
+ ds1
-> len
,
56 ds1
-> len
+= ds2
-> len
;
59 static isc_result_t
ddns_update_ptr (struct data_string
*ddns_fwd_name
,
60 struct data_string
*ddns_rev_name
,
65 isc_result_t result
= ISC_R_UNEXPECTED
;
68 * The DHCP server submits a DNS query which deletes all of the PTR RRs
69 * associated with the lease IP address, and adds a PTR RR whose data
70 * is the client's (possibly disambiguated) host name. The server also
71 * adds a DHCID RR specified in Section 4.3.
72 * -- "Interaction between DHCP and DNS"
75 ISC_LIST_INIT (updqueue
);
80 updrec
= minires_mkupdrec (S_UPDATE
,
81 (const char *)ddns_rev_name
-> data
,
84 result
= ISC_R_NOMEMORY
;
88 updrec
-> r_data
= (unsigned char *)0;
90 updrec
-> r_opcode
= DELETE
;
92 ISC_LIST_APPEND (updqueue
, updrec
, r_link
);
97 updrec
= minires_mkupdrec (S_UPDATE
,
98 (const char *)ddns_rev_name
-> data
,
101 result
= ISC_R_NOMEMORY
;
105 updrec
-> r_data
= ddns_fwd_name
-> data
;
106 updrec
-> r_size
= ddns_fwd_name
-> len
;
107 updrec
-> r_opcode
= ADD
;
109 ISC_LIST_APPEND (updqueue
, updrec
, r_link
);
112 * Attempt to perform the update.
114 result
= minires_nupdate (&resolver_state
, ISC_LIST_HEAD (updqueue
));
116 print_dns_status ((int)result
, &updqueue
);
118 if (result
== ISC_R_SUCCESS
) {
119 log_info ("added reverse map from %.*s to %.*s",
120 (int)ddns_rev_name
-> len
,
121 (const char *)ddns_rev_name
-> data
,
122 (int)ddns_fwd_name
-> len
,
123 (const char *)ddns_fwd_name
-> data
);
125 log_error ("unable to add reverse map from %.*s to %.*s: %s",
126 (int)ddns_rev_name
-> len
,
127 (const char *)ddns_rev_name
-> data
,
128 (int)ddns_fwd_name
-> len
,
129 (const char *)ddns_fwd_name
-> data
,
130 isc_result_totext (result
));
136 while (!ISC_LIST_EMPTY (updqueue
)) {
137 updrec
= ISC_LIST_HEAD (updqueue
);
138 ISC_LIST_UNLINK (updqueue
, updrec
, r_link
);
139 minires_freeupdrec (updrec
);
146 static isc_result_t
ddns_remove_ptr (struct data_string
*ddns_rev_name
)
153 * When a lease expires or a DHCP client issues a DHCPRELEASE request,
154 * the DHCP server SHOULD delete the PTR RR that matches the DHCP
155 * binding, if one was successfully added. The server's update query
156 * SHOULD assert that the name in the PTR record matches the name of
157 * the client whose lease has expired or been released.
158 * -- "Interaction between DHCP and DNS"
161 ISC_LIST_INIT (updqueue
);
164 * Delete the PTR RRset for the leased address.
166 updrec
= minires_mkupdrec (S_UPDATE
,
167 (const char *)ddns_rev_name
-> data
,
170 result
= ISC_R_NOMEMORY
;
174 updrec
-> r_data
= (unsigned char *)0;
175 updrec
-> r_size
= 0;
176 updrec
-> r_opcode
= DELETE
;
178 ISC_LIST_APPEND (updqueue
, updrec
, r_link
);
181 * Attempt to perform the update.
183 result
= minires_nupdate (&resolver_state
, ISC_LIST_HEAD (updqueue
));
185 print_dns_status ((int)result
, &updqueue
);
187 if (result
== ISC_R_SUCCESS
) {
188 log_info ("removed reverse map on %.*s",
189 (int)ddns_rev_name
-> len
,
190 (const char *)ddns_rev_name
-> data
);
192 if (result
!= ISC_R_NXRRSET
&& result
!= ISC_R_NXDOMAIN
)
193 log_error ("can't remove reverse map on %.*s: %s",
194 (int)ddns_rev_name
-> len
,
195 (const char *)ddns_rev_name
-> data
,
196 isc_result_totext (result
));
199 /* Not there is success. */
200 if (result
== ISC_R_NXRRSET
|| result
== ISC_R_NXDOMAIN
)
201 result
= ISC_R_SUCCESS
;
206 while (!ISC_LIST_EMPTY (updqueue
)) {
207 updrec
= ISC_LIST_HEAD (updqueue
);
208 ISC_LIST_UNLINK (updqueue
, updrec
, r_link
);
209 minires_freeupdrec (updrec
);
216 int ddns_updates (struct packet
*packet
,
217 struct lease
*lease
, struct lease
*old
,
218 struct lease_state
*state
)
220 unsigned long ddns_ttl
= DEFAULT_DDNS_TTL
;
221 struct data_string ddns_hostname
;
222 struct data_string ddns_domainname
;
223 struct data_string old_ddns_fwd_name
;
224 struct data_string ddns_fwd_name
;
225 struct data_string ddns_rev_name
;
226 struct data_string ddns_dhcid
;
228 struct data_string d1
;
229 struct option_cache
*oc
;
232 isc_result_t rcode1
= ISC_R_SUCCESS
, rcode2
= ISC_R_SUCCESS
;
233 int server_updates_a
= 1;
234 struct buffer
*bp
= (struct buffer
*)0;
237 if (ddns_update_style
!= 2)
240 /* Can only cope with IPv4 addrs at the moment. */
241 if (lease
-> ip_addr
. len
!= 4)
244 memset (&ddns_hostname
, 0, sizeof (ddns_hostname
));
245 memset (&ddns_domainname
, 0, sizeof (ddns_domainname
));
246 memset (&old_ddns_fwd_name
, 0, sizeof (ddns_fwd_name
));
247 memset (&ddns_fwd_name
, 0, sizeof (ddns_fwd_name
));
248 memset (&ddns_rev_name
, 0, sizeof (ddns_rev_name
));
249 memset (&ddns_dhcid
, 0, sizeof (ddns_dhcid
));
251 /* If we are allowed to accept the client's update of its own A
252 record, see if the client wants to update its own A record. */
253 if (!(oc
= lookup_option (&server_universe
, state
-> options
,
254 SV_CLIENT_UPDATES
)) ||
255 evaluate_boolean_option_cache (&ignorep
, packet
, lease
,
256 (struct client_state
*)0,
259 &lease
-> scope
, oc
, MDL
)) {
260 /* If there's no fqdn.no-client-update or if it's
261 nonzero, don't try to use the client-supplied
263 if (!(oc
= lookup_option (&fqdn_universe
, packet
-> options
,
264 FQDN_SERVER_UPDATE
)) ||
265 evaluate_boolean_option_cache (&ignorep
, packet
, lease
,
266 (struct client_state
*)0,
269 &lease
-> scope
, oc
, MDL
))
271 /* Win98 and Win2k will happily claim to be willing to
272 update an unqualified domain name. */
273 if (!(oc
= lookup_option (&fqdn_universe
, packet
-> options
,
276 if (!(oc
= lookup_option (&fqdn_universe
, packet
-> options
,
278 !evaluate_option_cache (&ddns_fwd_name
, packet
, lease
,
279 (struct client_state
*)0,
282 &lease
-> scope
, oc
, MDL
))
284 server_updates_a
= 0;
288 /* If do-forward-updates is disabled, this basically means don't
289 do an update unless the client is participating, so if we get
290 here and do-forward-updates is disabled, we can stop. */
291 if ((oc
= lookup_option (&server_universe
, state
-> options
,
292 SV_DO_FORWARD_UPDATES
)) &&
293 !evaluate_boolean_option_cache (&ignorep
, packet
, lease
,
294 (struct client_state
*)0,
297 &lease
-> scope
, oc
, MDL
)) {
301 /* If it's a static lease, then don't do the DNS update unless we're
302 specifically configured to do so. If the client asked to do its
303 own update and we allowed that, we don't do this test. */
304 if (lease
-> flags
& STATIC_LEASE
) {
305 if (!(oc
= lookup_option (&server_universe
, state
-> options
,
306 SV_UPDATE_STATIC_LEASES
)) ||
307 !evaluate_boolean_option_cache (&ignorep
, packet
, lease
,
308 (struct client_state
*)0,
311 &lease
-> scope
, oc
, MDL
))
316 * Compute the name for the A record.
318 oc
= lookup_option (&server_universe
, state
-> options
,
321 s1
= evaluate_option_cache (&ddns_hostname
, packet
, lease
,
322 (struct client_state
*)0,
325 &lease
-> scope
, oc
, MDL
);
329 oc
= lookup_option (&server_universe
, state
-> options
,
330 SV_DDNS_DOMAIN_NAME
);
332 s2
= evaluate_option_cache (&ddns_domainname
, packet
, lease
,
333 (struct client_state
*)0,
336 &lease
-> scope
, oc
, MDL
);
341 if (ddns_hostname
.len
+ ddns_domainname
.len
> 253) {
342 log_error ("ddns_update: host.domain name too long");
347 buffer_allocate (&ddns_fwd_name
.buffer
,
348 ddns_hostname
.len
+ ddns_domainname
.len
+ 2,
350 if (ddns_fwd_name
.buffer
) {
351 ddns_fwd_name
.data
= ddns_fwd_name
.buffer
-> data
;
352 data_string_append (&ddns_fwd_name
, &ddns_hostname
);
353 ddns_fwd_name
.buffer
-> data
[ddns_fwd_name
.len
] = '.';
355 data_string_append (&ddns_fwd_name
, &ddns_domainname
);
356 ddns_fwd_name
.buffer
-> data
[ddns_fwd_name
.len
] ='\0';
357 ddns_fwd_name
.terminated
= 1;
362 /* See if there's a name already stored on the lease. */
363 if (find_bound_string (&old_ddns_fwd_name
,
364 lease
-> scope
, "ddns-fwd-name")) {
365 /* If there is, see if it's different. */
366 if (old_ddns_fwd_name
.len
!= ddns_fwd_name
.len
||
367 memcmp (old_ddns_fwd_name
.data
, ddns_fwd_name
.data
,
368 old_ddns_fwd_name
.len
)) {
369 /* If the name is different, try to delete
371 if (!ddns_removals (lease
))
373 /* If the delete succeeded, go install the new
378 /* See if there's a DHCID on the lease. */
379 if (!find_bound_string (&ddns_dhcid
,
380 lease
-> scope
, "ddns-txt")) {
381 /* If there's no DHCID, the update was probably
382 done with the old-style ad-hoc DDNS updates.
383 So if the expiry and release events look like
384 they're the same, run them. This should delete
385 the old DDNS data. */
386 if (old
-> on_expiry
== old
-> on_release
) {
387 execute_statements ((struct binding_value
**)0,
388 (struct packet
*)0, lease
,
389 (struct client_state
*)0,
390 (struct option_state
*)0,
391 (struct option_state
*)0,
394 if (old
-> on_expiry
)
395 executable_statement_dereference
396 (&old
-> on_expiry
, MDL
);
397 if (old
-> on_release
)
398 executable_statement_dereference
399 (&old
-> on_release
, MDL
);
400 /* Now, install the DDNS data the new way. */
405 /* See if the administrator wants to do updates even
406 in cases where the update already appears to have been
408 if (!(oc
= lookup_option (&server_universe
, state
-> options
,
409 SV_UPDATE_OPTIMIZATION
)) ||
410 evaluate_boolean_option_cache (&ignorep
, packet
, lease
,
411 (struct client_state
*)0,
414 &lease
-> scope
, oc
, MDL
)) {
420 /* If there's no ddns-fwd-name on the lease, see if there's
421 a ddns-client-fqdn, indicating a prior client FQDN update.
422 If there is, and if we're still doing the client update,
423 see if the name has changed. If it hasn't, don't do the
425 if (find_bound_string (&old_ddns_fwd_name
,
426 lease
-> scope
, "ddns-client-fqdn")) {
427 /* If the name is not different, no need to update
429 if (old_ddns_fwd_name
.len
== ddns_fwd_name
.len
&&
430 !memcmp (old_ddns_fwd_name
.data
, ddns_fwd_name
.data
,
431 old_ddns_fwd_name
.len
) &&
432 (!(oc
= lookup_option (&server_universe
,
434 SV_UPDATE_OPTIMIZATION
)) ||
435 evaluate_boolean_option_cache (&ignorep
, packet
, lease
,
436 (struct client_state
*)0,
446 /* If we don't have a name that the client has been assigned, we
447 can just skip all this. */
448 if (!ddns_fwd_name
.len
)
451 if (ddns_fwd_name
.len
> 255) {
452 log_error ("client provided fqdn: too long");
457 * Compute the RR TTL.
459 ddns_ttl
= DEFAULT_DDNS_TTL
;
460 memset (&d1
, 0, sizeof d1
);
461 if ((oc
= lookup_option (&server_universe
, state
-> options
,
463 if (evaluate_option_cache (&d1
, packet
, lease
,
464 (struct client_state
*)0,
467 &lease
-> scope
, oc
, MDL
)) {
468 if (d1
.len
== sizeof (u_int32_t
))
469 ddns_ttl
= getULong (d1
.data
);
470 data_string_forget (&d1
, MDL
);
476 * Compute the reverse IP name.
478 oc
= lookup_option (&server_universe
, state
-> options
,
479 SV_DDNS_REV_DOMAIN_NAME
);
481 s1
= evaluate_option_cache (&d1
, packet
, lease
,
482 (struct client_state
*)0,
485 &lease
-> scope
, oc
, MDL
);
489 if (s1
&& (d1
.len
> 238)) {
490 log_error ("ddns_update: Calculated rev domain name too long.");
492 data_string_forget (&d1
, MDL
);
497 XXX.XXX.XXX.XXX.<ddns-rev-domain-name>\0 */
498 buffer_allocate (&ddns_rev_name
.buffer
,
500 if (ddns_rev_name
.buffer
) {
501 ddns_rev_name
.data
= ddns_rev_name
.buffer
-> data
;
503 /* %Audit% Cannot exceed 17 bytes. %2004.06.17,Safe% */
504 sprintf ((char *)ddns_rev_name
.buffer
-> data
,
506 lease
-> ip_addr
. iabuf
[3] & 0xff,
507 lease
-> ip_addr
. iabuf
[2] & 0xff,
508 lease
-> ip_addr
. iabuf
[1] & 0xff,
509 lease
-> ip_addr
. iabuf
[0] & 0xff);
512 strlen ((const char *)ddns_rev_name
.data
);
513 data_string_append (&ddns_rev_name
, &d1
);
514 ddns_rev_name
.buffer
-> data
[ddns_rev_name
.len
] ='\0';
515 ddns_rev_name
.terminated
= 1;
518 data_string_forget (&d1
, MDL
);
522 * If we are updating the A record, compute the DHCID value.
524 if (server_updates_a
) {
525 memset (&ddns_dhcid
, 0, sizeof ddns_dhcid
);
526 if (lease
-> uid
&& lease
-> uid_len
)
527 result
= get_dhcid (&ddns_dhcid
,
528 DHO_DHCP_CLIENT_IDENTIFIER
,
529 lease
-> uid
, lease
-> uid_len
);
531 result
= get_dhcid (&ddns_dhcid
, 0,
532 lease
-> hardware_addr
.hbuf
,
533 lease
-> hardware_addr
.hlen
);
539 * Start the resolver, if necessary.
541 if (!resolver_inited
) {
542 minires_ninit (&resolver_state
);
544 resolver_state
.retrans
= 1;
545 resolver_state
.retry
= 1;
551 if (ddns_fwd_name
.len
&& ddns_dhcid
.len
)
552 rcode1
= ddns_update_a (&ddns_fwd_name
, lease
-> ip_addr
,
553 &ddns_dhcid
, ddns_ttl
, 0);
555 if (rcode1
== ISC_R_SUCCESS
) {
556 if (ddns_fwd_name
.len
&& ddns_rev_name
.len
)
557 rcode2
= ddns_update_ptr (&ddns_fwd_name
,
558 &ddns_rev_name
, ddns_ttl
);
562 if (rcode1
== ISC_R_SUCCESS
&&
563 (server_updates_a
|| rcode2
== ISC_R_SUCCESS
)) {
564 bind_ds_value (&lease
-> scope
,
566 ? "ddns-fwd-name" : "ddns-client-fqdn"),
568 if (server_updates_a
)
569 bind_ds_value (&lease
-> scope
, "ddns-txt",
573 if (rcode2
== ISC_R_SUCCESS
) {
574 bind_ds_value (&lease
-> scope
, "ddns-rev-name",
578 /* Set up the outgoing FQDN option if there was an incoming
579 FQDN option. If there's a valid FQDN option, there should
580 be an FQDN_ENCODED suboption, so we test the latter to
581 detect the presence of the former. */
583 if ((oc
= lookup_option (&fqdn_universe
,
584 packet
-> options
, FQDN_ENCODED
))
585 && buffer_allocate (&bp
, ddns_fwd_name
.len
+ 5, MDL
)) {
586 bp
-> data
[0] = server_updates_a
;
587 if (!save_option_buffer (&fqdn_universe
, state
-> options
,
588 bp
, &bp
-> data
[0], 1,
589 &fqdn_options
[FQDN_SERVER_UPDATE
],
592 bp
-> data
[1] = server_updates_a
;
593 if (!save_option_buffer (&fqdn_universe
, state
-> options
,
594 bp
, &bp
-> data
[1], 1,
595 &fqdn_options
[FQDN_NO_CLIENT_UPDATE
],
598 /* Do the same encoding the client did. */
599 oc
= lookup_option (&fqdn_universe
, packet
-> options
,
602 evaluate_boolean_option_cache (&ignorep
, packet
, lease
,
603 (struct client_state
*)0,
606 &lease
-> scope
, oc
, MDL
))
610 if (!save_option_buffer (&fqdn_universe
, state
-> options
,
611 bp
, &bp
-> data
[2], 1,
612 &fqdn_options
[FQDN_ENCODED
],
615 bp
-> data
[3] = isc_rcode_to_ns (rcode1
);
616 if (!save_option_buffer (&fqdn_universe
, state
-> options
,
617 bp
, &bp
-> data
[3], 1,
618 &fqdn_options
[FQDN_RCODE1
],
621 bp
-> data
[4] = isc_rcode_to_ns (rcode2
);
622 if (!save_option_buffer (&fqdn_universe
, state
-> options
,
623 bp
, &bp
-> data
[4], 1,
624 &fqdn_options
[FQDN_RCODE2
],
627 if (ddns_fwd_name
.len
) {
628 memcpy (&bp
-> data
[5],
629 ddns_fwd_name
.data
, ddns_fwd_name
.len
);
630 if (!save_option_buffer (&fqdn_universe
, state
-> options
,
633 &fqdn_options
[FQDN_FQDN
],
644 data_string_forget (&ddns_hostname
, MDL
);
645 data_string_forget (&ddns_domainname
, MDL
);
646 data_string_forget (&old_ddns_fwd_name
, MDL
);
647 data_string_forget (&ddns_fwd_name
, MDL
);
648 data_string_forget (&ddns_rev_name
, MDL
);
649 data_string_forget (&ddns_dhcid
, MDL
);
651 buffer_dereference (&bp
, MDL
);
656 int ddns_removals (struct lease
*lease
)
658 struct data_string ddns_fwd_name
;
659 struct data_string ddns_rev_name
;
660 struct data_string ddns_dhcid
;
662 struct binding
*binding
;
664 int client_updated
= 0;
666 /* No scope implies that DDNS has not been performed for this lease. */
670 if (ddns_update_style
!= 2)
674 * Look up stored names.
676 memset (&ddns_fwd_name
, 0, sizeof (ddns_fwd_name
));
677 memset (&ddns_rev_name
, 0, sizeof (ddns_rev_name
));
678 memset (&ddns_dhcid
, 0, sizeof (ddns_dhcid
));
681 * Start the resolver, if necessary.
683 if (!resolver_inited
) {
684 minires_ninit (&resolver_state
);
686 resolver_state
.retrans
= 1;
687 resolver_state
.retry
= 1;
690 /* We need the fwd name whether we are deleting both records or just
691 the PTR record, so if it's not there, we can't proceed. */
692 if (!find_bound_string (&ddns_fwd_name
,
693 lease
-> scope
, "ddns-fwd-name")) {
694 /* If there's no ddns-fwd-name, look for the client fqdn,
695 in case the client did the update. */
696 if (!find_bound_string (&ddns_fwd_name
,
697 lease
-> scope
, "ddns-client-fqdn"))
703 /* If the ddns-txt binding isn't there, this isn't an interim
704 or rfc3??? record, so we can't delete the A record using
705 this mechanism, but we can delete the PTR record. */
706 if (!find_bound_string (&ddns_dhcid
, lease
-> scope
, "ddns-txt")) {
714 if (ddns_fwd_name
.len
)
715 rcode
= ddns_remove_a (&ddns_fwd_name
,
716 lease
-> ip_addr
, &ddns_dhcid
);
718 rcode
= ISC_R_SUCCESS
;
720 if (rcode
== ISC_R_SUCCESS
) {
722 unset (lease
-> scope
, "ddns-fwd-name");
723 unset (lease
-> scope
, "ddns-txt");
725 if (find_bound_string (&ddns_rev_name
,
726 lease
-> scope
, "ddns-rev-name")) {
727 if (ddns_remove_ptr(&ddns_rev_name
) == NOERROR
) {
728 unset (lease
-> scope
, "ddns-rev-name");
730 unset (lease
-> scope
,
732 /* XXX this is to compensate for a bug in
733 XXX 3.0rc8, and should be removed before
735 else if (!ddns_fwd_name
.len
)
736 unset (lease
-> scope
, "ddns-text");
742 data_string_forget (&ddns_fwd_name
, MDL
);
743 data_string_forget (&ddns_rev_name
, MDL
);
744 data_string_forget (&ddns_dhcid
, MDL
);
749 #endif /* NSUPDATE */