diff --git a/python/pyglue.c b/python/pyglue.c
index 07cde49e16b..d928b4cda41 100644
--- a/python/pyglue.c
+++ b/python/pyglue.c
@@ -78,6 +78,17 @@ static PyObject *py_generate_random_machine_password(PyObject *self, PyObject *a
return ret;
}
+static PyObject *py_check_password_quality(PyObject *self, PyObject *args)
+{
+ char *pass;
+
+ if (!PyArg_ParseTuple(args, "s", &pass)) {
+ return NULL;
+ }
+
+ return PyBool_FromLong(check_password_quality(pass));
+}
+
static PyObject *py_unix2nttime(PyObject *self, PyObject *args)
{
time_t t;
@@ -296,6 +307,12 @@ static PyMethodDef py_misc_methods[] = {
"(based on random utf16 characters converted to utf8 or "
"random ascii characters if 'unix charset' is not 'utf8')"
"with a length >= min (at least 14) and <= max (at most 255)." },
+ { "check_password_quality", (PyCFunction)py_check_password_quality,
+ METH_VARARGS, "check_password_quality(pass) -> bool\n"
+ "Check password quality against Samba's check_password_quality,"
+ "the implementation of Microsoft's rules:"
+ "http://msdn.microsoft.com/en-us/subscriptions/cc786468%28v=ws.10%29.aspx"
+ },
{ "unix2nttime", (PyCFunction)py_unix2nttime, METH_VARARGS,
"unix2nttime(timestamp) -> nttime" },
{ "nttime2unix", (PyCFunction)py_nttime2unix, METH_VARARGS,
diff --git a/python/samba/__init__.py b/python/samba/__init__.py
index 6f79b3cc960..6ba7c998074 100644
--- a/python/samba/__init__.py
+++ b/python/samba/__init__.py
@@ -388,6 +388,7 @@ nttime2unix = _glue.nttime2unix
unix2nttime = _glue.unix2nttime
generate_random_password = _glue.generate_random_password
generate_random_machine_password = _glue.generate_random_machine_password
+check_password_quality = _glue.check_password_quality
strcasecmp_m = _glue.strcasecmp_m
strstr_m = _glue.strstr_m
is_ntvfs_fileserver_built = _glue.is_ntvfs_fileserver_built
diff --git a/python/samba/netcmd/domain.py b/python/samba/netcmd/domain.py
index e3a0e4921f2..f54b4045e93 100644
--- a/python/samba/netcmd/domain.py
+++ b/python/samba/netcmd/domain.py
@@ -84,7 +84,8 @@ from samba.dsdb import (
from samba.provision import (
provision,
- ProvisioningError
+ ProvisioningError,
+ DEFAULT_MIN_PWD_LENGTH
)
from samba.provision.common import (
@@ -370,8 +371,9 @@ class cmd_domain_provision(Command):
while True:
adminpassplain = getpass("Administrator password: ")
- if not adminpassplain:
- self.errf.write("Invalid administrator password.\n")
+ issue = self._adminpass_issue(adminpassplain)
+ if issue:
+ self.errf.write("%s.\n" % issue)
else:
adminpassverify = getpass("Retype password: ")
if not adminpassplain == adminpassverify:
@@ -387,7 +389,11 @@ class cmd_domain_provision(Command):
if domain is None:
raise CommandError("No domain set!")
- if not adminpass:
+ if adminpass:
+ issue = self._adminpass_issue(adminpass)
+ if issue:
+ raise CommandError(issue)
+ else:
self.logger.info("Administrator password will be set randomly!")
if function_level == "2000":
@@ -501,6 +507,20 @@ class cmd_domain_provision(Command):
self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
+ def _adminpass_issue(self, adminpass):
+ """Returns error string for a bad administrator password,
+ or None if acceptable"""
+
+ if len(adminpass.decode('utf-8')) < DEFAULT_MIN_PWD_LENGTH:
+ return "Administrator password does not meet the default minimum" \
+ " password length requirement (%d characters)" \
+ % DEFAULT_MIN_PWD_LENGTH
+ elif not samba.check_password_quality(adminpass):
+ return "Administrator password does not meet the default" \
+ " quality standards"
+ else:
+ return None
+
class cmd_domain_dcpromo(Command):
"""Promote an existing domain member or NT4 PDC to an AD DC."""
diff --git a/python/samba/provision/__init__.py b/python/samba/provision/__init__.py
index 07c24795477..ad1430534c2 100644
--- a/python/samba/provision/__init__.py
+++ b/python/samba/provision/__init__.py
@@ -127,6 +127,8 @@ DEFAULT_DC_POLICY_GUID = "6AC1786C-016F-11D2-945F-00C04FB984F9"
DEFAULTSITE = "Default-First-Site-Name"
LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN"
+DEFAULT_MIN_PWD_LENGTH = 7
+
class ProvisionPaths(object):
@@ -1309,7 +1311,8 @@ def fill_samdb(samdb, lp, names, logger, policyguid,
"CONFIGDN": names.configdn,
"POLICYGUID": policyguid,
"DOMAIN_FUNCTIONALITY": str(domainFunctionality),
- "SAMBA_VERSION_STRING": version
+ "SAMBA_VERSION_STRING": version,
+ "MIN_PWD_LENGTH": str(DEFAULT_MIN_PWD_LENGTH)
})
# If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
diff --git a/python/samba/tests/password_quality.py b/python/samba/tests/password_quality.py
new file mode 100644
index 00000000000..890060e2796
--- /dev/null
+++ b/python/samba/tests/password_quality.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+# Unix SMB/CIFS implementation.
+# Copyright (C) Catalyst IT Ltd. 2017
+#
+# 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 .
+#
+
+"""Tests for the python wrapper of the check_password_quality function
+"""
+
+from samba import check_password_quality
+from samba.tests import TestCase, TestCaseInTempDir
+
+class PasswordQualityTests(TestCase):
+ def test_check_password_quality(self):
+ self.assertFalse(check_password_quality(""),
+ "empty password")
+ self.assertFalse(check_password_quality("a"),
+ "one char password")
+ self.assertFalse(check_password_quality("aaaaaaaaaaaa"),
+ "same char password")
+ self.assertFalse(check_password_quality("BLA"),
+ "multiple upcases password")
+ self.assertFalse(check_password_quality("123"),
+ "digits only")
+ self.assertFalse(check_password_quality("matthiéu"),
+ "not enough high symbols")
+ self.assertFalse(check_password_quality("abcdééàçè"),
+ "only lower case")
+ self.assertFalse(check_password_quality("abcdééàçè+"),
+ "only lower and symbols")
+ self.assertTrue(check_password_quality("abcdééàçè+ढ"),
+ "valid")
+ self.assertTrue(check_password_quality("ç+ढ"),
+ "valid")
+ self.assertTrue(check_password_quality("A2e"),
+ "valid")
+ self.assertTrue(check_password_quality("BA2eLi443"),
+ "valid")
diff --git a/python/samba/tests/samba_tool/provision_password_check.py b/python/samba/tests/samba_tool/provision_password_check.py
new file mode 100644
index 00000000000..b24e5804072
--- /dev/null
+++ b/python/samba/tests/samba_tool/provision_password_check.py
@@ -0,0 +1,56 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Catalyst IT Ltd. 2017
+#
+# 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 .
+#
+
+from samba.tests.samba_tool.base import SambaToolCmdTest
+import os
+import shutil
+
+class ProvisionPasswordTestCase(SambaToolCmdTest):
+ """Test for password validation in domain provision subcommand"""
+
+ def setUp(self):
+ super(SambaToolCmdTest, self).setUp()
+ self.tempsambadir = os.path.join(self.tempdir, "samba")
+ os.mkdir(self.tempsambadir)
+
+ def _provision_with_password(self, password):
+ return self.runsubcmd(
+ "domain", "provision", "--realm=foo.example.com", "--domain=FOO",
+ "--targetdir=%s" % self.tempsambadir, "--adminpass=%s" % password,
+ "--use-ntvfs")
+
+ def test_short_and_low_quality(self):
+ (result, out, err) = self._provision_with_password("foo")
+ self.assertCmdFail(result)
+
+ def test_short(self):
+ (result, out, err) = self._provision_with_password("Fo0!_9")
+ self.assertCmdFail(result)
+ self.assertRegexpMatches(err, r"minimum password length")
+
+ def test_low_quality(self):
+ (result, out, err) = self._provision_with_password("aaaaaaaaaaaaaaaaa")
+ self.assertCmdFail(result)
+ self.assertRegexpMatches(err, r"quality standards")
+
+ def test_good(self):
+ (result, out, err) = self._provision_with_password("Fo0!_9.")
+ self.assertCmdSuccess(result, out, err)
+
+ def tearDown(self):
+ super(SambaToolCmdTest, self).tearDown()
+ shutil.rmtree(self.tempsambadir)
diff --git a/selftest/knownfail.d/validate-password-early b/selftest/knownfail.d/validate-password-early
new file mode 100644
index 00000000000..76f48aca260
--- /dev/null
+++ b/selftest/knownfail.d/validate-password-early
@@ -0,0 +1 @@
+^samba4.blackbox.provision-backend.openldap-mmr-backend
\ No newline at end of file
diff --git a/selftest/tests.py b/selftest/tests.py
index 209800c8ba4..e65d63d8c6a 100644
--- a/selftest/tests.py
+++ b/selftest/tests.py
@@ -66,6 +66,7 @@ planpythontestsuite("none", "samba.tests.param", py3_compatible=True)
planpythontestsuite("none", "samba.tests.upgrade")
planpythontestsuite("none", "samba.tests.core", py3_compatible=True)
planpythontestsuite("none", "samba.tests.provision")
+planpythontestsuite("none", "samba.tests.password_quality")
planpythontestsuite("none", "samba.tests.samba3")
planpythontestsuite("none", "samba.tests.strings")
planpythontestsuite("none", "samba.tests.netcmd")
diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py
index a9ed46f5c8e..ec9f5c0d403 100755
--- a/source4/selftest/tests.py
+++ b/source4/selftest/tests.py
@@ -609,6 +609,7 @@ planpythontestsuite("ad_dc:local", "samba.tests.samba_tool.user_virtualCryptSHA"
planpythontestsuite("chgdcpass:local", "samba.tests.samba_tool.user_check_password_script")
planpythontestsuite("ad_dc_ntvfs:local", "samba.tests.samba_tool.group")
planpythontestsuite("ad_dc:local", "samba.tests.samba_tool.ntacl")
+planpythontestsuite("none", "samba.tests.samba_tool.provision_password_check")
planpythontestsuite("ad_dc:local", "samba.tests.samba_tool.sites")
planpythontestsuite("ad_dc:local", "samba.tests.samba_tool.dnscmd")
diff --git a/source4/setup/provision_basedn_modify.ldif b/source4/setup/provision_basedn_modify.ldif
index a5e704769db..7d852ba33a7 100644
--- a/source4/setup/provision_basedn_modify.ldif
+++ b/source4/setup/provision_basedn_modify.ldif
@@ -39,7 +39,7 @@ replace: minPwdAge
minPwdAge: -864000000000
-
replace: minPwdLength
-minPwdLength: 7
+minPwdLength: ${MIN_PWD_LENGTH}
-
replace: modifiedCount
modifiedCount: 1