winapitime

Win32 API: reliably determining the last change time of a user password using USER_INFO_3::usri3_password_age?


AFAIK, the only way to get the time of the last password change on a user account is to call NetUserGetInfo() and then calculate the time based on PUSER_INFO_3::usri3_password_age. But, it seems the problem is that knowing exactly when the 0 second for usri3_password_age is unreliable.

I have an algorithm:

time_t seconds = time(NULL);
time_t time_of_change = seconds - pExtraUserInfo->usri3_password_age;

But unfortunately, time_of_change is sometimes 1 second off. That's because time(NULL) doesn't return what usri3_password_age considers to be the 0 second; it's a race condition.

Is time() just the wrong function to use for this calculation? Or is NetUserGetInfo() the wrong function?

I'm open to using different functions to get the point in time of the last password change, but I rather have it be LDAP-agnostic.


Solution

  • I've come to the conclusion that the best answer is: there is no answer. As commenters were getting at, PUSER_INFO_3::usri3_password_age is not supposed to be used to pin-point a point in time; it is just meant to give a vague idea of how old the current password is. I did manage to come up with an algorithm that has the potential to improve accuracy, but in my case, it wasn't enough. I had to apply a 10-second tolerance on drift.

        auto nowMillisBefore = getCurrentTimeInMillis();
        NetUserGetInfo(0, user_name, level, &pExtraUserInfo);
        auto nowMillisAfter = getCurrentTimeInMillis();
        double nowAverageMillis = (nowMillisAfter + nowMillisBefore)/2.; //taking average from time before and time after
        uint64_t nowSeconds = static_cast<uint64_t>(round(nowAverageMillis/1000.));
    
        //"current" in that it was calculated most recently
        uint64_t currentTimeOfChange = nowSeconds - pExtraUserInfo->usri3_password_age;
        bool bPasswordSetMoreRecently = currentTimeOfChange - olderTimeOfChange > nDriftTollerance;
    

    The first part tries to find the point in time that NetUserGetInfo() considers to be now. The second part looks to see if there's a big enough change. If currentTimeOfChange is more than nDriftTollerance seconds than olderTimeOfChange then we know the password has been changed and we have a new date for that change. But who is to say how large nDriftTollerance should be? In my program, it was never more than 1 second, during dev testing, so I set nDriftTollerance to 10. But whose to say that's the right number?

    As was also discovered, it looks like getting the password set time can't be done any better than what's afforded by NetUserGetInfo() for local users. I've heard there are other options for LDAP.