Fix more compiler warnings.
[metze/samba/wip.git] / source4 / torture / rpc / samr.c
index 87690178a73246bcc4dc45e59f77a71770274720..821cac341e7646b16b12619bc229c4b1ddd9a6bc 100644 (file)
@@ -29,6 +29,8 @@
 #include "libcli/security/security.h"
 #include "torture/rpc/rpc.h"
 
+#include <unistd.h>
+
 #define TEST_ACCOUNT_NAME "samrtorturetest"
 #define TEST_ALIASNAME "samrtorturetestalias"
 #define TEST_GROUPNAME "samrtorturetestgroup"
@@ -37,6 +39,7 @@
 
 enum torture_samr_choice {
        TORTURE_SAMR_PASSWORDS,
+       TORTURE_SAMR_PASSWORDS_PWDLASTSET,
        TORTURE_SAMR_USER_ATTRIBUTES,
        TORTURE_SAMR_OTHER
 };
@@ -406,6 +409,12 @@ static bool test_SetUserInfo(struct dcerpc_pipe *p, struct torture_context *tctx
                           SAMR_FIELD_PARAMETERS);
        TEST_USERINFO_BINARYSTRING(21, parameters, 20, parameters, "xx21-20 parameters",
                           SAMR_FIELD_PARAMETERS);
+       /* also empty user parameters are allowed */
+       TEST_USERINFO_BINARYSTRING(20, parameters, 21, parameters, "", 0);
+       TEST_USERINFO_BINARYSTRING(21, parameters, 21, parameters, "",
+                          SAMR_FIELD_PARAMETERS);
+       TEST_USERINFO_BINARYSTRING(21, parameters, 20, parameters, "",
+                          SAMR_FIELD_PARAMETERS);
 
        TEST_USERINFO_INT(2, country_code, 2, country_code, __LINE__, 0);
        TEST_USERINFO_INT(2, country_code, 21, country_code, __LINE__, 0);
@@ -508,12 +517,19 @@ static bool test_SetUserInfo(struct dcerpc_pipe *p, struct torture_context *tctx
 /*
   generate a random password for password change tests
 */
-static char *samr_rand_pass(TALLOC_CTX *mem_ctx, int min_len)
+static char *samr_rand_pass_silent(TALLOC_CTX *mem_ctx, int min_len)
 {
        size_t len = MAX(8, min_len) + (random() % 6);
        char *s = generate_random_str(mem_ctx, len);
+       return s;
+}
+
+static char *samr_rand_pass(TALLOC_CTX *mem_ctx, int min_len)
+{
+       char *s = samr_rand_pass_silent(mem_ctx, min_len);
        printf("Generated password '%s'\n", s);
        return s;
+
 }
 
 /*
@@ -570,8 +586,7 @@ static bool test_SetUserPass(struct dcerpc_pipe *p, struct torture_context *tctx
        s.in.level = 24;
 
        encode_pw_buffer(u.info24.password.data, newpass, STR_UNICODE);
-       /* w2k3 ignores this length */
-       u.info24.pw_len = strlen_m(newpass) * 2;
+       u.info24.password_expired = 0;
 
        status = dcerpc_fetch_session_key(p, &session_key);
        if (!NT_STATUS_IS_OK(status)) {
@@ -709,7 +724,7 @@ static bool test_SetUserPassEx(struct dcerpc_pipe *p, struct torture_context *tc
        s.in.level = 26;
 
        encode_pw_buffer(u.info26.password.data, newpass, STR_UNICODE);
-       u.info26.pw_len = strlen(newpass);
+       u.info26.password_expired = 0;
 
        status = dcerpc_fetch_session_key(p, &session_key);
        if (!NT_STATUS_IS_OK(status)) {
@@ -840,6 +855,415 @@ static bool test_SetUserPass_25(struct dcerpc_pipe *p, struct torture_context *t
        return ret;
 }
 
+static bool test_SetUserPass_18(struct dcerpc_pipe *p, struct torture_context *tctx,
+                               struct policy_handle *handle, char **password)
+{
+       NTSTATUS status;
+       struct samr_SetUserInfo s;
+       union samr_UserInfo u;
+       bool ret = true;
+       DATA_BLOB session_key;
+       char *newpass;
+       struct samr_GetUserPwInfo pwp;
+       struct samr_PwInfo info;
+       int policy_min_pw_len = 0;
+       uint8_t lm_hash[16], nt_hash[16];
+
+       pwp.in.user_handle = handle;
+       pwp.out.info = &info;
+
+       status = dcerpc_samr_GetUserPwInfo(p, tctx, &pwp);
+       if (NT_STATUS_IS_OK(status)) {
+               policy_min_pw_len = pwp.out.info->min_password_length;
+       }
+       newpass = samr_rand_pass(tctx, policy_min_pw_len);
+
+       s.in.user_handle = handle;
+       s.in.info = &u;
+       s.in.level = 18;
+
+       ZERO_STRUCT(u);
+
+       u.info18.nt_pwd_active = true;
+       u.info18.lm_pwd_active = true;
+
+       E_md4hash(newpass, nt_hash);
+       E_deshash(newpass, lm_hash);
+
+       status = dcerpc_fetch_session_key(p, &session_key);
+       if (!NT_STATUS_IS_OK(status)) {
+               printf("SetUserInfo level %u - no session key - %s\n",
+                      s.in.level, nt_errstr(status));
+               return false;
+       }
+
+       {
+               DATA_BLOB in,out;
+               in = data_blob_const(nt_hash, 16);
+               out = data_blob_talloc_zero(tctx, 16);
+               sess_crypt_blob(&out, &in, &session_key, true);
+               memcpy(u.info18.nt_pwd.hash, out.data, out.length);
+       }
+       {
+               DATA_BLOB in,out;
+               in = data_blob_const(lm_hash, 16);
+               out = data_blob_talloc_zero(tctx, 16);
+               sess_crypt_blob(&out, &in, &session_key, true);
+               memcpy(u.info18.lm_pwd.hash, out.data, out.length);
+       }
+
+       torture_comment(tctx, "Testing SetUserInfo level 18 (set password hash)\n");
+
+       status = dcerpc_samr_SetUserInfo(p, tctx, &s);
+       if (!NT_STATUS_IS_OK(status)) {
+               printf("SetUserInfo level %u failed - %s\n",
+                      s.in.level, nt_errstr(status));
+               ret = false;
+       } else {
+               *password = newpass;
+       }
+
+       return ret;
+}
+
+static bool test_SetUserPass_21(struct dcerpc_pipe *p, struct torture_context *tctx,
+                               struct policy_handle *handle, uint32_t fields_present,
+                               char **password)
+{
+       NTSTATUS status;
+       struct samr_SetUserInfo s;
+       union samr_UserInfo u;
+       bool ret = true;
+       DATA_BLOB session_key;
+       char *newpass;
+       struct samr_GetUserPwInfo pwp;
+       struct samr_PwInfo info;
+       int policy_min_pw_len = 0;
+       uint8_t lm_hash[16], nt_hash[16];
+
+       pwp.in.user_handle = handle;
+       pwp.out.info = &info;
+
+       status = dcerpc_samr_GetUserPwInfo(p, tctx, &pwp);
+       if (NT_STATUS_IS_OK(status)) {
+               policy_min_pw_len = pwp.out.info->min_password_length;
+       }
+       newpass = samr_rand_pass(tctx, policy_min_pw_len);
+
+       s.in.user_handle = handle;
+       s.in.info = &u;
+       s.in.level = 21;
+
+       E_md4hash(newpass, nt_hash);
+       E_deshash(newpass, lm_hash);
+
+       ZERO_STRUCT(u);
+
+       u.info21.fields_present = fields_present;
+
+       if (fields_present & SAMR_FIELD_LM_PASSWORD_PRESENT) {
+               u.info21.lm_owf_password.length = 16;
+               u.info21.lm_owf_password.size = 16;
+               u.info21.lm_owf_password.array = (uint16_t *)lm_hash;
+               u.info21.lm_password_set = true;
+       }
+
+       if (fields_present & SAMR_FIELD_NT_PASSWORD_PRESENT) {
+               u.info21.nt_owf_password.length = 16;
+               u.info21.nt_owf_password.size = 16;
+               u.info21.nt_owf_password.array = (uint16_t *)nt_hash;
+               u.info21.nt_password_set = true;
+       }
+
+       status = dcerpc_fetch_session_key(p, &session_key);
+       if (!NT_STATUS_IS_OK(status)) {
+               printf("SetUserInfo level %u - no session key - %s\n",
+                      s.in.level, nt_errstr(status));
+               return false;
+       }
+
+       if (fields_present & SAMR_FIELD_LM_PASSWORD_PRESENT) {
+               DATA_BLOB in,out;
+               in = data_blob_const(u.info21.lm_owf_password.array,
+                                    u.info21.lm_owf_password.length);
+               out = data_blob_talloc_zero(tctx, 16);
+               sess_crypt_blob(&out, &in, &session_key, true);
+               u.info21.lm_owf_password.array = (uint16_t *)out.data;
+       }
+
+       if (fields_present & SAMR_FIELD_NT_PASSWORD_PRESENT) {
+               DATA_BLOB in,out;
+               in = data_blob_const(u.info21.nt_owf_password.array,
+                                    u.info21.nt_owf_password.length);
+               out = data_blob_talloc_zero(tctx, 16);
+               sess_crypt_blob(&out, &in, &session_key, true);
+               u.info21.nt_owf_password.array = (uint16_t *)out.data;
+       }
+
+       torture_comment(tctx, "Testing SetUserInfo level 21 (set password hash)\n");
+
+       status = dcerpc_samr_SetUserInfo(p, tctx, &s);
+       if (!NT_STATUS_IS_OK(status)) {
+               printf("SetUserInfo level %u failed - %s\n",
+                      s.in.level, nt_errstr(status));
+               ret = false;
+       } else {
+               *password = newpass;
+       }
+
+       /* try invalid length */
+       if (fields_present & SAMR_FIELD_NT_PASSWORD_PRESENT) {
+
+               u.info21.nt_owf_password.length++;
+
+               status = dcerpc_samr_SetUserInfo(p, tctx, &s);
+
+               if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+                       printf("SetUserInfo level %u should have failed with NT_STATUS_INVALID_PARAMETER - %s\n",
+                              s.in.level, nt_errstr(status));
+                       ret = false;
+               }
+       }
+
+       if (fields_present & SAMR_FIELD_LM_PASSWORD_PRESENT) {
+
+               u.info21.lm_owf_password.length++;
+
+               status = dcerpc_samr_SetUserInfo(p, tctx, &s);
+
+               if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+                       printf("SetUserInfo level %u should have failed with NT_STATUS_INVALID_PARAMETER - %s\n",
+                              s.in.level, nt_errstr(status));
+                       ret = false;
+               }
+       }
+
+       return ret;
+}
+
+static bool test_SetUserPass_level_ex(struct dcerpc_pipe *p,
+                                     struct torture_context *tctx,
+                                     struct policy_handle *handle,
+                                     uint16_t level,
+                                     uint32_t fields_present,
+                                     char **password, uint8_t password_expired,
+                                     bool use_setinfo2,
+                                     bool *matched_expected_error)
+{
+       NTSTATUS status;
+       NTSTATUS expected_error = NT_STATUS_OK;
+       struct samr_SetUserInfo s;
+       struct samr_SetUserInfo2 s2;
+       union samr_UserInfo u;
+       bool ret = true;
+       DATA_BLOB session_key;
+       DATA_BLOB confounded_session_key = data_blob_talloc(tctx, NULL, 16);
+       struct MD5Context ctx;
+       uint8_t confounder[16];
+       char *newpass;
+       struct samr_GetUserPwInfo pwp;
+       struct samr_PwInfo info;
+       int policy_min_pw_len = 0;
+       const char *comment = NULL;
+       uint8_t lm_hash[16], nt_hash[16];
+
+       pwp.in.user_handle = handle;
+       pwp.out.info = &info;
+
+       status = dcerpc_samr_GetUserPwInfo(p, tctx, &pwp);
+       if (NT_STATUS_IS_OK(status)) {
+               policy_min_pw_len = pwp.out.info->min_password_length;
+       }
+       newpass = samr_rand_pass_silent(tctx, policy_min_pw_len);
+
+       if (use_setinfo2) {
+               s2.in.user_handle = handle;
+               s2.in.info = &u;
+               s2.in.level = level;
+       } else {
+               s.in.user_handle = handle;
+               s.in.info = &u;
+               s.in.level = level;
+       }
+
+       if (fields_present & SAMR_FIELD_COMMENT) {
+               comment = talloc_asprintf(tctx, "comment: %ld\n", time(NULL));
+       }
+
+       ZERO_STRUCT(u);
+
+       switch (level) {
+       case 18:
+               E_md4hash(newpass, nt_hash);
+               E_deshash(newpass, lm_hash);
+
+               u.info18.nt_pwd_active = true;
+               u.info18.lm_pwd_active = true;
+               u.info18.password_expired = password_expired;
+
+               memcpy(u.info18.lm_pwd.hash, lm_hash, 16);
+               memcpy(u.info18.nt_pwd.hash, nt_hash, 16);
+
+               break;
+       case 21:
+               E_md4hash(newpass, nt_hash);
+               E_deshash(newpass, lm_hash);
+
+               u.info21.fields_present = fields_present;
+               u.info21.password_expired = password_expired;
+               u.info21.comment.string = comment;
+
+               if (fields_present & SAMR_FIELD_LM_PASSWORD_PRESENT) {
+                       u.info21.lm_owf_password.length = 16;
+                       u.info21.lm_owf_password.size = 16;
+                       u.info21.lm_owf_password.array = (uint16_t *)lm_hash;
+                       u.info21.lm_password_set = true;
+               }
+
+               if (fields_present & SAMR_FIELD_NT_PASSWORD_PRESENT) {
+                       u.info21.nt_owf_password.length = 16;
+                       u.info21.nt_owf_password.size = 16;
+                       u.info21.nt_owf_password.array = (uint16_t *)nt_hash;
+                       u.info21.nt_password_set = true;
+               }
+
+               break;
+       case 23:
+               u.info23.info.fields_present = fields_present;
+               u.info23.info.password_expired = password_expired;
+               u.info23.info.comment.string = comment;
+
+               encode_pw_buffer(u.info23.password.data, newpass, STR_UNICODE);
+
+               break;
+       case 24:
+               u.info24.password_expired = password_expired;
+
+               encode_pw_buffer(u.info24.password.data, newpass, STR_UNICODE);
+
+               break;
+       case 25:
+               u.info25.info.fields_present = fields_present;
+               u.info25.info.password_expired = password_expired;
+               u.info25.info.comment.string = comment;
+
+               encode_pw_buffer(u.info25.password.data, newpass, STR_UNICODE);
+
+               break;
+       case 26:
+               u.info26.password_expired = password_expired;
+
+               encode_pw_buffer(u.info26.password.data, newpass, STR_UNICODE);
+
+               break;
+       }
+
+       status = dcerpc_fetch_session_key(p, &session_key);
+       if (!NT_STATUS_IS_OK(status)) {
+               printf("SetUserInfo level %u - no session key - %s\n",
+                      s.in.level, nt_errstr(status));
+               return false;
+       }
+
+       generate_random_buffer((uint8_t *)confounder, 16);
+
+       MD5Init(&ctx);
+       MD5Update(&ctx, confounder, 16);
+       MD5Update(&ctx, session_key.data, session_key.length);
+       MD5Final(confounded_session_key.data, &ctx);
+
+       switch (level) {
+       case 18:
+               {
+                       DATA_BLOB in,out;
+                       in = data_blob_const(u.info18.nt_pwd.hash, 16);
+                       out = data_blob_talloc_zero(tctx, 16);
+                       sess_crypt_blob(&out, &in, &session_key, true);
+                       memcpy(u.info18.nt_pwd.hash, out.data, out.length);
+               }
+               {
+                       DATA_BLOB in,out;
+                       in = data_blob_const(u.info18.lm_pwd.hash, 16);
+                       out = data_blob_talloc_zero(tctx, 16);
+                       sess_crypt_blob(&out, &in, &session_key, true);
+                       memcpy(u.info18.lm_pwd.hash, out.data, out.length);
+               }
+
+               break;
+       case 21:
+               if (fields_present & SAMR_FIELD_LM_PASSWORD_PRESENT) {
+                       DATA_BLOB in,out;
+                       in = data_blob_const(u.info21.lm_owf_password.array,
+                                            u.info21.lm_owf_password.length);
+                       out = data_blob_talloc_zero(tctx, 16);
+                       sess_crypt_blob(&out, &in, &session_key, true);
+                       u.info21.lm_owf_password.array = (uint16_t *)out.data;
+               }
+               if (fields_present & SAMR_FIELD_NT_PASSWORD_PRESENT) {
+                       DATA_BLOB in,out;
+                       in = data_blob_const(u.info21.nt_owf_password.array,
+                                            u.info21.nt_owf_password.length);
+                       out = data_blob_talloc_zero(tctx, 16);
+                       sess_crypt_blob(&out, &in, &session_key, true);
+                       u.info21.nt_owf_password.array = (uint16_t *)out.data;
+               }
+               break;
+       case 23:
+               arcfour_crypt_blob(u.info23.password.data, 516, &session_key);
+               break;
+       case 24:
+               arcfour_crypt_blob(u.info24.password.data, 516, &session_key);
+               break;
+       case 25:
+               arcfour_crypt_blob(u.info25.password.data, 516, &confounded_session_key);
+               memcpy(&u.info25.password.data[516], confounder, 16);
+               break;
+       case 26:
+               arcfour_crypt_blob(u.info26.password.data, 516, &confounded_session_key);
+               memcpy(&u.info26.password.data[516], confounder, 16);
+               break;
+       }
+
+       if (use_setinfo2) {
+               status = dcerpc_samr_SetUserInfo2(p, tctx, &s2);
+       } else {
+               status = dcerpc_samr_SetUserInfo(p, tctx, &s);
+       }
+
+       if (!NT_STATUS_IS_OK(status)) {
+               if (fields_present == 0) {
+                       expected_error = NT_STATUS_INVALID_PARAMETER;
+               }
+               if (fields_present & SAMR_FIELD_LAST_PWD_CHANGE) {
+                       expected_error = NT_STATUS_ACCESS_DENIED;
+               }
+       }
+
+       if (!NT_STATUS_IS_OK(expected_error)) {
+               if (use_setinfo2) {
+                       torture_assert_ntstatus_equal(tctx,
+                               s2.out.result,
+                               expected_error, "SetUserInfo2 failed");
+               } else {
+                       torture_assert_ntstatus_equal(tctx,
+                               s.out.result,
+                               expected_error, "SetUserInfo failed");
+               }
+               *matched_expected_error = true;
+               return true;
+       }
+
+       if (!NT_STATUS_IS_OK(status)) {
+               printf("SetUserInfo%s level %u failed - %s\n",
+                      use_setinfo2 ? "2":"", level, nt_errstr(status));
+               ret = false;
+       } else {
+               *password = newpass;
+       }
+
+       return ret;
+}
+
 static bool test_SetAliasInfo(struct dcerpc_pipe *p, struct torture_context *tctx,
                               struct policy_handle *handle)
 {
@@ -1917,7 +2341,7 @@ bool test_ChangePasswordRandomBytes(struct dcerpc_pipe *p, struct torture_contex
 
        ZERO_STRUCT(u);
 
-       u.info25.info.fields_present = SAMR_FIELD_PASSWORD;
+       u.info25.info.fields_present = SAMR_FIELD_NT_PASSWORD_PRESENT;
 
        set_pw_in_buffer(u.info25.password.data, &new_random_pass);
 
@@ -2133,6 +2557,489 @@ static bool test_TestPrivateFunctionsUser(struct dcerpc_pipe *p, struct torture_
        return true;
 }
 
+static bool test_QueryUserInfo_pwdlastset(struct dcerpc_pipe *p,
+                                         struct torture_context *tctx,
+                                         struct policy_handle *handle,
+                                         bool use_info2,
+                                         NTTIME *pwdlastset)
+{
+       NTSTATUS status;
+       uint16_t levels[] = { /* 3, */ 5, 21 };
+       int i;
+       NTTIME pwdlastset3 = 0;
+       NTTIME pwdlastset5 = 0;
+       NTTIME pwdlastset21 = 0;
+
+       torture_comment(tctx, "Testing QueryUserInfo%s level 5 and 21 call ",
+                       use_info2 ? "2":"");
+
+       for (i=0; i<ARRAY_SIZE(levels); i++) {
+
+               struct samr_QueryUserInfo r;
+               struct samr_QueryUserInfo2 r2;
+               union samr_UserInfo *info;
+
+               if (use_info2) {
+                       r2.in.user_handle = handle;
+                       r2.in.level = levels[i];
+                       r2.out.info = &info;
+                       status = dcerpc_samr_QueryUserInfo2(p, tctx, &r2);
+
+               } else {
+                       r.in.user_handle = handle;
+                       r.in.level = levels[i];
+                       r.out.info = &info;
+                       status = dcerpc_samr_QueryUserInfo(p, tctx, &r);
+               }
+
+               if (!NT_STATUS_IS_OK(status) &&
+                   !NT_STATUS_EQUAL(status, NT_STATUS_INVALID_INFO_CLASS)) {
+                       printf("QueryUserInfo%s level %u failed - %s\n",
+                              use_info2 ? "2":"", levels[i], nt_errstr(status));
+                       return false;
+               }
+
+               switch (levels[i]) {
+               case 3:
+                       pwdlastset3 = info->info3.last_password_change;
+                       break;
+               case 5:
+                       pwdlastset5 = info->info5.last_password_change;
+                       break;
+               case 21:
+                       pwdlastset21 = info->info21.last_password_change;
+                       break;
+               default:
+                       return false;
+               }
+       }
+       /* torture_assert_int_equal(tctx, pwdlastset3, pwdlastset5,
+                                   "pwdlastset mixup"); */
+       torture_assert_int_equal(tctx, pwdlastset5, pwdlastset21,
+                                "pwdlastset mixup");
+
+       *pwdlastset = pwdlastset21;
+
+       torture_comment(tctx, "(pwdlastset: %lld)\n", *pwdlastset);
+
+       return true;
+}
+
+static bool test_SetPassword_level(struct dcerpc_pipe *p,
+                                  struct torture_context *tctx,
+                                  struct policy_handle *handle,
+                                  uint16_t level,
+                                  uint32_t fields_present,
+                                  uint8_t password_expired,
+                                  bool *matched_expected_error,
+                                  bool use_setinfo2,
+                                  char **password,
+                                  bool use_queryinfo2,
+                                  NTTIME *pwdlastset)
+{
+       const char *fields = NULL;
+       bool ret = true;
+
+       switch (level) {
+       case 21:
+       case 23:
+       case 25:
+               fields = talloc_asprintf(tctx, "(fields_present: 0x%08x)",
+                                        fields_present);
+               break;
+       default:
+               break;
+       }
+
+       torture_comment(tctx, "Testing SetUserInfo%s level %d call "
+               "(password_expired: %d) %s\n",
+               use_setinfo2 ? "2":"", level, password_expired,
+               fields ? fields : "");
+
+       if (!test_SetUserPass_level_ex(p, tctx, handle, level,
+                                      fields_present,
+                                      password,
+                                      password_expired,
+                                      use_setinfo2,
+                                      matched_expected_error)) {
+               ret = false;
+       }
+
+       if (!test_QueryUserInfo_pwdlastset(p, tctx, handle,
+                                          use_queryinfo2,
+                                          pwdlastset)) {
+               ret = false;
+       }
+
+       return ret;
+}
+
+static bool test_SetPassword_pwdlastset(struct dcerpc_pipe *p,
+                                       struct torture_context *tctx,
+                                       uint32_t acct_flags,
+                                       struct policy_handle *handle,
+                                       char **password)
+{
+       int s = 0, q = 0, f = 0, l = 0, z = 0;
+       bool ret = true;
+       int delay = 500000;
+       bool set_levels[] = { false, true };
+       bool query_levels[] = { false, true };
+       uint32_t levels[] = { 18, 21, 23, 24, 25, 26 };
+       uint32_t nonzeros[] = { 1, 24 };
+       uint32_t fields_present[] = {
+               0,
+               SAMR_FIELD_EXPIRED_FLAG,
+               SAMR_FIELD_LAST_PWD_CHANGE,
+               SAMR_FIELD_EXPIRED_FLAG | SAMR_FIELD_LAST_PWD_CHANGE,
+               SAMR_FIELD_COMMENT,
+               SAMR_FIELD_NT_PASSWORD_PRESENT,
+               SAMR_FIELD_NT_PASSWORD_PRESENT | SAMR_FIELD_LAST_PWD_CHANGE,
+               SAMR_FIELD_NT_PASSWORD_PRESENT | SAMR_FIELD_LM_PASSWORD_PRESENT,
+               SAMR_FIELD_NT_PASSWORD_PRESENT | SAMR_FIELD_LM_PASSWORD_PRESENT | SAMR_FIELD_LAST_PWD_CHANGE,
+               SAMR_FIELD_NT_PASSWORD_PRESENT | SAMR_FIELD_EXPIRED_FLAG,
+               SAMR_FIELD_NT_PASSWORD_PRESENT | SAMR_FIELD_LM_PASSWORD_PRESENT | SAMR_FIELD_EXPIRED_FLAG,
+               SAMR_FIELD_NT_PASSWORD_PRESENT | SAMR_FIELD_LM_PASSWORD_PRESENT | SAMR_FIELD_LAST_PWD_CHANGE | SAMR_FIELD_EXPIRED_FLAG
+       };
+
+       if (torture_setting_bool(tctx, "samba3", false)) {
+               delay = 1000000;
+               printf("Samba3 has second granularity, setting delay to: %d\n",
+                       delay);
+       }
+
+       /* set to 1 to enable testing for all possible opcode
+          (SetUserInfo, SetUserInfo2, QueryUserInfo, QueryUserInfo2)
+          combinations */
+#if 0
+#define TEST_SET_LEVELS 1
+#define TEST_QUERY_LEVELS 1
+#endif
+       for (l=0; l<ARRAY_SIZE(levels); l++) {
+       for (z=0; z<ARRAY_SIZE(nonzeros); z++) {
+       for (f=0; f<ARRAY_SIZE(fields_present); f++) {
+#ifdef TEST_SET_LEVELS
+       for (s=0; s<ARRAY_SIZE(set_levels); s++) {
+#endif
+#ifdef TEST_QUERY_LEVELS
+       for (q=0; q<ARRAY_SIZE(query_levels); q++) {
+#endif
+               NTTIME pwdlastset_old = 0;
+               NTTIME pwdlastset_new = 0;
+               bool matched_expected_error = false;
+
+               torture_comment(tctx, "------------------------------\n"
+                               "Testing pwdLastSet attribute for flags: 0x%08x "
+                               "(s: %d (l: %d), q: %d)\n",
+                               acct_flags, s, levels[l], q);
+
+               /* set #1 */
+
+               /* set a password and force password change (pwdlastset 0) by
+                * setting the password expired flag to a non-0 value */
+
+               if (!test_SetPassword_level(p, tctx, handle,
+                                           levels[l],
+                                           fields_present[f],
+                                           nonzeros[z],
+                                           &matched_expected_error,
+                                           set_levels[s],
+                                           password,
+                                           query_levels[q],
+                                           &pwdlastset_old)) {
+                       ret = false;
+               }
+
+               if (matched_expected_error == true) {
+                       /* skipping on expected failure */
+                       continue;
+               }
+
+               /* pwdlastset must be 0 afterwards, except for a level 21, 23 and 25
+                * set without the SAMR_FIELD_EXPIRED_FLAG */
+
+               switch (levels[l]) {
+               case 21:
+               case 23:
+               case 25:
+                       if ((pwdlastset_new != 0) &&
+                           !(fields_present[f] & SAMR_FIELD_EXPIRED_FLAG)) {
+                               torture_comment(tctx, "not considering a non-0 "
+                                       "pwdLastSet as a an error as the "
+                                       "SAMR_FIELD_EXPIRED_FLAG has not "
+                                       "been set\n");
+                               break;
+                       }
+               default:
+                       if (pwdlastset_new != 0) {
+                               torture_warning(tctx, "pwdLastSet test failed: "
+                                       "expected pwdLastSet 0 but got %lld\n",
+                                       pwdlastset_old);
+                               ret = false;
+                       }
+                       break;
+               }
+
+               switch (levels[l]) {
+               case 21:
+               case 23:
+               case 25:
+                       if (((fields_present[f] & SAMR_FIELD_NT_PASSWORD_PRESENT) ||
+                            (fields_present[f] & SAMR_FIELD_LM_PASSWORD_PRESENT)) &&
+                            (pwdlastset_old > 0) && (pwdlastset_new > 0) &&
+                            (pwdlastset_old >= pwdlastset_new)) {
+                               torture_warning(tctx, "pwdlastset not increasing\n");
+                               ret = false;
+                       }
+                       break;
+               default:
+                       if ((pwdlastset_old > 0) && (pwdlastset_new > 0) &&
+                           (pwdlastset_old >= pwdlastset_new)) {
+                               torture_warning(tctx, "pwdlastset not increasing\n");
+                               ret = false;
+                       }
+                       break;
+               }
+
+               usleep(delay);
+
+               /* set #2 */
+
+               /* set a password, pwdlastset needs to get updated (increased
+                * value), password_expired value used here is 0 */
+
+               if (!test_SetPassword_level(p, tctx, handle,
+                                           levels[l],
+                                           fields_present[f],
+                                           0,
+                                           &matched_expected_error,
+                                           set_levels[s],
+                                           password,
+                                           query_levels[q],
+                                           &pwdlastset_new)) {
+                       ret = false;
+               }
+
+               /* when a password has been changed, pwdlastset must not be 0 afterwards
+                * and must be larger then the old value */
+
+               switch (levels[l]) {
+               case 21:
+               case 23:
+               case 25:
+
+                       /* SAMR_FIELD_EXPIRED_FLAG has not been set and no
+                        * password has been changed, old and new pwdlastset
+                        * need to be the same value */
+
+                       if (!(fields_present[f] & SAMR_FIELD_EXPIRED_FLAG) &&
+                           !((fields_present[f] & SAMR_FIELD_NT_PASSWORD_PRESENT) ||
+                             (fields_present[f] & SAMR_FIELD_LM_PASSWORD_PRESENT)))
+                       {
+                               torture_assert_int_equal(tctx, pwdlastset_old,
+                                       pwdlastset_new, "pwdlastset must be equal");
+                               break;
+                       }
+               default:
+                       if (pwdlastset_old >= pwdlastset_new) {
+                               torture_warning(tctx, "pwdLastSet test failed: "
+                                       "expected last pwdlastset (%lld) < new pwdlastset (%lld)\n",
+                                       pwdlastset_old, pwdlastset_new);
+                               ret = false;
+                       }
+                       if (pwdlastset_new == 0) {
+                               torture_warning(tctx, "pwdLastSet test failed: "
+                                       "expected non-0 pwdlastset, got: %lld\n",
+                                       pwdlastset_new);
+                               ret = false;
+                       }
+               }
+
+               switch (levels[l]) {
+               case 21:
+               case 23:
+               case 25:
+                       if (((fields_present[f] & SAMR_FIELD_NT_PASSWORD_PRESENT) ||
+                            (fields_present[f] & SAMR_FIELD_LM_PASSWORD_PRESENT)) &&
+                            (pwdlastset_old > 0) && (pwdlastset_new > 0) &&
+                            (pwdlastset_old >= pwdlastset_new)) {
+                               torture_warning(tctx, "pwdlastset not increasing\n");
+                               ret = false;
+                       }
+                       break;
+               default:
+                       if ((pwdlastset_old > 0) && (pwdlastset_new > 0) &&
+                           (pwdlastset_old >= pwdlastset_new)) {
+                               torture_warning(tctx, "pwdlastset not increasing\n");
+                               ret = false;
+                       }
+                       break;
+               }
+
+               pwdlastset_old = pwdlastset_new;
+
+               usleep(delay);
+
+               /* set #2b */
+
+               /* set a password, pwdlastset needs to get updated (increased
+                * value), password_expired value used here is 0 */
+
+               if (!test_SetPassword_level(p, tctx, handle,
+                                           levels[l],
+                                           fields_present[f],
+                                           0,
+                                           &matched_expected_error,
+                                           set_levels[s],
+                                           password,
+                                           query_levels[q],
+                                           &pwdlastset_new)) {
+                       ret = false;
+               }
+
+               /* when a password has been changed, pwdlastset must not be 0 afterwards
+                * and must be larger then the old value */
+
+               switch (levels[l]) {
+               case 21:
+               case 23:
+               case 25:
+
+                       /* if no password has been changed, old and new pwdlastset
+                        * need to be the same value */
+
+                       if (!((fields_present[f] & SAMR_FIELD_NT_PASSWORD_PRESENT) ||
+                             (fields_present[f] & SAMR_FIELD_LM_PASSWORD_PRESENT)))
+                       {
+                               torture_assert_int_equal(tctx, pwdlastset_old,
+                                       pwdlastset_new, "pwdlastset must be equal");
+                               break;
+                       }
+               default:
+                       if (pwdlastset_old >= pwdlastset_new) {
+                               torture_warning(tctx, "pwdLastSet test failed: "
+                                       "expected last pwdlastset (%lld) < new pwdlastset (%lld)\n",
+                                       pwdlastset_old, pwdlastset_new);
+                               ret = false;
+                       }
+                       if (pwdlastset_new == 0) {
+                               torture_warning(tctx, "pwdLastSet test failed: "
+                                       "expected non-0 pwdlastset, got: %lld\n",
+                                       pwdlastset_new);
+                               ret = false;
+                       }
+               }
+
+               /* set #3 */
+
+               /* set a password and force password change (pwdlastset 0) by
+                * setting the password expired flag to a non-0 value */
+
+               if (!test_SetPassword_level(p, tctx, handle,
+                                           levels[l],
+                                           fields_present[f],
+                                           nonzeros[z],
+                                           &matched_expected_error,
+                                           set_levels[s],
+                                           password,
+                                           query_levels[q],
+                                           &pwdlastset_new)) {
+                       ret = false;
+               }
+
+               /* pwdlastset must be 0 afterwards, except for a level 21, 23 and 25
+                * set without the SAMR_FIELD_EXPIRED_FLAG */
+
+               switch (levels[l]) {
+               case 21:
+               case 23:
+               case 25:
+                       if ((pwdlastset_new != 0) &&
+                           !(fields_present[f] & SAMR_FIELD_EXPIRED_FLAG)) {
+                               torture_comment(tctx, "not considering a non-0 "
+                                       "pwdLastSet as a an error as the "
+                                       "SAMR_FIELD_EXPIRED_FLAG has not "
+                                       "been set\n");
+                               break;
+                       }
+
+                       /* SAMR_FIELD_EXPIRED_FLAG has not been set and no
+                        * password has been changed, old and new pwdlastset
+                        * need to be the same value */
+
+                       if (!(fields_present[f] & SAMR_FIELD_EXPIRED_FLAG) &&
+                           !((fields_present[f] & SAMR_FIELD_NT_PASSWORD_PRESENT) ||
+                             (fields_present[f] & SAMR_FIELD_LM_PASSWORD_PRESENT)))
+                       {
+                               torture_assert_int_equal(tctx, pwdlastset_old,
+                                       pwdlastset_new, "pwdlastset must be equal");
+                               break;
+                       }
+               default:
+
+                       if (pwdlastset_old == pwdlastset_new) {
+                               torture_warning(tctx, "pwdLastSet test failed: "
+                                       "expected last pwdlastset (%lld) != new pwdlastset (%lld)\n",
+                                       pwdlastset_old, pwdlastset_new);
+                               ret = false;
+                       }
+
+                       if (pwdlastset_new != 0) {
+                               torture_warning(tctx, "pwdLastSet test failed: "
+                                       "expected pwdLastSet 0, got %lld\n",
+                                       pwdlastset_old);
+                               ret = false;
+                       }
+                       break;
+               }
+
+               switch (levels[l]) {
+               case 21:
+               case 23:
+               case 25:
+                       if (((fields_present[f] & SAMR_FIELD_NT_PASSWORD_PRESENT) ||
+                            (fields_present[f] & SAMR_FIELD_LM_PASSWORD_PRESENT)) &&
+                            (pwdlastset_old > 0) && (pwdlastset_new > 0) &&
+                            (pwdlastset_old >= pwdlastset_new)) {
+                               torture_warning(tctx, "pwdlastset not increasing\n");
+                               ret = false;
+                       }
+                       break;
+               default:
+                       if ((pwdlastset_old > 0) && (pwdlastset_new > 0) &&
+                           (pwdlastset_old >= pwdlastset_new)) {
+                               torture_warning(tctx, "pwdlastset not increasing\n");
+                               ret = false;
+                       }
+                       break;
+               }
+
+               /* if the level we are testing does not have a fields_present
+                * field, skip all fields present tests by setting f to to
+                * arraysize */
+               switch (levels[l]) {
+               case 18:
+               case 24:
+               case 26:
+                       f = ARRAY_SIZE(fields_present);
+                       break;
+               }
+
+#ifdef TEST_QUERY_LEVELS
+       }
+#endif
+#ifdef TEST_SET_LEVELS
+       }
+#endif
+       } /* fields present */
+       } /* nonzeros */
+       } /* levels */
+
+#undef TEST_SET_LEVELS
+#undef TEST_QUERY_LEVELS
+
+       return ret;
+}
 
 static bool test_user_ops(struct dcerpc_pipe *p, 
                          struct torture_context *tctx,
@@ -2150,9 +3057,9 @@ static bool test_user_ops(struct dcerpc_pipe *p,
        int i;
        uint32_t rid;
        const uint32_t password_fields[] = {
-               SAMR_FIELD_PASSWORD,
-               SAMR_FIELD_PASSWORD2,
-               SAMR_FIELD_PASSWORD | SAMR_FIELD_PASSWORD2,
+               SAMR_FIELD_NT_PASSWORD_PRESENT,
+               SAMR_FIELD_LM_PASSWORD_PRESENT,
+               SAMR_FIELD_NT_PASSWORD_PRESENT | SAMR_FIELD_LM_PASSWORD_PRESENT,
                0
        };
        
@@ -2226,7 +3133,7 @@ static bool test_user_ops(struct dcerpc_pipe *p,
                                ret = false;
                        }
                }
-               
+
                for (i = 0; password_fields[i]; i++) {
                        if (!test_SetUserPass_23(p, tctx, user_handle, password_fields[i], &password)) {
                                ret = false;
@@ -2257,6 +3164,37 @@ static bool test_user_ops(struct dcerpc_pipe *p,
                        ret = false;
                }       
 
+               if (torture_setting_bool(tctx, "samba4", false)) {
+                       printf("skipping Set Password level 18 and 21 against Samba4\n");
+               } else {
+
+                       if (!test_SetUserPass_18(p, tctx, user_handle, &password)) {
+                               ret = false;
+                       }
+
+                       if (!test_ChangePasswordUser3(p, tctx, base_acct_name, 0, &password, NULL, 0, false)) {
+                               ret = false;
+                       }
+
+                       for (i = 0; password_fields[i]; i++) {
+
+                               if (password_fields[i] == SAMR_FIELD_LM_PASSWORD_PRESENT) {
+                                       /* we need to skip as that would break
+                                        * the ChangePasswordUser3 verify */
+                                       continue;
+                               }
+
+                               if (!test_SetUserPass_21(p, tctx, user_handle, password_fields[i], &password)) {
+                                       ret = false;
+                               }
+
+                               /* check it was set right */
+                               if (!test_ChangePasswordUser3(p, tctx, base_acct_name, 0, &password, NULL, 0, false)) {
+                                       ret = false;
+                               }
+                       }
+               }
+
                q.in.user_handle = user_handle;
                q.in.level = 5;
                q.out.info = &info;
@@ -2282,6 +3220,23 @@ static bool test_user_ops(struct dcerpc_pipe *p,
                }
 
                break;
+
+       case TORTURE_SAMR_PASSWORDS_PWDLASTSET:
+
+               /* test last password change timestamp behaviour */
+               if (!test_SetPassword_pwdlastset(p, tctx, base_acct_flags,
+                                                user_handle, &password)) {
+                       ret = false;
+               }
+
+               if (ret == true) {
+                       torture_comment(tctx, "pwdLastSet test succeeded\n");
+               } else {
+                       torture_warning(tctx, "pwdLastSet test failed\n");
+               }
+
+               break;
+
        case TORTURE_SAMR_OTHER:
                /* We just need the account to exist */
                break;
@@ -4507,6 +5462,13 @@ static bool test_OpenDomain(struct dcerpc_pipe *p, struct torture_context *tctx,
                        printf("Testing PASSWORDS or ATTRIBUTES on domain %s failed!\n", dom_sid_string(tctx, sid));
                }
                break;
+       case TORTURE_SAMR_PASSWORDS_PWDLASTSET:
+               ret &= test_CreateUser2(p, tctx, &domain_handle, sid, which_ops);
+               ret &= test_CreateUser(p, tctx, &domain_handle, &user_handle, sid, which_ops);
+               if (!ret) {
+                       printf("Testing PASSWORDS PWDLASTSET on domain %s failed!\n", dom_sid_string(tctx, sid));
+               }
+               break;
        case TORTURE_SAMR_OTHER:
                ret &= test_CreateUser(p, tctx, &domain_handle, &user_handle, sid, which_ops);
                if (!ret) {
@@ -4846,3 +5808,25 @@ bool torture_rpc_samr_passwords(struct torture_context *torture)
        return ret;
 }
 
+bool torture_rpc_samr_passwords_pwdlastset(struct torture_context *torture)
+{
+       NTSTATUS status;
+       struct dcerpc_pipe *p;
+       bool ret = true;
+       struct policy_handle handle;
+
+       status = torture_rpc_connection(torture, &p, &ndr_table_samr);
+       if (!NT_STATUS_IS_OK(status)) {
+               return false;
+       }
+
+       ret &= test_Connect(p, torture, &handle);
+
+       ret &= test_EnumDomains(p, torture, &handle,
+                               TORTURE_SAMR_PASSWORDS_PWDLASTSET);
+
+       ret &= test_samr_handle_Close(p, torture, &handle);
+
+       return ret;
+}
+