22 Jul 2004 bolsh   » (Master)

PAM stuff

Every so often (I guess this happens to most of us) someone comes along asking me to fix something, and I end up having to read the docs they didn't to find out how to do it. Recently, someone had an interesting problem which led to me learning a little bit about PAM, which I thought I'd share.

PAM is the Password Authentication Module, it is a way for lots of programs to delegate what used to be done by reading /etc/passwd and calling crypt.

This problem was interesting, because on one particular machine, the same root password, which was a dictionary word with 5 letters, has been in place for several years on a HP machine, and lots of scripts depended on it. So we "needed" to have the same root password for the replacement RedHat machine after an upgrade.

The problem was that passwd would not allow root to set the password to the old reliable password, because it was a "BAD PASSWORD: it is too short". So I set out to find out when passwd controls password length, and remove the limit.

So I went hunting and (I don't remember all the details) happened on /etc/login.defs, which has the following field:
PASS_MIN_LEN 6

Woohoo! I thought, and went on my merry way. Change that to 5, and we're done.

No joy - passwd continued to refuse the password.

Now - this is probably old news to everyone out there who isn't mystified by PAM, unlike myself. So I dug a little deeper. And here's (more or less, from a high level) how PAM works:

  1. Program X requests that PAM authenticate a password
  2. PAM checks whether X is registered as a password service (in /etc/pam.d) and has a config file associated with it
  3. PAM loads each of the modules specified in /etc/pam.d/X from /lib/security, and pipes the password through them in turn.
Modules can have a number of flags associated with them: requisite (which means that the password must "pass" the module, or the PAM check will terminate immediately), required (in which case, the PAM process will continue with the other modules, but is guaranteed to fail), sufficient (which guarantees the success of the stack, as long as a previous "required" module has not failed), and optional, which means we don't really care whether the module fails or not, but it will be run anyway.

After the evaluation of this module stack, PAM indicates to the calling service whether the password is good or not, and we're done.

A sample stack (taken from /etc/pam.d/passwd here) looks like this:

auth required /lib/security/pam_pwdb.so shadow nullok
account required /lib/security/pam_pwdb.so
password required /lib/security/pam_cracklib.so retry=3
password required /lib/security/pam_pwdb.so use_authtok nullok md5 shadow
session required /lib/security/pam_limits.so

Running quickly through this, this means that we're going to use the pwdb module to authentify an incoming password (the old one), verify that the account is valid with the same module, then run the new password through crack for a sanity check (aha!) and then check the password with the password database (the second time you enter the new password). Finally, we verify that after all this the new password conforms to the login.defs file (the "limits" module). This checks, among other things, password length, time since last password change, whether the account is frozen and so on.

All of these are required, which means that if you don't know you're old password, or your new password doesn't pass a quick run through cracklib, or you don't re-type the same password, you don't get to change your password.

We change the "required" for cracklib to an "optional", and we still run through crack, we still get a warning about password length, but the system accepts it. Right?

Wrong. Bummer.

Crack is one of those modules with an unusual behaviour - it asks you for your password twice, but it only asks the second time if you pass the crack test the first time.

So flow control goes like this

[david@charon pam.d]$ passwd
{passwd executable} Changing password for user david.
{Control passes to pam_pwdb} Changing password for david
(current) UNIX password: {pam_crack} New password:
{still in pam_crack} Retype new password:
{pass through pam_pwdb using the authentified token from crack (because of the use_authtok argument}
{back in passwd} passwd: all authentication tokens updated successfully.

Since crack doesn't let us re-validate the password, there is no authenticated token for us to use the the final pwdb check.

Solution? Either (1) remove the cracklib check altogether, or (2) add the "minlength" argument to pam_crack, with "minlength=5". And don't forget to change login.defs too.

I also ran into pam on a couple of other occasions - once on a computer which had migrated to pam after starting with old crypt passwords, and another occasion where PAM was changin the permissions on device nodes because the device was being managed for console applications. But those stories are for another day.

Latest blog entries     Older blog entries

New Advogato Features

New HTML Parser: The long-awaited libxml2 based HTML parser code is live. It needs further work but already handles most markup better than the original parser.

Keep up with the latest Advogato features by reading the Advogato status blog.

If you're a C programmer with some spare time, take a look at the mod_virgule project page and help us with one of the tasks on the ToDo list!