From 4cbc34f2edf8a82986948860f1054cf37d17a4fe Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 1 Feb 2013 13:14:05 +0100 Subject: [PATCH 1/8] s3: use generate_random_password() instead of generate_random_str() Signed-off-by: Stefan Metzmacher Reviewed-by: Michael Adam (cherry picked from commit 9292e5b74310632e1f0b4b2b76a9ef4ccae6874e) --- source3/libads/util.c | 6 ++++-- source3/libnet/libnet_join.c | 8 ++++++-- source3/libsmb/trusts_util.c | 7 ++++--- source3/utils/net_rpc_join.c | 4 +++- source3/utils/net_rpc_trust.c | 8 ++++---- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/source3/libads/util.c b/source3/libads/util.c index 6a6b42a..2e22bca 100644 --- a/source3/libads/util.c +++ b/source3/libads/util.c @@ -35,8 +35,10 @@ ADS_STATUS ads_change_trust_account_password(ADS_STRUCT *ads, char *host_princip return ADS_ERROR_SYSTEM(ENOENT); } - new_password = generate_random_str(talloc_tos(), DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH); - + new_password = generate_random_password(talloc_tos(), + DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH, + DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH); + ret = kerberos_set_password(ads->auth.kdc_server, host_principal, password, host_principal, new_password, ads->auth.time_offset); if (!ADS_ERR_OK(ret)) { diff --git a/source3/libnet/libnet_join.c b/source3/libnet/libnet_join.c index d6aa793..3d0a6d3 100644 --- a/source3/libnet/libnet_join.c +++ b/source3/libnet/libnet_join.c @@ -811,7 +811,9 @@ static NTSTATUS libnet_join_joindomain_rpc_unsecure(TALLOC_CTX *mem_ctx, } if (!r->in.machine_password) { - r->in.machine_password = generate_random_str(mem_ctx, DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH); + r->in.machine_password = generate_random_password(mem_ctx, + DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH, + DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH); NT_STATUS_HAVE_NO_MEMORY(r->in.machine_password); } @@ -882,7 +884,9 @@ static NTSTATUS libnet_join_joindomain_rpc(TALLOC_CTX *mem_ctx, } if (!r->in.machine_password) { - r->in.machine_password = generate_random_str(mem_ctx, DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH); + r->in.machine_password = generate_random_password(mem_ctx, + DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH, + DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH); NT_STATUS_HAVE_NO_MEMORY(r->in.machine_password); } diff --git a/source3/libsmb/trusts_util.c b/source3/libsmb/trusts_util.c index be1f1f8..0d039bc 100644 --- a/source3/libsmb/trusts_util.c +++ b/source3/libsmb/trusts_util.c @@ -52,10 +52,11 @@ NTSTATUS trust_pw_change_and_store_it(struct rpc_pipe_client *cli, TALLOC_CTX *m } /* Create a random machine account password */ - new_trust_passwd = generate_random_str(mem_ctx, DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH); - + new_trust_passwd = generate_random_password(mem_ctx, + DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH, + DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH); if (new_trust_passwd == NULL) { - DEBUG(0, ("talloc_strdup failed\n")); + DEBUG(0, ("generate_random_password failed\n")); return NT_STATUS_NO_MEMORY; } diff --git a/source3/utils/net_rpc_join.c b/source3/utils/net_rpc_join.c index ed81aac..7167cf9 100644 --- a/source3/utils/net_rpc_join.c +++ b/source3/utils/net_rpc_join.c @@ -401,7 +401,9 @@ int net_rpc_join_newstyle(struct net_context *c, int argc, const char **argv) /* Create a random machine account password */ - clear_trust_password = generate_random_str(talloc_tos(), DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH); + clear_trust_password = generate_random_password(talloc_tos(), + DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH, + DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH); E_md4hash(clear_trust_password, md4_trust_password); /* Set password on machine account */ diff --git a/source3/utils/net_rpc_trust.c b/source3/utils/net_rpc_trust.c index d15d10c..9060700 100644 --- a/source3/utils/net_rpc_trust.c +++ b/source3/utils/net_rpc_trust.c @@ -518,11 +518,11 @@ static int rpc_trust_common(struct net_context *net_ctx, int argc, } DEBUG(0, ("Using random trust password.\n")); - /* FIXME: why only 8 characters work? Would it be possible to use a - * random binary password? */ - trust_pw = generate_random_str(mem_ctx, 8); + trust_pw = generate_random_password(mem_ctx, + DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH, + DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH); if (trust_pw == NULL) { - DEBUG(0, ("generate_random_str failed.\n")); + DEBUG(0, ("generate_random_password failed.\n")); goto done; } } else { -- 1.7.9.5 From e6feb34845bb55c0b96b1febb55978396736dc46 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 4 Feb 2013 09:18:59 +0100 Subject: [PATCH 2/8] dsdb/password_hash: make sure that io->n.cleartext_utf8.data is a null terminated string Signed-off-by: Stefan Metzmacher Reviewed-by: Michael Adam (cherry picked from commit 6eccfc74cd9a16e96a2b6214b943f5b2f9adfe65) --- source4/dsdb/samdb/ldb_modules/password_hash.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/source4/dsdb/samdb/ldb_modules/password_hash.c b/source4/dsdb/samdb/ldb_modules/password_hash.c index 9bf596c..6b8cd9c 100644 --- a/source4/dsdb/samdb/ldb_modules/password_hash.c +++ b/source4/dsdb/samdb/ldb_modules/password_hash.c @@ -2203,6 +2203,29 @@ static int setup_io(struct ph_context *ac, } } + if (io->n.cleartext_utf8 != NULL) { + struct ldb_val *cleartext_utf8_blob; + char *p; + + cleartext_utf8_blob = talloc(io->ac, struct ldb_val); + if (!cleartext_utf8_blob) { + return ldb_oom(ldb); + } + + *cleartext_utf8_blob = *io->n.cleartext_utf8; + + /* make sure we have a null terminated string */ + p = talloc_strndup(cleartext_utf8_blob, + (const char *)io->n.cleartext_utf8->data, + io->n.cleartext_utf8->length); + if ((p == NULL) && (io->n.cleartext_utf8->length > 0)) { + return ldb_oom(ldb); + } + cleartext_utf8_blob->data = (uint8_t *)p; + + io->n.cleartext_utf8 = cleartext_utf8_blob; + } + ret = msg_find_old_and_new_pwd_val(orig_msg, "clearTextPassword", ac->req->operation, &io->n.cleartext_utf16, -- 1.7.9.5 From 5a32c0ff32885cd18e3047787b2e47a4cd50b9e4 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 4 Feb 2013 09:47:31 +0100 Subject: [PATCH 3/8] dsdb/password_hash: rename variable 'stat' to 'vstat' Signed-off-by: Stefan Metzmacher Reviewed-by: Michael Adam (cherry picked from commit 54cc3b1f42eba19170e611b0ee0ea464ea4ac604) --- source4/dsdb/samdb/ldb_modules/password_hash.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/password_hash.c b/source4/dsdb/samdb/ldb_modules/password_hash.c index 6b8cd9c..e9c2bca 100644 --- a/source4/dsdb/samdb/ldb_modules/password_hash.c +++ b/source4/dsdb/samdb/ldb_modules/password_hash.c @@ -1876,7 +1876,6 @@ static int check_password_restrictions(struct setup_password_fields_io *io) { struct ldb_context *ldb; int ret; - enum samr_ValidationStatus stat; ldb = ldb_module_get_ctx(io->ac->module); @@ -1973,10 +1972,11 @@ static int check_password_restrictions(struct setup_password_fields_io *io) * It is also in use by "dcesrv_samr_ValidatePassword". */ if (io->n.cleartext_utf8 != NULL) { - stat = samdb_check_password(io->n.cleartext_utf8, - io->ac->status->domain_data.pwdProperties, - io->ac->status->domain_data.minPwdLength); - switch (stat) { + enum samr_ValidationStatus vstat; + vstat = samdb_check_password(io->n.cleartext_utf8, + io->ac->status->domain_data.pwdProperties, + io->ac->status->domain_data.minPwdLength); + switch (vstat) { case SAMR_VALIDATION_STATUS_SUCCESS: /* perfect -> proceed! */ break; -- 1.7.9.5 From 06a4d298f7b01702cf876d5990f4bf774dc74705 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 4 Feb 2013 09:19:54 +0100 Subject: [PATCH 4/8] dsdb/util: rework samdb_check_password() to support utf8 Signed-off-by: Stefan Metzmacher Reviewed-by: Michael Adam (cherry picked from commit e5ca813ffb4398faeefc96c224d3b2677e576c7a) --- source4/dsdb/common/util.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c index 2b96bd4..8e40776 100644 --- a/source4/dsdb/common/util.c +++ b/source4/dsdb/common/util.c @@ -1906,19 +1906,30 @@ int samdb_search_for_parent_domain(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, * * Result codes from "enum samr_ValidationStatus" (consider "samr.idl") */ -enum samr_ValidationStatus samdb_check_password(const DATA_BLOB *password, +enum samr_ValidationStatus samdb_check_password(const DATA_BLOB *utf8_blob, const uint32_t pwdProperties, const uint32_t minPwdLength) { + const char *utf8_pw = (const char *)utf8_blob->data; + size_t utf8_len = strlen_m(utf8_pw); + /* checks if the "minPwdLength" property is satisfied */ - if (minPwdLength > password->length) + if (minPwdLength > utf8_len) { return SAMR_VALIDATION_STATUS_PWD_TOO_SHORT; + } /* checks the password complexity */ - if (((pwdProperties & DOMAIN_PASSWORD_COMPLEX) != 0) - && (password->data != NULL) - && (!check_password_quality((const char *) password->data))) + if (!(pwdProperties & DOMAIN_PASSWORD_COMPLEX)) { + return SAMR_VALIDATION_STATUS_SUCCESS; + } + + if (utf8_len == 0) { return SAMR_VALIDATION_STATUS_NOT_COMPLEX_ENOUGH; + } + + if (!check_password_quality(utf8_pw)) { + return SAMR_VALIDATION_STATUS_NOT_COMPLEX_ENOUGH; + } return SAMR_VALIDATION_STATUS_SUCCESS; } -- 1.7.9.5 From 58e6d66eb4e17a1dc9fe2ec84b7f9e6c39e7bc9b Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 4 Feb 2013 08:45:48 +0100 Subject: [PATCH 5/8] lib/util: improve check_password_quality() to handle utf8 Signed-off-by: Stefan Metzmacher Reviewed-by: Michael Adam (cherry picked from commit 65f2bba559a33edb3c352d552aebb259e5e008eb) --- lib/util/genrand.c | 134 +++++++++++++++++++++++++++++++++++++++------- lib/util/tests/genrand.c | 5 +- 2 files changed, 120 insertions(+), 19 deletions(-) diff --git a/lib/util/genrand.c b/lib/util/genrand.c index 57884ef..3dfaf08 100644 --- a/lib/util/genrand.c +++ b/lib/util/genrand.c @@ -298,29 +298,127 @@ _PUBLIC_ uint32_t generate_random(void) /** - very basic password quality checker + Microsoft composed the following rules (among others) for quality + checks. This is an abridgment from + http://msdn.microsoft.com/en-us/subscriptions/cc786468%28v=ws.10%29.aspx: + + Passwords must contain characters from three of the following five + categories: + + - Uppercase characters of European languages (A through Z, with + diacritic marks, Greek and Cyrillic characters) + - Lowercase characters of European languages (a through z, sharp-s, + with diacritic marks, Greek and Cyrillic characters) + - Base 10 digits (0 through 9) + - Nonalphanumeric characters: ~!@#$%^&*_-+=`|\(){}[]:;"'<>,.?/ + - Any Unicode character that is categorized as an alphabetic character + but is not uppercase or lowercase. This includes Unicode characters + from Asian languages. + + Note: for now do not check if the unicode category is + alphabetic character **/ -_PUBLIC_ bool check_password_quality(const char *s) +_PUBLIC_ bool check_password_quality(const char *pwd) { - int has_digit=0, has_capital=0, has_lower=0, has_special=0, has_high=0; - const char* reals = s; - while (*s) { - if (isdigit((unsigned char)*s)) { - has_digit |= 1; - } else if (isupper((unsigned char)*s)) { - has_capital |= 1; - } else if (islower((unsigned char)*s)) { - has_lower |= 1; - } else if (isascii((unsigned char)*s)) { - has_special |= 1; - } else { - has_high++; + size_t ofs = 0; + size_t num_chars = 0; + size_t num_digits = 0; + size_t num_upper = 0; + size_t num_lower = 0; + size_t num_nonalpha = 0; + size_t num_unicode = 0; + size_t num_categories = 0; + + if (pwd == NULL) { + return false; + } + + while (true) { + const char *s = &pwd[ofs]; + size_t len = 0; + codepoint_t c; + + c = next_codepoint(s, &len); + if (c == INVALID_CODEPOINT) { + return false; + } else if (c == 0) { + break; + } + ofs += len; + num_chars += 1; + + if (len == 1) { + const char *na = "~!@#$%^&*_-+=`|\\(){}[]:;\"'<>,.?/"; + + if (isdigit(c)) { + num_digits += 1; + continue; + } + + if (isupper(c)) { + num_upper += 1; + continue; + } + + if (islower(c)) { + num_lower += 1; + continue; + } + + if (strchr(na, c)) { + num_nonalpha += 1; + continue; + } + + /* + * the rest does not belong to + * a category. + */ + continue; } - s++; + + if (isupper_m(c)) { + num_upper += 1; + continue; + } + + if (islower_m(c)) { + num_lower += 1; + continue; + } + + /* + * Note: for now do not check if the unicode category is + * alphabetic character + * + * We would have to import the details from + * ftp://ftp.unicode.org/Public/6.3.0/ucd/UnicodeData-6.3.0d1.txt + */ + num_unicode += 1; + continue; + } + + if (num_digits > 0) { + num_categories += 1; + } + if (num_upper > 0) { + num_categories += 1; + } + if (num_lower > 0) { + num_categories += 1; + } + if (num_nonalpha > 0) { + num_categories += 1; + } + if (num_unicode > 0) { + num_categories += 1; + } + + if (num_categories >= 3) { + return true; } - return ((has_digit + has_lower + has_capital + has_special) >= 3 - || (has_high > strlen(reals)/2)); + return false; } /** diff --git a/lib/util/tests/genrand.c b/lib/util/tests/genrand.c index 50d77bb..ff608cd 100644 --- a/lib/util/tests/genrand.c +++ b/lib/util/tests/genrand.c @@ -41,7 +41,10 @@ static bool test_check_password_quality(struct torture_context *tctx) torture_assert(tctx, !check_password_quality("BLA"), "multiple upcases password"); torture_assert(tctx, !check_password_quality("123"), "digits only"); torture_assert(tctx, !check_password_quality("matthiéu"), "not enough high symbols"); - torture_assert(tctx, check_password_quality("abcdééàçè"), "valid"); + torture_assert(tctx, !check_password_quality("abcdééàçè"), "only lower case"); + torture_assert(tctx, !check_password_quality("abcdééàçè+"), "only lower and symbols"); + torture_assert(tctx, check_password_quality("abcdééàçè+ढ"), "valid"); + torture_assert(tctx, check_password_quality("ç+ढ"), "valid"); torture_assert(tctx, check_password_quality("A2e"), "valid"); torture_assert(tctx, check_password_quality("BA2eLi443"), "valid"); return true; -- 1.7.9.5 From 13310d0181cff9e9fa18cf0cab59fbb5da94dcb5 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 4 Feb 2013 11:41:39 +0100 Subject: [PATCH 6/8] s4:scripting/python: add support for utf-8 passwords from the command line Signed-off-by: Stefan Metzmacher Reviewed-by: Michael Adam (cherry picked from commit d60be8167b7264dadae7d4735ee5977233d4cea9) --- source4/scripting/python/samba/samdb.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source4/scripting/python/samba/samdb.py b/source4/scripting/python/samba/samdb.py index d83e0a6..5478e24 100644 --- a/source4/scripting/python/samba/samdb.py +++ b/source4/scripting/python/samba/samdb.py @@ -449,12 +449,13 @@ member: %s if len(res) > 1: raise Exception('Matched %u multiple users with filter "%s"' % (len(res), search_filter)) user_dn = res[0].dn + pw = unicode('"' + password + '"', 'utf-8').encode('utf-16-le') setpw = """ dn: %s changetype: modify replace: unicodePwd unicodePwd:: %s -""" % (user_dn, base64.b64encode(("\"" + password + "\"").encode('utf-16-le'))) +""" % (user_dn, base64.b64encode(pw)) self.modify_ldif(setpw) -- 1.7.9.5 From a2c3931a9640d7fb23e46c9c472153333e16f3c9 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 4 Feb 2013 13:35:48 +0100 Subject: [PATCH 7/8] samba-tool/user setpassword: fix help message Signed-off-by: Stefan Metzmacher Reviewed-by: Michael Adam (cherry picked from commit 2e7bc87fa54148655ce13a59bd3274fb6285a579) --- source4/scripting/python/samba/netcmd/user.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source4/scripting/python/samba/netcmd/user.py b/source4/scripting/python/samba/netcmd/user.py index dc50d77..7d045a8 100644 --- a/source4/scripting/python/samba/netcmd/user.py +++ b/source4/scripting/python/samba/netcmd/user.py @@ -480,17 +480,17 @@ It is good security practice for the administrator to use the --must-change-at-n The command may be run from the root userid or another authorized userid. The -H or --URL= option can be used to execute the command against a remote server. Example1: -samba-tool user setpassword TestUser1 passw0rd --URL=ldap://samba.samdom.example.com -Uadministrator%passw1rd +samba-tool user setpassword TestUser1 --newpassword=passw0rd --URL=ldap://samba.samdom.example.com -Uadministrator%passw1rd Example1 shows how to set the password of user TestUser1 on a remote LDAP server. The --URL parameter is used to specify the remote target server. The -U option is used to pass the username and password of a user that exists on the remote server and is authorized to update the server. Example2: -sudo samba-tool user setpassword TestUser2 passw0rd --must-change-at-next-login +sudo samba-tool user setpassword TestUser2 --newpassword=passw0rd --must-change-at-next-login Example2 shows how an administrator would reset the TestUser2 user's password to passw0rd. The user is running under the root userid using the sudo command. In this example the user TestUser2 must change their password the next time they logon to the account. Example3: -samba-tool user setpassword --filter=samaccountname=TestUser3 --password=passw0rd +samba-tool user setpassword --filter=samaccountname=TestUser3 --newpassword=passw0rd Example3 shows how an administrator would reset TestUser3 user's password to passw0rd using the --filter= option to specify the username. -- 1.7.9.5 From 8cdfe35f79343e6dfe2a944ed4cf818cf2e4b0ce Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 4 Feb 2013 11:41:39 +0100 Subject: [PATCH 8/8] samba-tool/domain provision: add support for utf-8 passwords for --adminpass Signed-off-by: Stefan Metzmacher Reviewed-by: Michael Adam Autobuild-User(master): Michael Adam Autobuild-Date(master): Mon Feb 4 18:54:32 CET 2013 on sn-devel-104 (cherry picked from commit dc6c40b193e125e8810cf95129fc99f7d4f6db27) --- .../scripting/python/samba/provision/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source4/scripting/python/samba/provision/__init__.py b/source4/scripting/python/samba/provision/__init__.py index 3c878e9..a5385e7 100644 --- a/source4/scripting/python/samba/provision/__init__.py +++ b/source4/scripting/python/samba/provision/__init__.py @@ -2128,6 +2128,7 @@ def provision(logger, session_info, credentials, smbconf=None, adminpass = samba.generate_random_password(12, 32) adminpass_generated = True else: + adminpass = unicode(adminpass, 'utf-8') adminpass_generated = False if samdb_fill == FILL_FULL: -- 1.7.9.5