Build a Safe pam_userdb Test and Add It to PAM
You will create a Berkeley DB password store, add a pam_userdb rule to a PAM service, and understand what a successful lookup proves. The examples keep the database under /tmp and show plaintext only for a disposable test. Allow about 20 minutes, plus time to test the real application that will use the PAM stack.
The route
Jump straight to the step you need, or tick off Done means at the end.
Security boundary
Do not use a plaintext database for a real account. The module can compare either plaintext values or crypt(3) hashes, but a database containing plaintext passwords is a credential leak waiting to happen. A PAM change can also lock users out, so keep an existing login or root shell open while testing.
1. Check the installed module and tools
This host has Linux-PAM package version 1.5.3-5ubuntu5.7 and Berkeley DB tools version 5.3.28. The local pam_userdb(8) manual describes a Berkeley DB indexed by username. The database path is required and must be written without the .db suffix.
$ dpkg-query -W -f='${Package} ${Version}\n' libpam-modules:amd64
libpam-modules 1.5.3-5ubuntu5.7
$ command -v db_load
/usr/bin/db_load
$ db_load -V
Berkeley DB 5.3.28: (September 9, 2013)
$ ls -l /lib/x86_64-linux-gnu/security/pam_userdb.so
The final path is an installed module check, not a command you run directly. pam_userdb.so is loaded by PAM configuration. The module provides the auth and account types.
Checkpoint
Stop if db_load is missing or the module file cannot be found. Do not compensate by copying a module from another system; install the matching distribution package through your normal change process.
2. Create a disposable database
Make a private working directory and feed db_load alternating key and value lines. With -T -t hash, the first line is the username and the second is its corresponding password. The output name below is pam-userdb-demo.db, while PAM will be given /tmp/pam-userdb-demo/pam-userdb-demo.
$ umask 077
$ demo_dir=$(mktemp -d)
$ printf '%s\n' demo-user demo-password | db_load -T -t hash "$demo_dir/pam-userdb-demo.db"
$ db_dump -p "$demo_dir/pam-userdb-demo.db"
VERSION=3
format=print
type=hash
demo-user
demo-password
$ stat -c '%A %n' "$demo_dir/pam-userdb-demo.db"
-rw------- /tmp/tmp.abcdef/pam-userdb-demo.db
The temporary directory name in the output is host-specific. The useful checks are a successful db_load exit status, a key of demo-user, and a value of demo-password. Do not use either value outside this disposable test. Real databases should contain crypt-format values and should be readable only by the account that needs them, usually through a carefully chosen group and file permission.
Checkpoint
Pass PAM the absolute database stem, not the file with .db. Replace REPLACE_WITH_demo_dir below with the value printed by printf '%s\n' "$demo_dir": db=/tmp/REPLACE_WITH_demo_dir/pam-userdb-demo. PAM does not expand shell variables. Supplying the suffix is a common reason for a lookup failure.
3. Add the module to a PAM service
PAM rules normally live in a file under /etc/pam.d. Do not edit a production service while you are still proving the database format. First choose a dedicated service name used only by a test program, then make a backup before changing its file. This step requires root.
# sudo cp -p /etc/pam.d/TEST_SERVICE /etc/pam.d/TEST_SERVICE.bak
# sudoedit /etc/pam.d/TEST_SERVICE
Add this rule to the service's auth section, replacing DB_STEM with the absolute path to your database stem:
auth required pam_userdb.so db=DB_STEM crypt=none
crypt=none matches the disposable plaintext data from step 2. It is deliberately explicit: the module's storage mode is not something to leave to guesswork. For a real deployment, replace it with crypt=crypt and load values generated in the format accepted by crypt(3). Do not put debug or dump in a production rule. The manual warns that debug logging can expose password hashes, and dump logs every database entry.
The control flag required means this check must succeed, although PAM will process the rest of the stack before returning a failure. Do not replace an existing auth stack with this one-line example without understanding the service's current account, session and password rules.
Checkpoint
Review the edited file as root and confirm that the database path has no .db suffix:
$ sudo grep -n 'pam_userdb' /etc/pam.d/TEST_SERVICE
auth required pam_userdb.so db=/tmp/REPLACE_WITH_demo_dir/pam-userdb-demo crypt=none
4. Test the PAM conversation
Use the client application that owns the test service. The module obtains a password through PAM's conversation unless an earlier module has already supplied an authentication token. If the client cannot converse, the result can be a conversation error even when the database is correct.
$ PAM_USER=demo-user your-pam-test-client --service TEST_SERVICE
Password: demo-password
$ printf 'exit status: %s\n' "$?"
exit status: 0
The client name and its output are application-specific, so do not copy this placeholder command literally. A zero status means that the client accepted the PAM transaction. It does not prove that every service using the database has the same PAM stack.
For a deliberately wrong password, expect a non-zero result. A user absent from the database normally produces PAM_USER_UNKNOWN; a matching username with a wrong password produces PAM_AUTH_ERR. A missing or unreadable database is a service error, not a failed password. Inspect the service's journal or authentication log according to the distribution's logging setup, but never enable debug merely to collect credentials.
5. Choose token-sharing options deliberately
Without a token-sharing option, pam_userdb obtains the password itself. Use try_first_pass when an earlier module should supply the token first but a prompt is acceptable if none exists. Use use_first_pass when a missing earlier token must fail without another prompt. These options matter in a stack with several password-checking modules; adding both is confusing and should be avoided.
unknown_ok changes an absent user into a non-error result so another pam_userdb rule can try another database. Use it only when that fallback is intentional. icase makes plaintext password checks case-insensitive and has no effect on encrypted storage. That weakens the password space, so it is suited to non-password identifiers such as registration numbers, not ordinary user passwords.
6. Recover from a failed test and remove the demo
If the service stops authenticating, keep your existing root session open and restore the saved PAM file:
# sudo cp -p /etc/pam.d/TEST_SERVICE.bak /etc/pam.d/TEST_SERVICE
That restores the file changed in step 3. If the application caches PAM configuration, restart only that application after checking its documented procedure. Do not restart an unrelated production service as a diagnostic.
When the test is complete, remove the disposable directory. This is destructive and cannot recover the test data:
$ rm -rf -- "$demo_dir"
$ test ! -e "$demo_dir" && echo 'demo database removed'
demo database removed
Remove the backup only after you have confirmed that the service file is correct and your normal configuration backup policy does not need it. Never remove a real credential database as part of a blind cleanup command.
Done means
- You confirmed the installed Linux-PAM and Berkeley DB versions.
- You created a hash database with alternating username and password records.
- Your PAM rule uses the database stem without the
.dbsuffix. - You understand the difference between plaintext testing and
crypt(3)storage. - You tested through the real PAM client and checked both success and failure paths.
- You have a rollback copy of any PAM file you changed, and the disposable database is removed.