From 5af54ded9173d336113c65ab1e83dec5d1ca77b5 Mon Sep 17 00:00:00 2001
From: Pavel Reichl <preichl@redhat.com>
Date: Fri, 20 Nov 2015 09:25:11 -0500
Subject: [PATCH] DDNS: Use nsupdate keywords for all transactions

Keywords realm and server might be used in fallback attempt to update
DDNS.

This patch puts them explicitely to the nsupdate transaction.

Resolves:
https://fedorahosted.org/sssd/ticket/2872
https://fedorahosted.org/sssd/ticket/2734
---
 src/providers/dp_dyndns.c      | 217 +++++++++++++++++++++++++++++------------
 src/tests/cmocka/test_dyndns.c | 133 +++++++++++++++++++++++--
 2 files changed, 279 insertions(+), 71 deletions(-)

diff --git a/src/providers/dp_dyndns.c b/src/providers/dp_dyndns.c
index f717bf6cd39e900510d47a9c3b4f412688dcbd4d..4f930df9be1eaecdd6fcfb54d6d86357e2b66ac4 100644
--- a/src/providers/dp_dyndns.c
+++ b/src/providers/dp_dyndns.c
@@ -265,74 +265,53 @@ done:
     return ret;
 }
 
+struct msg_af_data {
+    struct sss_iface_addr *addresses;
+    const char *hostname;
+    int ttl;
+};
+
+static char*
+enh_gen_msg_af(char *update_msg, char *prefix,
+               uint8_t remove_af,
+               int address_family,
+               struct msg_af_data data);
+
 static char *
-nsupdate_msg_add_fwd(char *update_msg, struct sss_iface_addr *addresses,
-                     const char *hostname, int ttl, uint8_t remove_af)
+nsupdate_msg_add_fwd(TALLOC_CTX *mem_ctx, char *common_msg,
+                     struct sss_iface_addr *addresses, const char *hostname,
+                     int ttl, uint8_t remove_af)
 {
-    struct sss_iface_addr *new_record;
-    char ip_addr[INET6_ADDRSTRLEN];
-    errno_t ret;
+    char *update_msg;
+    struct msg_af_data data = {
+        .addresses = addresses,
+        .hostname = hostname,
+        .ttl = ttl };
+
+    update_msg = talloc_zero(mem_ctx, char);
+    if (update_msg == NULL) {
+        goto done;
+    }
 
     /* A addresses first */
-    /* Remove existing entries as needed */
-    if (remove_af & DYNDNS_REMOVE_A) {
-        update_msg = talloc_asprintf_append(update_msg,
-                                            "update delete %s. in A\n",
-                                            hostname);
-        if (update_msg == NULL) {
-            return NULL;
-        }
+    update_msg = enh_gen_msg_af(update_msg, common_msg, remove_af, AF_INET,
+                                data);
+    if (update_msg == NULL) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              "Failed to create nsupdate message for A records.\n");
+        /* Non-fatal - continue */
     }
-    DLIST_FOR_EACH(new_record, addresses) {
-        if (new_record->addr->ss_family == AF_INET) {
-            ret = addr_to_str(new_record->addr, ip_addr, INET6_ADDRSTRLEN);
-            if (ret != EOK) {
-                DEBUG(SSSDBG_MINOR_FAILURE, "addr_to_str failed: %d:[%s],\n",
-                      ret, sss_strerror(ret));
-                return NULL;
-            }
-
-            /* Format the record update */
-            update_msg = talloc_asprintf_append(update_msg,
-                                                "update add %s. %d in %s %s\n",
-                                                hostname, ttl, "A", ip_addr);
-            if (update_msg == NULL) {
-                return NULL;
-            }
-        }
-    }
-    update_msg = talloc_asprintf_append(update_msg, "send\n");
 
     /* AAAA addresses next */
-    /* Remove existing entries as needed */
-    if (remove_af & DYNDNS_REMOVE_AAAA) {
-        update_msg = talloc_asprintf_append(update_msg,
-                                            "update delete %s. in AAAA\n",
-                                            hostname);
-        if (update_msg == NULL) {
-            return NULL;
-        }
+    update_msg = enh_gen_msg_af(update_msg, common_msg, remove_af, AF_INET6,
+                                data);
+    if (update_msg == NULL) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              "Failed to create nsupdate message for AAAA records.\n");
     }
-    DLIST_FOR_EACH(new_record, addresses) {
-        if (new_record->addr->ss_family == AF_INET6) {
-            ret = addr_to_str(new_record->addr, ip_addr, INET6_ADDRSTRLEN);
-            if (ret != EOK) {
-                DEBUG(SSSDBG_MINOR_FAILURE, "addr_to_str failed: %d:[%s],\n",
-                      ret, sss_strerror(ret));
-                return NULL;
-            }
 
-            /* Format the record update */
-            update_msg = talloc_asprintf_append(update_msg,
-                                                "update add %s. %d in %s %s\n",
-                                                hostname, ttl, "AAAA", ip_addr);
-            if (update_msg == NULL) {
-                return NULL;
-            }
-        }
-    }
-
-    return talloc_asprintf_append(update_msg, "send\n");
+done:
+    return update_msg;
 }
 
 static uint8_t *nsupdate_convert_address(struct sockaddr_storage *add_address)
@@ -400,12 +379,17 @@ static char *nsupdate_msg_add_ptr(char *update_msg,
 static char *
 nsupdate_msg_add_realm_cmd(TALLOC_CTX *mem_ctx, const char *realm)
 {
+    char *msg;
+
+    msg = talloc_zero(mem_ctx, char);
+
 #ifdef HAVE_NSUPDATE_REALM
     if (realm != NULL) {
-        return talloc_asprintf(mem_ctx, "realm %s\n", realm);
+        msg = talloc_asprintf_append(msg, "realm %s\n", realm);
     }
 #endif
-    return talloc_asprintf(mem_ctx, "\n");
+
+    return msg;
 }
 
 static char *
@@ -464,6 +448,7 @@ be_nsupdate_create_fwd_msg(TALLOC_CTX *mem_ctx, const char *realm,
 {
     int ret;
     char *update_msg;
+    char *common_msg;
     TALLOC_CTX *tmp_ctx;
 
     /* in some cases realm could have been NULL if we weren't using TSIG */
@@ -474,13 +459,13 @@ be_nsupdate_create_fwd_msg(TALLOC_CTX *mem_ctx, const char *realm,
     tmp_ctx = talloc_new(NULL);
     if (tmp_ctx == NULL) return ENOMEM;
 
-    update_msg = nsupdate_msg_create_common(tmp_ctx, realm, servername);
-    if (update_msg == NULL) {
+    common_msg = nsupdate_msg_create_common(tmp_ctx, realm, servername);
+    if (common_msg == NULL) {
         ret = ENOMEM;
         goto done;
     }
 
-    update_msg = nsupdate_msg_add_fwd(update_msg, addresses, hostname,
+    update_msg = nsupdate_msg_add_fwd(tmp_ctx, common_msg, addresses, hostname,
                                       ttl, remove_af);
     if (update_msg == NULL) {
         ret = ENOMEM;
@@ -1356,3 +1341,107 @@ done:
     talloc_free(tmp_ctx);
     return ret;
 }
+
+/* Generate nsupdate message for given address family */
+static errno_t
+gen_msg_af(TALLOC_CTX *mem_ctx, int address_family,
+           uint8_t remove_af, struct msg_af_data data,
+           char **_msg)
+{
+    errno_t ret;
+    bool remove_entries;
+    const char *remove_addr_fmt;
+    const char *update_addr_fmt;
+    char *msg = NULL;
+    struct sss_iface_addr *new_record;
+
+    if (address_family == AF_INET) {
+        remove_entries = remove_af & DYNDNS_REMOVE_A;
+        remove_addr_fmt = "update delete %s. in A\n";
+        update_addr_fmt = "update add %s. %d in A %s\n";
+    } else if (address_family == AF_INET6) {
+        remove_entries = remove_af & DYNDNS_REMOVE_AAAA;
+        remove_addr_fmt = "update delete %s. in AAAA\n";
+        update_addr_fmt = "update add %s. %d in AAAA %s\n";
+    } else {
+        ret = EINVAL;
+        goto done;
+    }
+
+    msg = talloc_zero(mem_ctx, char);
+
+    /* Remove existing entries as needed */
+    if (remove_entries) {
+        msg = talloc_asprintf_append(msg, remove_addr_fmt, data.hostname);
+        if (msg == NULL) {
+            ret = ENOMEM;
+            goto done;
+        }
+    }
+
+    DLIST_FOR_EACH(new_record, data.addresses) {
+        char ip_addr[INET6_ADDRSTRLEN];
+
+        if (new_record->addr->ss_family == address_family) {
+            ret = addr_to_str(new_record->addr, ip_addr, INET6_ADDRSTRLEN);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_MINOR_FAILURE, "addr_to_str failed: %d:[%s],\n",
+                      ret, sss_strerror(ret));
+                goto done;
+            }
+
+            /* Format the record update */
+            msg = talloc_asprintf_append(msg, update_addr_fmt, data.hostname,
+                                         data.ttl, ip_addr);
+            if (msg == NULL) {
+                ret = ENOMEM;
+                goto done;
+            }
+        }
+    }
+
+    *_msg = msg;
+    ret = EOK;
+
+done:
+    if (ret != EOK) {
+        talloc_free(msg);
+    }
+    return ret;
+}
+
+/* Enhance (prepend common prefix and append send directive) generated
+ * nsupdate message for given address family */
+static char*
+enh_gen_msg_af(char *update_msg, char *prefix,
+               uint8_t remove_af,
+               int address_family,
+               struct msg_af_data data)
+{
+    errno_t ret;
+    char *msg = NULL;
+    TALLOC_CTX *tmp_ctx;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        goto done;
+    }
+
+    ret = gen_msg_af(tmp_ctx, address_family, remove_af, data, &msg);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    /* Message is not empty */
+    if (strlen(msg) != 0) {
+        update_msg = talloc_strdup_append(update_msg, prefix);
+        update_msg = talloc_strdup_append(update_msg, msg);
+        update_msg = talloc_strdup_append(update_msg, "send\n");
+    }
+
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+    return update_msg;
+}
diff --git a/src/tests/cmocka/test_dyndns.c b/src/tests/cmocka/test_dyndns.c
index 43df0e1b58793e2efe55ea1317fc1f7ca8cc579f..b1c96a3fa8f410a55f23f7a505d35f63cae507ff 100644
--- a/src/tests/cmocka/test_dyndns.c
+++ b/src/tests/cmocka/test_dyndns.c
@@ -388,7 +388,7 @@ void dyndns_test_create_fwd_msg(void **state)
     assert_int_equal(ret, EOK);
 
     assert_string_equal(msg,
-                        "\nupdate delete bran_stark. in A\n"
+                        "update delete bran_stark. in A\n"
                         "update add bran_stark. 1234 in A 192.168.0.2\n"
                         "send\n"
                         "update delete bran_stark. in AAAA\n"
@@ -409,6 +409,8 @@ void dyndns_test_create_fwd_msg(void **state)
                         "update delete bran_stark. in A\n"
                         "update add bran_stark. 1234 in A 192.168.0.2\n"
                         "send\n"
+                        "server Winterfell\n"
+                        "realm North\n"
                         "update delete bran_stark. in AAAA\n"
                         "update add bran_stark. 1234 in AAAA 2001:cdba::555\n"
                         "send\n");
@@ -426,6 +428,7 @@ void dyndns_test_create_fwd_msg(void **state)
                         "update delete bran_stark. in A\n"
                         "update add bran_stark. 1234 in A 192.168.0.2\n"
                         "send\n"
+                        "realm North\n"
                         "update delete bran_stark. in AAAA\n"
                         "update add bran_stark. 1234 in AAAA 2001:cdba::555\n"
                         "send\n");
@@ -440,10 +443,10 @@ void dyndns_test_create_fwd_msg(void **state)
 
     assert_string_equal(msg,
                         "server Winterfell\n"
-                        "\n"
                         "update delete bran_stark. in A\n"
                         "update add bran_stark. 1234 in A 192.168.0.2\n"
                         "send\n"
+                        "server Winterfell\n"
                         "update delete bran_stark. in AAAA\n"
                         "update add bran_stark. 1234 in AAAA 2001:cdba::555\n"
                         "send\n");
@@ -456,7 +459,7 @@ void dyndns_test_create_fwd_msg(void **state)
     assert_int_equal(ret, EOK);
 
     assert_string_equal(msg,
-                        "\nupdate delete bran_stark. in A\n"
+                        "update delete bran_stark. in A\n"
                         "update add bran_stark. 1234 in A 192.168.0.2\n"
                         "send\n"
                         "update add bran_stark. 1234 in AAAA 2001:cdba::555\n"
@@ -470,7 +473,7 @@ void dyndns_test_create_fwd_msg(void **state)
     assert_int_equal(ret, EOK);
 
     assert_string_equal(msg,
-                        "\nupdate add bran_stark. 1234 in A 192.168.0.2\n"
+                        "update add bran_stark. 1234 in A 192.168.0.2\n"
                         "send\n"
                         "update delete bran_stark. in AAAA\n"
                         "update add bran_stark. 1234 in AAAA 2001:cdba::555\n"
@@ -478,6 +481,7 @@ void dyndns_test_create_fwd_msg(void **state)
     talloc_zfree(msg);
 
     talloc_free(addrlist);
+
     assert_true(check_leaks_pop(dyndns_test_ctx) == true);
 }
 
@@ -514,7 +518,7 @@ void dyndns_test_create_fwd_msg_mult(void **state)
     assert_int_equal(ret, EOK);
 
     assert_string_equal(msg,
-                        "\nupdate delete bran_stark. in A\n"
+                        "update delete bran_stark. in A\n"
                         "update add bran_stark. 1234 in A 192.168.0.1\n"
                         "update add bran_stark. 1234 in A 192.168.0.2\n"
                         "send\n"
@@ -559,7 +563,7 @@ void dyndns_test_create_fwd_msg_A(void **state)
     assert_int_equal(ret, EOK);
 
     assert_string_equal(msg,
-                        "\nupdate delete bran_stark. in A\n"
+                        "update delete bran_stark. in A\n"
                         "update add bran_stark. 1234 in A 192.168.0.1\n"
                         "update add bran_stark. 1234 in A 192.168.0.2\n"
                         "send\n"
@@ -603,7 +607,7 @@ void dyndns_test_create_fwd_msg_AAAA(void **state)
     assert_int_equal(ret, EOK);
 
     assert_string_equal(msg,
-                        "\nupdate delete bran_stark. in A\n"
+                        "update delete bran_stark. in A\n"
                         "send\n"
                         "update delete bran_stark. in AAAA\n"
                         "update add bran_stark. 1234 in AAAA 2001:cdba::444\n"
@@ -615,6 +619,118 @@ void dyndns_test_create_fwd_msg_AAAA(void **state)
     assert_true(check_leaks_pop(dyndns_test_ctx) == true);
 }
 
+void dyndns_test_create_ptr_msg(void **state)
+{
+    errno_t ret;
+    char *msg;
+    struct sockaddr_in sin;
+    struct sockaddr_storage address;
+
+    check_leaks_push(dyndns_test_ctx);
+
+    memset (&sin, 0, sizeof (sin));
+    sin.sin_family = AF_INET;
+    sin.sin_addr.s_addr = inet_addr ("192.168.0.2");
+    memcpy (&address, &sin, sizeof (sin));
+
+    /* No realm and no server */
+    /* Delete */
+    ret = be_nsupdate_create_ptr_msg(dyndns_test_ctx, NULL, NULL, "bran_stark",
+                                     1234, &address, true, &msg);
+    assert_int_equal(ret, EOK);
+
+    assert_string_equal(msg,
+                        "update delete 2.0.168.192.in-addr.arpa. in PTR\n"
+                        "send\n");
+    talloc_zfree(msg);
+
+    /* Create */
+    ret = be_nsupdate_create_ptr_msg(dyndns_test_ctx, NULL, NULL, "bran_stark",
+                                     1234, &address, false, &msg);
+    assert_int_equal(ret, EOK);
+
+    assert_string_equal(msg,
+        "update add 2.0.168.192.in-addr.arpa. 1234 in PTR bran_stark.\n"
+                        "send\n");
+    talloc_zfree(msg);
+
+    /* Realm and server */
+    /* Delete */
+    ret = be_nsupdate_create_ptr_msg(dyndns_test_ctx, "North", "Winterfell",
+                                     "bran_stark", 1234, &address, true, &msg);
+    assert_int_equal(ret, EOK);
+
+    assert_string_equal(msg,
+                        "server Winterfell\n"
+                        "realm North\n"
+                        "update delete 2.0.168.192.in-addr.arpa. in PTR\n"
+                        "send\n");
+    talloc_zfree(msg);
+
+    /* Create */
+    ret = be_nsupdate_create_ptr_msg(dyndns_test_ctx, "North", "Winterfell",
+                                     "bran_stark", 1234, &address, false,
+                                     &msg);
+    assert_int_equal(ret, EOK);
+
+    assert_string_equal(msg,
+                        "server Winterfell\n"
+                        "realm North\n"
+        "update add 2.0.168.192.in-addr.arpa. 1234 in PTR bran_stark.\n"
+                        "send\n");
+    talloc_zfree(msg);
+
+    /* Just realm */
+    /* Delete */
+    ret = be_nsupdate_create_ptr_msg(dyndns_test_ctx, "North", NULL,
+                                     "bran_stark", 1234, &address, true, &msg);
+    assert_int_equal(ret, EOK);
+
+    assert_string_equal(msg,
+                        "realm North\n"
+                        "update delete 2.0.168.192.in-addr.arpa. in PTR\n"
+                        "send\n");
+    talloc_zfree(msg);
+
+    /* Create */
+    ret = be_nsupdate_create_ptr_msg(dyndns_test_ctx, "North", NULL,
+                                     "bran_stark", 1234, &address, false,
+                                     &msg);
+    assert_int_equal(ret, EOK);
+
+    assert_string_equal(msg,
+                        "realm North\n"
+        "update add 2.0.168.192.in-addr.arpa. 1234 in PTR bran_stark.\n"
+                        "send\n");
+    talloc_zfree(msg);
+
+    /* Just server */
+    /* Delete */
+    ret = be_nsupdate_create_ptr_msg(dyndns_test_ctx, NULL, "Winterfell",
+                                     "bran_stark", 1234, &address, true, &msg);
+    assert_int_equal(ret, EOK);
+
+    assert_string_equal(msg,
+                        "server Winterfell\n"
+                        "update delete 2.0.168.192.in-addr.arpa. in PTR\n"
+                        "send\n");
+    talloc_zfree(msg);
+
+    /* Create */
+    ret = be_nsupdate_create_ptr_msg(dyndns_test_ctx, NULL, "Winterfell",
+                                     "bran_stark", 1234, &address, false,
+                                     &msg);
+    assert_int_equal(ret, EOK);
+
+    assert_string_equal(msg,
+                        "server Winterfell\n"
+        "update add 2.0.168.192.in-addr.arpa. 1234 in PTR bran_stark.\n"
+                        "send\n");
+    talloc_zfree(msg);
+
+    assert_true(check_leaks_pop(dyndns_test_ctx) == true);
+}
+
 void dyndns_test_dualstack(void **state)
 {
     errno_t ret;
@@ -1043,6 +1159,9 @@ int main(int argc, const char *argv[])
         cmocka_unit_test_setup_teardown(dyndns_test_create_fwd_msg_AAAA,
                                         dyndns_test_setup,
                                         dyndns_test_teardown),
+        cmocka_unit_test_setup_teardown(dyndns_test_create_ptr_msg,
+                                        dyndns_test_setup,
+                                        dyndns_test_teardown),
     };
 
     /* Set debug level to invalid value so we can deside if -d 0 was used. */
-- 
2.4.3

