none
LogonUser returns ERROR_PASSWORD_MUST_CHANGE

    Question

  • Hello

    I use Logon user to get logon token and run some code with specified user rights (impersonate).

    If account password is expired, LogonUser returns flase and GetLastError returns ERROR_PASSWORD_MUST_CHANGE.
    Help me please - is it possible to change Windows user password from my code using some Windows API functions?

    Thank you.

    Tuesday, September 05, 2006 8:17 AM

Answers

All replies

  • Yes,

    You can use the API NetUserChangePassword to change a password.

    Regards
    Michael

    Tuesday, September 05, 2006 12:36 PM
  • Hello.

    I should add some more words to make my problem clear.

    My programm works in domain A. User wants make some action in domain B. So I'd wrote some code that will work impersonated as user from domain B. It looks like:

    1. run programm (with rights of user from domain A)

    2. type user name, password from domain B

    3. logon to domain B

    4. impersonate as user from domain B

    5. connect to computer in domain B and run some task

    6. Unimpersonate

    As you could see I don't have any username and password from domain B. It only is typed by user. 

    And If I run NetUserChangePassword (my programm works with user rights from domain A) it will return "Access denied" because it looks like user from domain A changes password for user from domain B.

    I would like to impersonate as user from domain B before changing password but couldn't logon since its password has expired :(

    Help me please - is there any workaround? Please note that I wil never get administrator password from domain B.

    PS If I first impersonate as user from domain B and than change password for the impersonated user with NetUserChangePassword it works fine. It works only if password hasn't been expired.

    Friday, September 15, 2006 7:39 AM
  • I don't think that there is a workaraound.
    I'd expect that changing an expired password must not work without administrative privileges. If this would work, the entire password expiry thing would be useless.

    Tuesday, September 26, 2006 3:41 PM
  • Hello, Michael

    I would agree with you if I hadn't seen how expired passwords could be changed from Windows GUI:

    When user turns computer on and log into domain, domain controller could say that password is expired. And Windows shows dialog that ask for a new password. Situation is the same - password expired and user doesnt't have administrative rights in the domain. But somehow user could change own expired password via Windows GUI. I mean that user is not actually logged in domain since password is expired but can change own password. And, one more - domain controller knows expired password so it can verify that password is correct.

    So I think it should be possible and via API. May be only gina.dll contain such functionality or only from ginal.dll it is possible change expired Windows password, I don't now.

    Wednesday, September 27, 2006 12:16 PM
  • I'm not sure I see the problem.

    Your code is calling LogonUser with a user name and password from domain B, so your code does have a user name and password from domain B.  It should use the user name in the call to NetUserChangePassword, along with the password it tried as the old password.  The new password, obviously, should come from a prompt you issue to the user to say "your password has expired".

    Wednesday, October 11, 2006 4:16 PM
  • Hello Alun.

    As I said before there is a little problem with NetUserChangePassword when password is expired. You could refer to my detailed explanation above.

    The problem is that my code is running in domain A. So when password has been expired LogonUser fails and I couldn't get token to impersonate as a user from domain B. So NetUserChangePassword will be called with login name of user from domain B old password and new one. But it will be called from code that is running from domain A :-( since impersonation has been not impossible. And for domain controller B it will be look that someone from domain A whants to change password for user from domain B. And I got security exception that has not enough rights :-( when trying to change expired password.

    But I wonder how Windows could change expired password when logging in.

    Here is simple code that shows the problem. This code should be run under user from domain A for user from domain B. User from domain A shouldn't have any rights in domain B. Unfortunately I could test it only with "User must change password at first logon" because it is too long to wait while password will expire. But I think in generally it is the same and I need to handle both errors as well.

    #include <windows.h>
    #include <lm.h>
    #include <iostream>
    using std::cout;

    LPWSTR c2w(char *source)
    {
     size_t convertedChars;
     int bufferSize = strlen(source) + 1;
     LPWSTR result = new WCHAR[bufferSize];
     mbstowcs_s(&convertedChars,  result,  bufferSize,  source,  _TRUNCATE);
     return result;
    }

    void ChangePassword(char *domain, char *login, char *oldPassword, char *newPassword)
    {
     const int bufferSize = 255;

     NET_API_STATUS status = NetUserChangePassword(c2w(domain), c2w(login), c2w(oldPassword), c2w(newPassword));
     if (status == NERR_Success)
      cout << "\r\npassword has been changed";
     else
      // 2245 The password does not meet the password policy requirements
      cout << "\r\nerror " << status << " when changing password";
    }

    int _tmain(int argc, _TCHAR* argv[])
    {
     HANDLE token;
     char *login = "dummy2";
     char *oldPassword = "zzzxxxcccvvv";
     char *newPassword = "qqqwwweeerrrtttyyy";
     char *domain = "DEVELOPMENT";
     
     bool result = LogonUser(c2w(login), c2w(domain), c2w(oldPassword), LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token);
     if (result)
     {
      cout << "\r\nlogged in";
      if (ImpersonateLoggedOnUser(token))
      {
       ChangePassword(domain, login, oldPassword, newPassword);
      }
     }
     else
     {
      int lastError = GetLastError();
      cout << "\r\nerror " << lastError;
         //1331 - account disabled
         //1907 - user should change password
         //1326 - ERROR_LOGON_FAILURE
      //1398 - ERROR_TIME_SKEW
      if (lastError == 1907)
      {
       /* since impersonation is not possible here domain controller will not allow change password for user from user from another domain */

       ChangePassword(domain, login, oldPassword, newPassword);
      }
     }
     return 0;
    }

    Best regards.

    Thursday, October 19, 2006 10:24 AM