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: ddns.c,v 1.7 2005/08/11 17:13:30 drochner Exp $ 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
;
227 struct data_string d1
;
228 struct option_cache
*oc
;
231 isc_result_t rcode1
= ISC_R_SUCCESS
, rcode2
= ISC_R_SUCCESS
;
232 int server_updates_a
= 1;
233 struct buffer
*bp
= (struct buffer
*)0;
236 s1
= 0; /* XXXGCC -Wuninitialized [arm / sparc64] */
238 if (ddns_update_style
!= 2)
241 /* Can only cope with IPv4 addrs at the moment. */
242 if (lease
-> ip_addr
. len
!= 4)
245 memset (&ddns_hostname
, 0, sizeof (ddns_hostname
));
246 memset (&ddns_domainname
, 0, sizeof (ddns_domainname
));
247 memset (&old_ddns_fwd_name
, 0, sizeof (ddns_fwd_name
));
248 memset (&ddns_fwd_name
, 0, sizeof (ddns_fwd_name
));
249 memset (&ddns_rev_name
, 0, sizeof (ddns_rev_name
));
250 memset (&ddns_dhcid
, 0, sizeof (ddns_dhcid
));
252 /* If we are allowed to accept the client's update of its own A
253 record, see if the client wants to update its own A record. */
254 if (!(oc
= lookup_option (&server_universe
, state
-> options
,
255 SV_CLIENT_UPDATES
)) ||
256 evaluate_boolean_option_cache (&ignorep
, packet
, lease
,
257 (struct client_state
*)0,
260 &lease
-> scope
, oc
, MDL
)) {
261 /* If there's no fqdn.no-client-update or if it's
262 nonzero, don't try to use the client-supplied
264 if (!(oc
= lookup_option (&fqdn_universe
, packet
-> options
,
265 FQDN_SERVER_UPDATE
)) ||
266 evaluate_boolean_option_cache (&ignorep
, packet
, lease
,
267 (struct client_state
*)0,
270 &lease
-> scope
, oc
, MDL
))
272 /* Win98 and Win2k will happily claim to be willing to
273 update an unqualified domain name. */
274 if (!(oc
= lookup_option (&fqdn_universe
, packet
-> options
,
277 if (!(oc
= lookup_option (&fqdn_universe
, packet
-> options
,
279 !evaluate_option_cache (&ddns_fwd_name
, packet
, lease
,
280 (struct client_state
*)0,
283 &lease
-> scope
, oc
, MDL
))
285 server_updates_a
= 0;
289 /* If do-forward-updates is disabled, this basically means don't
290 do an update unless the client is participating, so if we get
291 here and do-forward-updates is disabled, we can stop. */
292 if ((oc
= lookup_option (&server_universe
, state
-> options
,
293 SV_DO_FORWARD_UPDATES
)) &&
294 !evaluate_boolean_option_cache (&ignorep
, packet
, lease
,
295 (struct client_state
*)0,
298 &lease
-> scope
, oc
, MDL
)) {
302 /* If it's a static lease, then don't do the DNS update unless we're
303 specifically configured to do so. If the client asked to do its
304 own update and we allowed that, we don't do this test. */
305 if (lease
-> flags
& STATIC_LEASE
) {
306 if (!(oc
= lookup_option (&server_universe
, state
-> options
,
307 SV_UPDATE_STATIC_LEASES
)) ||
308 !evaluate_boolean_option_cache (&ignorep
, packet
, lease
,
309 (struct client_state
*)0,
312 &lease
-> scope
, oc
, MDL
))
317 * Compute the name for the A record.
319 oc
= lookup_option (&server_universe
, state
-> options
,
322 s1
= evaluate_option_cache (&ddns_hostname
, packet
, lease
,
323 (struct client_state
*)0,
326 &lease
-> scope
, oc
, MDL
);
330 oc
= lookup_option (&server_universe
, state
-> options
,
331 SV_DDNS_DOMAIN_NAME
);
333 s2
= evaluate_option_cache (&ddns_domainname
, packet
, lease
,
334 (struct client_state
*)0,
337 &lease
-> scope
, oc
, MDL
);
342 if (ddns_hostname
.len
+ ddns_domainname
.len
> 253) {
343 log_error ("ddns_update: host.domain name too long");
348 buffer_allocate (&ddns_fwd_name
.buffer
,
349 ddns_hostname
.len
+ ddns_domainname
.len
+ 2,
351 if (ddns_fwd_name
.buffer
) {
352 ddns_fwd_name
.data
= ddns_fwd_name
.buffer
-> data
;
353 data_string_append (&ddns_fwd_name
, &ddns_hostname
);
354 ddns_fwd_name
.buffer
-> data
[ddns_fwd_name
.len
] = '.';
356 data_string_append (&ddns_fwd_name
, &ddns_domainname
);
357 ddns_fwd_name
.buffer
-> data
[ddns_fwd_name
.len
] ='\0';
358 ddns_fwd_name
.terminated
= 1;
363 /* See if there's a name already stored on the lease. */
364 if (find_bound_string (&old_ddns_fwd_name
,
365 lease
-> scope
, "ddns-fwd-name")) {
366 /* If there is, see if it's different. */
367 if (old_ddns_fwd_name
.len
!= ddns_fwd_name
.len
||
368 memcmp (old_ddns_fwd_name
.data
, ddns_fwd_name
.data
,
369 old_ddns_fwd_name
.len
)) {
370 /* If the name is different, try to delete
372 if (!ddns_removals (lease
))
374 /* If the delete succeeded, go install the new
379 /* See if there's a DHCID on the lease. */
380 if (!find_bound_string (&ddns_dhcid
,
381 lease
-> scope
, "ddns-txt")) {
382 /* If there's no DHCID, the update was probably
383 done with the old-style ad-hoc DDNS updates.
384 So if the expiry and release events look like
385 they're the same, run them. This should delete
386 the old DDNS data. */
387 if (old
-> on_expiry
== old
-> on_release
) {
388 execute_statements ((struct binding_value
**)0,
389 (struct packet
*)0, lease
,
390 (struct client_state
*)0,
391 (struct option_state
*)0,
392 (struct option_state
*)0,
395 if (old
-> on_expiry
)
396 executable_statement_dereference
397 (&old
-> on_expiry
, MDL
);
398 if (old
-> on_release
)
399 executable_statement_dereference
400 (&old
-> on_release
, MDL
);
401 /* Now, install the DDNS data the new way. */
406 /* See if the administrator wants to do updates even
407 in cases where the update already appears to have been
409 if (!(oc
= lookup_option (&server_universe
, state
-> options
,
410 SV_UPDATE_OPTIMIZATION
)) ||
411 evaluate_boolean_option_cache (&ignorep
, packet
, lease
,
412 (struct client_state
*)0,
415 &lease
-> scope
, oc
, MDL
)) {
421 /* If there's no ddns-fwd-name on the lease, see if there's
422 a ddns-client-fqdn, indicating a prior client FQDN update.
423 If there is, and if we're still doing the client update,
424 see if the name has changed. If it hasn't, don't do the
426 if (find_bound_string (&old_ddns_fwd_name
,
427 lease
-> scope
, "ddns-client-fqdn")) {
428 /* If the name is not different, no need to update
430 if (old_ddns_fwd_name
.len
== ddns_fwd_name
.len
&&
431 !memcmp (old_ddns_fwd_name
.data
, ddns_fwd_name
.data
,
432 old_ddns_fwd_name
.len
) &&
433 (!(oc
= lookup_option (&server_universe
,
435 SV_UPDATE_OPTIMIZATION
)) ||
436 evaluate_boolean_option_cache (&ignorep
, packet
, lease
,
437 (struct client_state
*)0,
447 /* If we don't have a name that the client has been assigned, we
448 can just skip all this. */
449 if (!ddns_fwd_name
.len
)
452 if (ddns_fwd_name
.len
> 255) {
453 log_error ("client provided fqdn: too long");
458 * Compute the RR TTL.
460 ddns_ttl
= DEFAULT_DDNS_TTL
;
461 memset (&d1
, 0, sizeof d1
);
462 if ((oc
= lookup_option (&server_universe
, state
-> options
,
464 if (evaluate_option_cache (&d1
, packet
, lease
,
465 (struct client_state
*)0,
468 &lease
-> scope
, oc
, MDL
)) {
469 if (d1
.len
== sizeof (u_int32_t
))
470 ddns_ttl
= getULong (d1
.data
);
471 data_string_forget (&d1
, MDL
);
477 * Compute the reverse IP name.
479 oc
= lookup_option (&server_universe
, state
-> options
,
480 SV_DDNS_REV_DOMAIN_NAME
);
482 s1
= evaluate_option_cache (&d1
, packet
, lease
,
483 (struct client_state
*)0,
486 &lease
-> scope
, oc
, MDL
);
490 if (s1
&& (d1
.len
> 238)) {
491 log_error ("ddns_update: Calculated rev domain name too long.");
493 data_string_forget (&d1
, MDL
);
498 XXX.XXX.XXX.XXX.<ddns-rev-domain-name>\0 */
499 buffer_allocate (&ddns_rev_name
.buffer
,
501 if (ddns_rev_name
.buffer
) {
502 ddns_rev_name
.data
= ddns_rev_name
.buffer
-> data
;
504 /* %Audit% Cannot exceed 17 bytes. %2004.06.17,Safe% */
505 sprintf ((char *)ddns_rev_name
.buffer
-> data
,
507 lease
-> ip_addr
. iabuf
[3] & 0xff,
508 lease
-> ip_addr
. iabuf
[2] & 0xff,
509 lease
-> ip_addr
. iabuf
[1] & 0xff,
510 lease
-> ip_addr
. iabuf
[0] & 0xff);
513 strlen ((const char *)ddns_rev_name
.data
);
514 data_string_append (&ddns_rev_name
, &d1
);
515 ddns_rev_name
.buffer
-> data
[ddns_rev_name
.len
] ='\0';
516 ddns_rev_name
.terminated
= 1;
519 data_string_forget (&d1
, MDL
);
523 * If we are updating the A record, compute the DHCID value.
525 if (server_updates_a
) {
526 memset (&ddns_dhcid
, 0, sizeof ddns_dhcid
);
527 if (lease
-> uid
&& lease
-> uid_len
)
528 result
= get_dhcid (&ddns_dhcid
,
529 DHO_DHCP_CLIENT_IDENTIFIER
,
530 lease
-> uid
, lease
-> uid_len
);
532 result
= get_dhcid (&ddns_dhcid
, 0,
533 lease
-> hardware_addr
.hbuf
,
534 lease
-> hardware_addr
.hlen
);
540 * Start the resolver, if necessary.
542 if (!resolver_inited
) {
543 minires_ninit (&resolver_state
);
545 resolver_state
.retrans
= 1;
546 resolver_state
.retry
= 1;
552 if (ddns_fwd_name
.len
&& ddns_dhcid
.len
)
553 rcode1
= ddns_update_a (&ddns_fwd_name
, lease
-> ip_addr
,
554 &ddns_dhcid
, ddns_ttl
, 0);
556 if (rcode1
== ISC_R_SUCCESS
) {
557 if (ddns_fwd_name
.len
&& ddns_rev_name
.len
)
558 rcode2
= ddns_update_ptr (&ddns_fwd_name
,
559 &ddns_rev_name
, ddns_ttl
);
563 if (rcode1
== ISC_R_SUCCESS
&&
564 (server_updates_a
|| rcode2
== ISC_R_SUCCESS
)) {
565 bind_ds_value (&lease
-> scope
,
567 ? "ddns-fwd-name" : "ddns-client-fqdn"),
569 if (server_updates_a
)
570 bind_ds_value (&lease
-> scope
, "ddns-txt",
574 if (rcode2
== ISC_R_SUCCESS
) {
575 bind_ds_value (&lease
-> scope
, "ddns-rev-name",
579 /* Set up the outgoing FQDN option if there was an incoming
580 FQDN option. If there's a valid FQDN option, there should
581 be an FQDN_ENCODED suboption, so we test the latter to
582 detect the presence of the former. */
584 if ((oc
= lookup_option (&fqdn_universe
,
585 packet
-> options
, FQDN_ENCODED
))
586 && buffer_allocate (&bp
, ddns_fwd_name
.len
+ 5, MDL
)) {
587 bp
-> data
[0] = server_updates_a
;
588 if (!save_option_buffer (&fqdn_universe
, state
-> options
,
589 bp
, &bp
-> data
[0], 1,
590 &fqdn_options
[FQDN_SERVER_UPDATE
],
593 bp
-> data
[1] = server_updates_a
;
594 if (!save_option_buffer (&fqdn_universe
, state
-> options
,
595 bp
, &bp
-> data
[1], 1,
596 &fqdn_options
[FQDN_NO_CLIENT_UPDATE
],
599 /* Do the same encoding the client did. */
600 oc
= lookup_option (&fqdn_universe
, packet
-> options
,
603 evaluate_boolean_option_cache (&ignorep
, packet
, lease
,
604 (struct client_state
*)0,
607 &lease
-> scope
, oc
, MDL
))
611 if (!save_option_buffer (&fqdn_universe
, state
-> options
,
612 bp
, &bp
-> data
[2], 1,
613 &fqdn_options
[FQDN_ENCODED
],
616 bp
-> data
[3] = isc_rcode_to_ns (rcode1
);
617 if (!save_option_buffer (&fqdn_universe
, state
-> options
,
618 bp
, &bp
-> data
[3], 1,
619 &fqdn_options
[FQDN_RCODE1
],
622 bp
-> data
[4] = isc_rcode_to_ns (rcode2
);
623 if (!save_option_buffer (&fqdn_universe
, state
-> options
,
624 bp
, &bp
-> data
[4], 1,
625 &fqdn_options
[FQDN_RCODE2
],
628 if (ddns_fwd_name
.len
) {
629 memcpy (&bp
-> data
[5],
630 ddns_fwd_name
.data
, ddns_fwd_name
.len
);
631 if (!save_option_buffer (&fqdn_universe
, state
-> options
,
634 &fqdn_options
[FQDN_FQDN
],
645 data_string_forget (&ddns_hostname
, MDL
);
646 data_string_forget (&ddns_domainname
, MDL
);
647 data_string_forget (&old_ddns_fwd_name
, MDL
);
648 data_string_forget (&ddns_fwd_name
, MDL
);
649 data_string_forget (&ddns_rev_name
, MDL
);
650 data_string_forget (&ddns_dhcid
, MDL
);
652 buffer_dereference (&bp
, MDL
);
657 int ddns_removals (struct lease
*lease
)
659 struct data_string ddns_fwd_name
;
660 struct data_string ddns_rev_name
;
661 struct data_string ddns_dhcid
;
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 */