The Microsoft Management Console provides plugins for managing many aspects of Active Directory, including user accounts.  The Active Directory Users and Computers MMC plugin allows you to view and manage user accounts, but there are some things you cannot discover, such as last logon time or when a user’s password will expire (if at all).  Password expiration can be particularly vexing for road warriors or those who use non-Windows platforms but still rely on ADS for authentication to numerous corporate resources.  Windows users have two possible means of being warned, and non-Windows users are just out of luck!

If you are connected to the domain with a Windows computer, you will be warned of an impending password expiration and be able to easily change your password.  If you are remote, you can probably use a VPN client to connect to the domain before you logon to Windows.

If you use Exchange, then you will probably also have the ability to change your password using Outlook Web Access (OWA).  Laptop users may already know that besides the credentials in Active Directory, there also exists a locally cached copy so that users can logon while disconnected from the domain.  Changes through OWA only reset the Active Directory credentials, not the locally-cached copy, so the two can become out of synch.  Users then have to change the password on the laptop separately.

Danger, Will Robinson!

When a remote user has no clue that his or her password is about to expire, certain productivity loss looms large on the horizon.  It may happen at a very inopportune moment when remote access to some resource is vitally important.  It may take some time before the user has an idea of what is wrong and contacts support for remote assistance.  Warnings are essential to eliminating such wasteful and unnecessary events.  Larger organizations may have identity access and management systems or even a self-service mechanism that reduce some of the cost of this kind of event, but the goal should always be prevention first, and cost reduction second. Aside from that, thinking of your next good password may take some time, and being forced to come up with one quickly may make it easier to forget, which may again require intervention from your support desk.

Active Directory stores the last password reset time for each user and also contains the domain policy for the maximum password lifetime.  Given these two pieces of information, it is possible to compute when any particular user’s password will expire.  There are commericial tools available (e.g. Password Reminder Pro), but a little perl scripting and the LDAP protocol is all you need!  We now have what we need to provide e-mail notification to every user before passwords actually expire.

Script Configuration

You find the script here, and the configuration file here.  It’s good practice to separate configuration from code, even with scripts.  It will make your scripts more portable, and therefore more easily consumed by others.  As a bonus, without the need to edit the script, it is less likely that changes you make will cause execution to fail completely.  I like to use the Config::IniFiles package.  From it’s description…

Config::IniFiles provides a way to have readable configuration files outside your Perl script. Configurations can be imported (inherited, stacked,…), sections can be grouped, and settings can be accessed from a tied hash.

The script assumes the initialization file will be found at /usr/local/etc/pwdexpire.ini — if this is not where you want to place the initialization file, you will have to modify the script.  The initialization file is divided into sections; each section name is found on a single line between square brackets.  After that, each parameter is one per line in ‘name=value’ pairs.

A few of the most important parameters to set are below:

    # One server per line; use a backslash to continue to next line
    # Use server:port to specify a non-standard or SSL port
    DCs			= dc1.example.com \
			  dc2.example.com

List each of your domain controllers here, or at least the nearby ones that can be used for LDAP queries.  These will be tried in some random order.

    # Root DN for domain
    rootDN		= dc=example,dc=com

    # Base DN for search of all users
    baseDN		= ou=employees,dc=example,dc=com

These two may or may not be different, but your ADS should be structured so that users are in a separate subtree.  The root is where the maximum password age for the domain will be found.  This will be a base tree search only.

    # Bind anonymously (0|1); if set then user/passwd are ignored
    bindAnon		= 0

    # User may be a DN or a UPN name, e.g. user\@domain.com
    user		= cn=adsquery,ou=Service,dc=cigital,dc=com
    passwd		= secret

Depending on your Active Directory security, you may not be able to bind anonymously to ADS.  If you’re not sure, try it anonymously first, and then with some set of credentials.

In the “Expire” section, check the following parameters:

    # Number of days before expiration to begin warnings
    warnDays		= 14

You will want to set this to some reasonable value.  What that means will depend on a number of factors, including how long someone might be away without e-mail access and how often you require password changes.  If you require password changes every month, warning someone two weeks in advance will start to get rather tedious; someone who goes on vacation for two weeks will expect to have to deal with an expired password, so it’s probably not a good idea to issue e-mail warnings to everyone halfway through the normal cycle.  One the otherhand, for a 90-day password reset cycle, this might be tolerable.

    # The e-mail address for from/reply-to
    fromAddr		= "HelpDesk" <helpdesk@example.com>

    # The organization signature for the e-mail warning message
    orgSig		= <<EOT
Example Information Technology
helpdesk@example.com
EOT

    # A brief description of the organizational services that
    # use ADS authentication
    orgSvcs		= Example's e-mail, VPN, and other internal

    # The URL for additional help to change a password
    helpURL		= http://intranet.example.com/pwdexpire.html

These parameters affect the content of the e-mail message.  These can customized per your requirements.  See the script for the fixed content of the e-mail message.  The idea is to keep the message short, but include a URL to a help page that can walk someone through the password change process and provide them with additional means of getting help.  The e-mail “from” and “reply-to” address might be your helpdesk, so that a reply generates a helpdesk ticket.

    # If you wish to test prior to enabling for production, set a test
    # e-mail recipient for all warnings (rather than end users).
    # Defaults to root@localhost
    testAddr		= hostmaster\@example.com

    # Enable test mode (0=production, 1=test)
    testMode		= 1

These last two parameters are for testing, and enable you to direct all warning messages to a single e-mail address.  You can verify that the script is functioning as you expect, and then set testMode to 0 (zero).

Automation: The final step

While this script should execute (with minor modifications) on a Windows host, I’m assuming this will run on some Un*x host.

Now create a crontab entry for the perl script on a host with access to your domain controllers. Add the entry for a user that has access to the both the script and the initialization file. This entry might look like:

30 0 * * * /usr/local/bin/pwdexpire.pl

This will run the script at 00:30 (or 12:30am) every day. Often you will see the output of cron jobs sent to /dev/null by adding the following after the script:

>/dev/null 2>&1

This sends standard output and standard error to the null device; the script may write an output log of its own.   In this case, we do not want this behavior. The script will not have any output if no users have passwords that will expire within the warning timeframe.  When there is output, this will normally be mailed to the owner of the crontab.

Your operating system may provide another means for jobs to execute on a regular basis, e.g. Debian Linux (and derived version like Ubuntu) provide directories in which all scripts will be executed, e.g. /etc/cron.daily.  Use whatever method you’re comfortable with — it’s only important that the script execute regularly.

That’s it — enjoy!