diff --git a/python/samba/netcmd/dns.py b/python/samba/netcmd/dns.py index a267c4105b5..11ca90b1f3e 100644 --- a/python/samba/netcmd/dns.py +++ b/python/samba/netcmd/dns.py @@ -505,6 +505,94 @@ class cmd_serverinfo(Command): print_serverinfo(self.outf, typeid, res) +def _add_integer_options(table, takes_options, integer_properties): + """Generate options for cmd_zoneoptions""" + for k, doc, _min, _max in table: + o = '--' + k.lower() + opt = Option(o, + help=f"{doc} [{_min}-{_max}]", + type="int", + dest=k) + takes_options.append(opt) + integer_properties.append((k, _min, _max, o)) + + +class cmd_zoneoptions(Command): + """Change zone aging options.""" + + synopsis = '%prog [options]' + + takes_args = ['server', 'zone'] + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "versionopts": options.VersionOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option('--client-version', help='Client Version', + default='longhorn', metavar='w2k|dotnet|longhorn', + choices=['w2k', 'dotnet', 'longhorn'], dest='cli_ver'), + ] + + integer_properties = [] + # Any zone parameter that is stored as an integer (which is most of + # them) can be added to this table. The name should be the dnsp + # mixed case name, which will get munged into a lowercase name for + # the option. (e.g. "Aging" becomes "--aging"). + # + # Note: just because we add a name here doesn't mean we will use + # it. + _add_integer_options([ + # ( name, help-string, min, max ) + ('Aging', 'Enable record aging', 0, 1), + ('NoRefreshInterval', + 'Aging no refresh interval in hours (0: use default)', + 0, 10 * 365 * 24), + ('RefreshInterval', + 'Aging refresh interval in hours (0: use default)', + 0, 10 * 365 * 24), + ], + takes_options, + integer_properties) + + def run(self, server, zone, cli_ver, sambaopts=None, credopts=None, + versionopts=None, **kwargs): + self.lp = sambaopts.get_loadparm() + self.creds = credopts.get_credentials(self.lp) + dns_conn = dns_connect(server, self.lp, self.creds) + + client_version = dns_client_version(cli_ver) + nap_type = dnsserver.DNSSRV_TYPEID_NAME_AND_PARAM + + for k, _min, _max, o in self.integer_properties: + if kwargs.get(k) is None: + continue + v = kwargs[k] + if _min is not None and v < _min: + raise CommandError(f"{o} must be at least {_min}") + if _max is not None and v > _max: + raise CommandError(f"{o} can't exceed {_max}") + + name_param = dnsserver.DNS_RPC_NAME_AND_PARAM() + name_param.dwParam = v + name_param.pszNodeName = k + try: + dns_conn.DnssrvOperation2(client_version, + 0, + server, + zone, + 0, + 'ResetDwordProperty', + nap_type, + name_param) + except WERRORError as e: + raise CommandError(f"Could not set {k} to {v}") from None + + print(f"Set {k} to {v}", file=self.outf) + + class cmd_zoneinfo(Command): """Query for zone information.""" @@ -1065,6 +1153,7 @@ class cmd_dns(SuperCommand): subcommands = {} subcommands['serverinfo'] = cmd_serverinfo() + subcommands['zoneoptions'] = cmd_zoneoptions() subcommands['zoneinfo'] = cmd_zoneinfo() subcommands['zonelist'] = cmd_zonelist() subcommands['zonecreate'] = cmd_zonecreate() diff --git a/python/samba/tests/samba_tool/dnscmd.py b/python/samba/tests/samba_tool/dnscmd.py index 356b2c46d05..0048e390ce5 100644 --- a/python/samba/tests/samba_tool/dnscmd.py +++ b/python/samba/tests/samba_tool/dnscmd.py @@ -17,6 +17,7 @@ import os import ldb +import re from samba.auth import system_session from samba.samdb import SamDB @@ -910,3 +911,56 @@ class DnsCmdTestCase(SambaToolCmdTest): err, "Failed to print zoneinfo") self.assertTrue(out != '') + + def test_zoneoptions(self): + for options, vals, error in ( + (['--aging=1'], {'fAging': 'TRUE'}, False), + (['--aging=0'], {'fAging': 'FALSE'}, False), + (['--aging=-1'], {'fAging': 'FALSE'}, True), + (['--aging=2'], {}, True), + (['--aging=2', '--norefreshinterval=1'], {}, True), + (['--aging=1', '--norefreshinterval=1'], + {'fAging': 'TRUE', 'dwNoRefreshInterval': '1'}, False), + (['--aging=1', '--norefreshinterval=0'], + {'fAging': 'TRUE', 'dwNoRefreshInterval': '0'}, False), + (['--aging=0', '--norefreshinterval=99', '--refreshinterval=99'], + {'fAging': 'FALSE', + 'dwNoRefreshInterval': '99', + 'dwRefreshInterval': '99'}, False), + (['--aging=0', '--norefreshinterval=-99', '--refreshinterval=99'], + {}, True), + (['--refreshinterval=9999999'], {}, True), + (['--norefreshinterval=9999999'], {}, True), + ): + result, out, err = self.runsubcmd("dns", + "zoneoptions", + os.environ["SERVER"], + self.zone, + self.creds_string, + *options) + if error: + self.assertCmdFail(result, "zoneoptions should fail") + else: + self.assertCmdSuccess(result, + out, + err, + "zoneoptions shouldn't fail") + + + info_r, info_out, info_err = self.runsubcmd("dns", + "zoneinfo", + os.environ["SERVER"], + self.zone, + self.creds_string) + + self.assertCmdSuccess(info_r, + info_out, + info_err, + "zoneinfo shouldn't fail after zoneoptions") + + info = {k: v for k, v in re.findall(r'^\s*(\w+)\s*:\s*(\w+)\s*$', + info_out, + re.MULTILINE)} + for k, v in vals.items(): + self.assertIn(k, info) + self.assertEqual(v, info[k])