]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
libcli/dns: Add infrastructure for DNS updates
authorVolker Lendecke <vl@samba.org>
Sun, 3 May 2026 18:06:15 +0000 (20:06 +0200)
committerVolker Lendecke <vl@samba.org>
Fri, 3 Jul 2026 08:08:36 +0000 (08:08 +0000)
This creates struct dns_name_packets equivalent of
dns_create_update_request() in lib/addns to allow the use of dns.idl
structures instead of hand-marshalling DNS packets in lib/addns.

Signed-off-by: Volker Lendecke <vl@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
libcli/dns/dns.c
libcli/dns/libdns.h

index 854433d8788cf1385e1e65def3e65df5a88e4d69..5087715b5e7c8d5ff9be789fcc5040f3cd1824c0 100644 (file)
@@ -31,6 +31,8 @@
 #include "libcli/util/error.h"
 #include "librpc/ndr/libndr.h"
 #include "librpc/gen_ndr/ndr_dns.h"
+#include "lib/util/util_net.h"
+#include "auth/gensec/gensec.h"
 
 struct dns_udp_request_state {
        struct tevent_context *ev;
@@ -748,3 +750,346 @@ fail:
        TALLOC_FREE(p);
        return NULL;
 }
+
+static struct dns_name_packet *dns_cli_create_update_base(TALLOC_CTX *mem_ctx,
+                                                         const char *zone,
+                                                         const char *host)
+{
+       struct dns_name_packet *p = NULL;
+       struct dns_name_question *q = NULL;
+       struct dns_res_rec *answer = NULL;
+
+       p = talloc(mem_ctx, struct dns_name_packet);
+       if (p == NULL) {
+               goto fail;
+       }
+
+       q = talloc(p, struct dns_name_question);
+       if (q == NULL) {
+               goto fail;
+       }
+       *q = (struct dns_name_question){
+               .name = talloc_strdup(q, zone),
+               .question_type = DNS_QTYPE_SOA,
+               .question_class = DNS_QCLASS_IN,
+       };
+       if (q->name == NULL) {
+               goto fail;
+       }
+
+       answer = talloc(p, struct dns_res_rec);
+       if (answer == NULL) {
+               goto fail;
+       }
+
+       /*
+        * Prerequisite: Can't overwrite a cname
+        */
+       *answer = (struct dns_res_rec){
+               .name = talloc_strdup(answer, host),
+               .rr_type = DNS_QTYPE_CNAME,
+               .rr_class = DNS_QCLASS_NONE,
+       };
+       if (answer->name == NULL) {
+               goto fail;
+       }
+
+       *p = (struct dns_name_packet){
+               .operation = DNS_OPCODE_UPDATE,
+               .qdcount = 1,
+               .ancount = 1,
+               .questions = q,
+               .answers = answer,
+       };
+
+       generate_random_buffer((uint8_t *)&p->id, sizeof(p->id));
+
+       return p;
+fail:
+       TALLOC_FREE(p);
+       return NULL;
+}
+
+static bool dns_cli_add_ip_records(TALLOC_CTX *parent,
+                                  struct dns_res_rec **_recs,
+                                  uint16_t *_num_recs,
+                                  const char *host,
+                                  const struct samba_sockaddr *ips,
+                                  size_t num_ips,
+                                  uint32_t ttl)
+{
+       size_t i;
+       size_t num_recs = *_num_recs;
+       struct dns_res_rec *recs = NULL;
+
+       if ((num_ips + num_recs < num_ips) ||
+           (num_ips + num_recs > UINT16_MAX)) {
+               return false;
+       }
+
+       recs = talloc_realloc(parent,
+                             *_recs,
+                             struct dns_res_rec,
+                             num_recs + num_ips);
+       if (recs == NULL) {
+               return false;
+       }
+       *_recs = recs;
+
+       for (i = 0; i < num_ips; i++) {
+               struct dns_res_rec *rec = &recs[num_recs + i];
+               const struct samba_sockaddr *ip = &ips[i];
+               struct ssaddr_buf buf;
+               char *addrstr = NULL;
+
+               *rec = (struct dns_res_rec){
+                       .name = talloc_strdup(parent, host),
+                       .rr_class = DNS_QCLASS_IN,
+                       .ttl = ttl,
+                       .length = 1,
+               };
+               if (rec->name == NULL) {
+                       return false;
+               }
+
+               addrstr = talloc_strdup(parent, ssaddr_str_buf(ip, &buf));
+               if (addrstr == NULL) {
+                       return false;
+               }
+
+               switch (ip->u.sa.sa_family) {
+               case AF_INET:
+                       rec->rr_type = DNS_QTYPE_A;
+                       rec->rdata.ipv4_record = addrstr;
+                       break;
+               case AF_INET6:
+                       rec->rr_type = DNS_QTYPE_AAAA;
+                       rec->rdata.ipv6_record = addrstr;
+                       break;
+               default:
+                       return false;
+               }
+       }
+
+       *_num_recs = num_recs + num_ips;
+       return true;
+}
+
+struct dns_name_packet *dns_cli_create_probe(TALLOC_CTX *mem_ctx,
+                                            const char *zone,
+                                            const char *host,
+                                            const struct samba_sockaddr *ips,
+                                            size_t num_ips)
+{
+       struct dns_name_packet *p = NULL;
+       bool ok;
+
+       p = dns_cli_create_update_base(mem_ctx, zone, host);
+       if (p == NULL) {
+               goto fail;
+       }
+
+       /* A/AAAA in use prerequisites */
+       ok = dns_cli_add_ip_records(
+               p, &p->answers, &p->ancount, host, ips, num_ips, 0);
+       if (!ok) {
+               goto fail;
+       }
+
+       return p;
+fail:
+       TALLOC_FREE(p);
+       return NULL;
+}
+
+struct dns_name_packet *dns_cli_create_update(TALLOC_CTX *mem_ctx,
+                                             const char *zone,
+                                             const char *host,
+                                             const struct samba_sockaddr *ips,
+                                             size_t num_ips,
+                                             uint32_t ttl)
+{
+       struct dns_name_packet *p = NULL;
+       struct dns_res_rec *nsrec = NULL;
+       bool ok;
+
+       p = dns_cli_create_update_base(mem_ctx, zone, host);
+       if (p == NULL) {
+               goto fail;
+       }
+
+       nsrec = talloc(p, struct dns_res_rec);
+       if (nsrec == NULL) {
+               goto fail;
+       }
+
+       /* Delete any existing records */
+       *nsrec = (struct dns_res_rec){
+               .name = talloc_strdup(nsrec, host),
+               .rr_type = DNS_QTYPE_ALL,
+               .rr_class = DNS_QCLASS_ANY,
+       };
+       if (nsrec->name == NULL) {
+               goto fail;
+       }
+
+       p->nscount = 1;
+       p->nsrecs = nsrec;
+
+       /* Add A/AAAA records */
+       ok = dns_cli_add_ip_records(
+               p, &p->nsrecs, &p->nscount, host, ips, num_ips, ttl);
+       if (!ok) {
+               goto fail;
+       }
+
+       return p;
+
+fail:
+       TALLOC_FREE(p);
+       return NULL;
+}
+
+/*
+ * Pass in gensec_sign_packet() via a pointer so that we don't have to
+ * pull in gensec into the dependencies here.
+ */
+
+int dns_cli_sign_packet(
+       struct dns_name_packet *p,
+       struct gensec_security *gensec,
+       NTSTATUS (*sign)(struct gensec_security *gensec_security,
+                        TALLOC_CTX *mem_ctx,
+                        const uint8_t *data,
+                        size_t length,
+                        const uint8_t *whole_pdu,
+                        size_t pdu_length,
+                        DATA_BLOB *sig),
+       const char *keyname,
+       const char *algorithmname)
+{
+       TALLOC_CTX *frame = talloc_stackframe();
+       DATA_BLOB packet_blob = {};
+       DATA_BLOB tsig_blob = {};
+       DATA_BLOB mic = {};
+       struct dns_res_rec *additional = NULL;
+       struct dns_fake_tsig_rec fake_tsig = {};
+       struct dns_tsig_record tsig = {};
+       struct dns_res_rec *tsig_rec = NULL;
+       enum ndr_err_code ndr_err;
+       NTSTATUS status;
+       time_t now = time(NULL);
+       int ret;
+       bool ok;
+
+       if (p->arcount >= UINT16_MAX) {
+               ret = ERANGE;
+               goto fail;
+       }
+
+       /* Marshal the packet to sign */
+       ndr_err = ndr_push_struct_blob(&packet_blob,
+                                      frame,
+                                      p,
+                                      (ndr_push_flags_fn_t)
+                                              ndr_push_dns_name_packet);
+       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+               ret = ndr_map_error2errno(ndr_err);
+               goto fail;
+       }
+
+       /* Create the fake TSIG record for hashing */
+       fake_tsig = (struct dns_fake_tsig_rec){
+               .name = keyname,
+               .rr_class = DNS_QCLASS_ANY,
+               .algorithm_name = algorithmname,
+               .time = now,
+               .fudge = 300,
+       };
+
+       ndr_err = ndr_push_struct_blob(&tsig_blob,
+                                      frame,
+                                      &fake_tsig,
+                                      (ndr_push_flags_fn_t)
+                                              ndr_push_dns_fake_tsig_rec);
+       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+               ret = ndr_map_error2errno(ndr_err);
+               goto fail;
+       }
+
+       ok = data_blob_append(frame,
+                             &packet_blob,
+                             tsig_blob.data,
+                             tsig_blob.length);
+       if (!ok) {
+               goto nomem;
+       }
+
+       status = sign(gensec,
+                     frame,
+                     packet_blob.data,
+                     packet_blob.length,
+                     packet_blob.data,
+                     packet_blob.length,
+                     &mic);
+       if (!NT_STATUS_IS_OK(status)) {
+               ret = map_errno_from_nt_status(status);
+               goto fail;
+       }
+
+       if (mic.length > UINT16_MAX) {
+               ret = ERANGE;
+               goto fail;
+       }
+
+       /* Add TSIG to additional records */
+
+       additional = talloc_realloc(p,
+                                   p->additional,
+                                   struct dns_res_rec,
+                                   p->arcount + 1);
+       if (additional == NULL) {
+               goto nomem;
+       }
+
+       p->additional = additional;
+       tsig_rec = &p->additional[p->arcount];
+
+       tsig = (struct dns_tsig_record){
+               .algorithm_name = talloc_strdup(additional, algorithmname),
+               .time_prefix = 0,
+               .time = now,
+               .fudge = 300,
+               .mac_size = mic.length,
+               .mac = talloc_memdup(additional, mic.data, mic.length),
+               .original_id = p->id,
+       };
+
+       if (tsig.algorithm_name == NULL ||
+           (mic.length > 0 && tsig.mac == NULL)) {
+               goto nomem;
+       }
+
+       *tsig_rec = (struct dns_res_rec){
+               .name = talloc_strdup(additional, keyname),
+               .rr_type = DNS_QTYPE_TSIG,
+               .rr_class = DNS_QCLASS_ANY,
+               .length = 1,
+               .rdata.tsig_record = tsig,
+       };
+       if (tsig_rec->name == NULL) {
+               goto nomem;
+       }
+
+       p->arcount += 1;
+
+       TALLOC_FREE(frame);
+
+       return 0;
+
+nomem:
+       ret = ENOMEM;
+fail:
+       TALLOC_FREE(frame);
+       return ret;
+}
index af5ddd7b519e7f643201783262ff1abb66e1db55..d1b6a4cc77c65a5b0b8ce63f8796f034aedf2104 100644 (file)
 #include "lib/util/data_blob.h"
 #include "lib/util/time.h"
 #include "librpc/gen_ndr/dns.h"
+#include "libcli/util/ntstatus.h"
+
+struct gensec_security;
+struct samba_sockaddr;
 
 /*
  * DNS request with fallback to TCP on truncation
@@ -44,5 +48,28 @@ struct dns_name_packet *dns_cli_create_query(TALLOC_CTX *mem_ctx,
                                             const char *name,
                                             enum dns_qclass qclass,
                                             enum dns_qtype qtype);
+struct dns_name_packet *dns_cli_create_probe(TALLOC_CTX *mem_ctx,
+                                            const char *zone,
+                                            const char *host,
+                                            const struct samba_sockaddr *ips,
+                                            size_t num_ips);
+struct dns_name_packet *dns_cli_create_update(TALLOC_CTX *mem_ctx,
+                                             const char *zone,
+                                             const char *host,
+                                             const struct samba_sockaddr *ips,
+                                             size_t num_ips,
+                                             uint32_t ttl);
 
+int dns_cli_sign_packet(
+       struct dns_name_packet *p,
+       struct gensec_security *gensec,
+       NTSTATUS (*sign)(struct gensec_security *gensec_security,
+                        TALLOC_CTX *mem_ctx,
+                        const uint8_t *data,
+                        size_t length,
+                        const uint8_t *whole_pdu,
+                        size_t pdu_length,
+                        DATA_BLOB *sig),
+       const char *keyname,
+       const char *algorithmname);
 #endif /*__LIBDNS_H__*/