Programmatically finding the password expiration date on Windows

Hi,

I have a hard time finding out how to deduce (using C++) the password expiration date on Windows. Do anyone know how to do that?

Thanks,
  jules
coldingAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

jkrCommented:
You can do that using 'NetUserGetInfo()' (http://msdn.microsoft.com/en-us/library/aa370654.aspx) at info level 3. See the attached code sample (from http://win32.mvps.org/network/nugi.cpp)
// compile with:
//		"cl -DUNICODE nugi.cpp" for a Unicode app
//		"cl nugi.cpp" for an MBCS app
 
// make sure that either both _UNICODE and UNICODE are defined, or none of them is
#if defined( _UNICODE ) && ! defined( UNICODE )
#define UNICODE 1
#endif
#if defined( UNICODE ) && ! defined( _UNICODE )
#define _UNICODE 1
#endif
 
#include <windows.h>
#include <lm.h>
#include <stdio.h>
#include <time.h>
#include <tchar.h>
#pragma hdrstop
#pragma comment( lib, "netapi32.lib" )
 
 
 
// in Unicode apps, _tprintf() maps to wprintf(), and "%s" refers to
// a Unicode string, while "%S" wants an ANSI string. In MBCS apps,
// "%s" expects an ANSI string, while "%S" wants a Unicode string. We
// could do some creative trickery with macros, but I am lazy, so I'll
// rely on "%hs" and "%ls" instead. Independent of whether printf() or
// wprintf() is ultimately invokes, "%hs" always expects an ANSI string,
// and "%ls" always expects a Unicode string.
// Since Net*() APIs on NT deal in Unicode only, the strings from the
// USER_INFO_3 struct get shown with "%ls" (but note that strings from
// elsewhere are in the ambient character size and use plain "%s").
 
 
 
#define lenof(a) (sizeof(a)/sizeof((a)[0]))
 
 
 
typedef struct {
	DWORD bits;
	const TCHAR *name;
} flag_t;
 
 
 
const TCHAR *getText( DWORD num, flag_t *defs )
{
	static TCHAR s[60];
 
	for ( ; defs->name != 0; ++ defs )
	{
		if ( defs->bits == num )
			return defs->name;
	}
 
	_stprintf( s, _T( "%lu [0x%lx] (unknown)" ), num, num );
	return &s[0];
}
 
 
 
const TCHAR *buildFlags( DWORD bits, flag_t *defs )
{
	static TCHAR s[4096];
	TCHAR *p = &s[0];
 
	p += _stprintf( p, _T( "0x%lx" ), bits );
 
	for ( ; defs->name != 0; ++ defs )
	{
		if ( ( defs->bits & bits ) == defs->bits )
		{
			if ( p != &s[0] )
			{
				_tcscpy( p, _T( ", " ) );
				p += _tcslen( p );
			}
			_tcscpy( p, defs->name );
			p += _tcslen( p );
		}
	}
 
	return &s[0];
}
 
 
 
int nugi( TCHAR *machine, TCHAR *user )
{
	DWORD rc;
	USER_INFO_3 *buf = 0;
	TCHAR temp[40];
	wchar_t unicodeMachine[UNCLEN + 4], unicodeUser[UNLEN + 2];
	static flag_t privilegeTexts[] =
	{
		{ USER_PRIV_GUEST, _T( "USER_PRIV_GUEST" ) },
		{ USER_PRIV_USER, _T( "USER_PRIV_USER" ) },
		{ USER_PRIV_ADMIN, _T( "USER_PRIV_ADMIN" ) },
		{ 0, 0 }
	};
	static flag_t authFlags[] =
	{
		{ AF_OP_PRINT, _T( "AF_OP_PRINT" ) },
		{ AF_OP_COMM, _T( "AF_OP_COMM" ) },
		{ AF_OP_SERVER, _T( "AF_OP_SERVER" ) },
		{ AF_OP_ACCOUNTS, _T( "AF_OP_ACCOUNTS" ) },
		{ 0, 0 }
	};
	static flag_t acctFlags[] =
	{
		{ UF_SCRIPT, _T( "UF_SCRIPT" ) },
		{ UF_ACCOUNTDISABLE, _T( "UF_ACCOUNTDISABLE" ) },
		{ UF_HOMEDIR_REQUIRED, _T( "UF_HOMEDIR_REQUIRED" ) },
		{ UF_PASSWD_NOTREQD, _T( "UF_PASSWD_NOTREQD" ) },
		{ UF_PASSWD_CANT_CHANGE, _T( "UF_PASSWD_CANT_CHANGE" ) },
		{ UF_LOCKOUT, _T( "UF_LOCKOUT" ) },
		{ UF_DONT_EXPIRE_PASSWD, _T( "UF_DONT_EXPIRE_PASSWD" ) },
#ifdef UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED
		{ UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED, _T( "UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED" ) },
#endif
#ifdef UF_NOT_DELEGATED
		{ UF_NOT_DELEGATED, _T( "UF_NOT_DELEGATED" ) },
#endif
#ifdef UF_SMARTCARD_REQUIRED
		{ UF_SMARTCARD_REQUIRED, _T( "UF_SMARTCARD_REQUIRED" ) },
#endif
		{ UF_NORMAL_ACCOUNT, _T( "UF_NORMAL_ACCOUNT" ) },
		{ UF_TEMP_DUPLICATE_ACCOUNT, _T( "UF_TEMP_DUPLICATE_ACCOUNT" ) },
		{ UF_WORKSTATION_TRUST_ACCOUNT, _T( "UF_WORKSTATION_TRUST_ACCOUNT" ) },
		{ UF_SERVER_TRUST_ACCOUNT, _T( "UF_SERVER_TRUST_ACCOUNT" ) },
		{ UF_INTERDOMAIN_TRUST_ACCOUNT, _T( "UF_INTERDOMAIN_TRUST_ACCOUNT" ) },
		{ 0, 0 }
	};
 
#ifdef UNICODE
	_tcsncpy( unicodeMachine, machine, lenof( unicodeMachine ) );
	_tcsncpy( unicodeUser, user, lenof( unicodeUser ) );
#else
	mbstowcs( unicodeMachine, machine, lenof( unicodeMachine ) );
	mbstowcs( unicodeUser, user, lenof( unicodeUser ) );
#endif
	unicodeMachine[lenof( unicodeMachine ) - 1] = _T( '\0' );
	unicodeUser[lenof( unicodeUser ) - 1] = _T( '\0' );
 
	_tprintf( _T( "User %s on %s: " ), user, machine );
 
	rc = NetUserGetInfo( unicodeMachine, unicodeUser, 3, (byte **) &buf );
	if ( rc != 0 )
	{
		_tprintf( _T( "error %lu.\n" ), rc );
		return rc;
	}
 
	_tprintf( _T( "found.\n" ) );
 
	_tprintf( _T( "  Name:          %ls\n" ), buf->usri3_name );
	_tprintf( _T( "  Password:      %ls\n" ), buf->usri3_password );
	_tprintf( _T( "  Password age:  %lu days, %02lu:%02lu:%02lu\n" ), buf->usri3_password_age / 86400UL,
		buf->usri3_password_age % 86400UL / 3600UL, buf->usri3_password_age % 3600UL / 60UL,
		buf->usri3_password_age % 60UL );
	_tprintf( _T( "  Privilege:     %s\n" ), getText( buf->usri3_priv, privilegeTexts ) );
	_tprintf( _T( "  Home dir:      %ls\n" ), buf->usri3_home_dir );
	_tprintf( _T( "  Comment:       %ls\n" ), buf->usri3_comment );
	_tprintf( _T( "  Flags:         %s\n" ), buildFlags( buf->usri3_flags, acctFlags ) );
	_tprintf( _T( "  Script path:   %ls\n" ), buf->usri3_script_path );
	_tprintf( _T( "  Auth flags:    %s\n" ), buildFlags( buf->usri3_auth_flags, authFlags ) );
	_tprintf( _T( "  Full name:     %ls\n" ), buf->usri3_full_name );
	_tprintf( _T( "  User comment:  %ls\n" ), buf->usri3_usr_comment );
	_tprintf( _T( "  RAS params:    (not displayed)\n" ) );
	_tprintf( _T( "  Workstations:  %ls\n" ), buf->usri3_workstations );
	_tprintf( _T( "  Last logon:    %s" ), buf->usri3_last_logon == 0? _T( "never\n" ): _tctime( (long *) &buf->usri3_last_logon ) );
	_tprintf( _T( "  Last logoff:   %s" ), buf->usri3_last_logoff == 0? _T( "unknown\n" ): _tctime( (long *) &buf->usri3_last_logoff ) );
	_tprintf( _T( "  Expires:       %s" ), buf->usri3_acct_expires == TIMEQ_FOREVER? _T( "never\n" ): _tctime( (long *) &buf->usri3_acct_expires ) );
	_tprintf( _T( "  Max storage:   %s\n" ), buf->usri3_max_storage == USER_MAXSTORAGE_UNLIMITED? _T( "unlimited" ): _ultot( buf->usri3_max_storage, temp, 10 ) );
	_tprintf( _T( "  Logon hours:   (not displayed)\n" ) );
	_tprintf( _T( "  Bad PW count:  %s\n" ), buf->usri3_bad_pw_count == (DWORD) -1L? _T( "unlimited" ): _ultot( buf->usri3_bad_pw_count, temp, 10 ) );
	_tprintf( _T( "  # of logons:   %s\n" ), buf->usri3_num_logons == (DWORD) -1L? _T( "unknown" ): _ultot( buf->usri3_num_logons, temp, 10 ) );
	_tprintf( _T( "  Logon server:  %ls\n" ), buf->usri3_logon_server );
	_tprintf( _T( "  Country code:  %lu\n" ), buf->usri3_country_code );
	_tprintf( _T( "  Code page:     %lu\n" ), buf->usri3_code_page );
	_tprintf( _T( "  User RID:      %lu\n" ), buf->usri3_user_id );
	_tprintf( _T( "  Pgroup RID:    %lu\n" ), buf->usri3_primary_group_id );
	_tprintf( _T( "  Profile path:  %ls\n" ), buf->usri3_profile );
	_tprintf( _T( "  Home drive:    %ls\n" ), buf->usri3_home_dir_drive );
	_tprintf( _T( "  PW expired:    %s\n" ), buf->usri3_password_expired? _T( "yes" ): _T( "no" ) );
	_tprintf( _T( "\n" ) );
 
	return 0;
}
 
 
 
#ifdef UNICODE
int wmain( int argc, TCHAR *argv[] )
#else
int main( int argc, TCHAR *argv[] )
#endif
{
	TCHAR *machine = 0;
	int i;
 
	if ( argc < 2 )
	{
		_tprintf( _T( "usage: nugi [\\\\machine] user [user ...] [[\\\\machine] user [user ...] ...]\n" ) );
		return 1;
	}
 
	for ( i = 1; i < argc; ++ i )
	{
		if ( argv[i][0] == _T( '\\' ) && argv[i][1] == _T( '\\' ) )
		{
			machine = argv[i];
			continue;
		}
 
		nugi( machine, argv[i] );
	}
 
	return 0;
}

Open in new window

0
coldingAuthor Commented:
Thank you very much.

One question: I can see that the sample code is using the structure member "usri3_acct_expires". Doesn't that tell when the account expires as opposed to when the password expires?

Thanks,
  jules
0
jkrCommented:
Well, yes, that's for the account. The relevant part in the above is the pasword age (usri3_password_age). There's a maximum password age set for the domain or machine - which setup are you on, domain or workgroup?
0
Cloud Class® Course: C++ 11 Fundamentals

This course will introduce you to C++ 11 and teach you about syntax fundamentals.

coldingAuthor Commented:
Domain. So if I can get the maximum password age for this specific domain user and subtract the password age then I get the time until expiration, right?
0
jkrCommented:
Yesd, that's the idea. You should be able to read the max. age from the registry, see http://support.microsoft.com/kb/555540 ("Setup strong password policy in Windows XP"). That is 'MaxPasswordAge' from 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Network'
0
coldingAuthor Commented:
Is the maximum password age identical for all domain users?
0
jkrCommented:
Usually, but not necessarily. Actually, you can safely assume it is. Unless you have an admin who wants to go on his bosses nerves and sets his pasword to expire every two days ;o)
0
coldingAuthor Commented:
So there is no absolutely fail-safe method to get the expiration date for all cases?

Hmm, what about SecPkgContext_PasswordExpiry ? It should be returned by QueryContextAttributes() but the documentation is not at all clear to me at the moment. Well, the function is, but what is the security context to be queried (the first parameter)?
0
jkrCommented:
Hm, I thought this way is. You can of course also query AD to get the limit, but I'd only do that as a last resort. The individual age limit (if that would be different) is replicated to the registry location above by GPO anyway...
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
coldingAuthor Commented:
Would you know what the security context mentioned in http://msdn.microsoft.com/en-us/library/aa379326(VS.85).aspx is and how I can get at it for a specific domain user?
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Editors IDEs

From novice to tech pro — start learning today.