pydns: expose dns timestamp utils to python, and test
authorDouglas Bagnall <douglas.bagnall@catalyst.net.nz>
Sat, 27 Mar 2021 09:09:56 +0000 (09:09 +0000)
committerJeremy Allison <jra@samba.org>
Mon, 29 Mar 2021 23:20:37 +0000 (23:20 +0000)
Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Jeremy Allison <jra@samba.org>
python/samba/tests/dsdb_dns.py [new file with mode: 0644]
source4/dns_server/pydns.c
source4/selftest/tests.py

diff --git a/python/samba/tests/dsdb_dns.py b/python/samba/tests/dsdb_dns.py
new file mode 100644 (file)
index 0000000..8c7fc34
--- /dev/null
@@ -0,0 +1,86 @@
+# Unix SMB/CIFS implementation. Tests for dsdb_dns module
+# Copyright © Catalyst IT 2021
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+from samba.tests import TestCase
+from samba import dsdb_dns
+import time
+
+
+def unix2nttime(t):
+    # here we reimplement unix_to_nt_time from lib/util/time.c
+    if t == -1:
+        return t
+    if t == (1 << 63) - 1:
+        return (1 << 63) - 1
+    if t == 0:
+        return 0
+    t += 11644473600
+    t *= 1e7
+    return int(t)
+
+
+def unix2dns_timestamp(t):
+    nt = unix2nttime(t)
+    if nt < 0:
+        # because NTTIME is a uint64_t.
+        nt += 1 << 64
+    return nt // int(3.6e10)
+
+
+def timestamp2nttime(ts):
+    nt = ts * int(3.6e10)
+    if nt >= 1 << 63:
+        raise OverflowError("nt time won't fit this")
+    return nt
+
+
+class DsdbDnsTestCase(TestCase):
+    def test_unix_to_dns_timestamp(self):
+        unixtimes = [1616829393,
+                     1,
+                     0,
+                     -1,
+                     1 << 31 - 1]
+
+        for t in unixtimes:
+            expected = unix2dns_timestamp(t)
+            result = dsdb_dns.unix_to_dns_timestamp(t)
+            self.assertEqual(result, expected)
+
+    def test_dns_timestamp_to_nt_time(self):
+        timestamps = [16168393,
+                      1,
+                      0,
+                      (1 << 32) - 1,
+                      (1 << 63) - 1,
+                      int((1 << 63) / 3.6e10),
+                      int((1 << 63) / 3.6e10) + 1, # overflows
+                      ]
+
+        for t in timestamps:
+            overflows = False
+            try:
+                expected = timestamp2nttime(t)
+            except OverflowError:
+                overflows = True
+            try:
+                result = dsdb_dns.dns_timestamp_to_nt_time(t)
+            except ValueError:
+                self.assertTrue(overflows, f"timestamp {t} should not overflow")
+                continue
+            self.assertFalse(overflows, f"timestamp {t} should overflow")
+
+            self.assertEqual(result, expected)
index 1e6a1cb6105a36fe0b95bb63881eccd4822e5eb2..ad5a96bf7639af03f032b667b3283e806fef8153 100644 (file)
@@ -325,6 +325,48 @@ static PyObject *py_dsdb_dns_replace_by_dn(PyObject *self, PyObject *args)
        Py_RETURN_NONE;
 }
 
+static PyObject *py_dsdb_dns_unix_to_dns_timestamp(PyObject *self, PyObject *args)
+{
+       uint32_t timestamp;
+       time_t t;
+       long long lt;
+
+       if (!PyArg_ParseTuple(args, "L", &lt)) {
+               return NULL;
+       }
+
+       t = lt;
+       if (t != lt) {
+               /* time_t is presumably 32 bit here */
+               PyErr_SetString(PyExc_ValueError, "Time out of range");
+               return NULL;
+       }
+       timestamp = unix_to_dns_timestamp(t);
+       return Py_BuildValue("k", (unsigned long) timestamp);
+}
+
+static PyObject *py_dsdb_dns_timestamp_to_nt_time(PyObject *self, PyObject *args)
+{
+       unsigned long long timestamp;
+       NTSTATUS status;
+       NTTIME nt;
+       if (!PyArg_ParseTuple(args, "K", &timestamp)) {
+               return NULL;
+       }
+
+       if (timestamp > UINT32_MAX || timestamp < 0) {
+               PyErr_SetString(PyExc_ValueError, "Time out of range");
+               return NULL;
+       }
+       status = dns_timestamp_to_nt_time(&nt, (uint32_t)timestamp);
+       if (!NT_STATUS_IS_OK(status)) {
+               PyErr_SetString(PyExc_ValueError, "Time out of range");
+               return NULL;
+       }
+       return Py_BuildValue("L", (long long) nt);
+}
+
+
 static PyMethodDef py_dsdb_dns_methods[] = {
 
        { "lookup", PY_DISCARD_FUNC_SIG(PyCFunction, py_dsdb_dns_lookup),
@@ -336,6 +378,12 @@ static PyMethodDef py_dsdb_dns_methods[] = {
                METH_VARARGS, "Replace the DNS database entries for a LDB DN"},
        { "extract", (PyCFunction)py_dsdb_dns_extract,
                METH_VARARGS, "Return the DNS database entry as a python structure from an Ldb.MessageElement of type dnsRecord"},
+       { "unix_to_dns_timestamp", (PyCFunction)py_dsdb_dns_unix_to_dns_timestamp,
+         METH_VARARGS,
+         "Convert a time.time() value to a dns timestamp (hours since 1601)"},
+       { "dns_timestamp_to_nt_time", (PyCFunction)py_dsdb_dns_timestamp_to_nt_time,
+         METH_VARARGS,
+         "Convert a dns timestamp to an NTTIME value"},
        {0}
 };
 
index 051f483c5e30e42230be3b61c2ad8e6265884f38..cd5730e672fad16e85681b3b20f7d37395320364 100755 (executable)
@@ -823,6 +823,8 @@ planoldpythontestsuite("ad_dc_default:local", "samba.tests.krb5.s4u_tests",
                                 'SERVICE_PASSWORD':'$PASSWORD',
                                 'FOR_USER':'$USERNAME'})
 
+planoldpythontestsuite("ad_dc_default", "samba.tests.dsdb_dns")
+
 planoldpythontestsuite("fl2008r2dc:local", "samba.tests.krb5.xrealm_tests")
 
 for env in ["ad_dc", smbv1_disabled_testenv]: