Show a Login Message from PAM with pam_echo
A janky welcome banner cobbled together in a shell wrapper is exactly what pam_echo replaces. It prints a text file as a proper PAM informational message with no custom script required. This guide sets one up, tests it safely and then removes it again. Allow about ten minutes.
The route
Jump straight to the step you need, or tick off Done means at the end.
pam_echo is a PAM module, not a command you run at a shell prompt: a PAM-aware service loads its shared object while working through its configuration. This guide matches the installed libpam-modules package, version 1.5.3-5ubuntu5.7. You need an ordinary shell for the checks, and root only when you edit a system PAM file or place a message under a protected directory.
1. Know what the module actually does
pam_echo prints a file as a PAM_TEXT_INFO message. It provides all four PAM module types: auth, account, password and session. It does not authenticate anyone, change a password or grant access on its own; the calling application's conversation function decides whether and how the message actually reaches the user.
The configuration line always has this shape:
type control pam_echo.so file=/path/to/message
type is the point in the PAM transaction, control is the stack rule, and file= names the message file. Keep the message short enough for the client to display cleanly.
2. Write and inspect a test message
Make a disposable message in /tmp first. This step is unprivileged and never touches PAM configuration:
message_file=$(mktemp /tmp/pam-echo.XXXXXX)
chmod 0644 "$message_file"
cat >"$message_file" <<'EOF'
Welcome to %h. Service: %s. Account: %u.
EOF
printf 'Message file: %s\n' "$message_file"
sed -n '1,3p' "$message_file"
Expect the temporary pathname followed by the line with the literal placeholders still in it. Do not expand those in the shell; pam_echo expands them itself when PAM reads the message:
%his the local host,%His the remote host.%sis the PAM service name,%tis the controlling terminal.%uis the local user,%Uis the remote user.
Any other sequence starting with % just becomes the character after the percent sign, an easy trap: write a literal percent sign as %%.
3. Add the module to a PAM stack
Choose the service deliberately: PAM configuration is service-specific, so a line in /etc/pam.d/sshd only affects SSH, while a line in another file affects something else entirely. Look at the target file before touching it:
sudo sed -n '1,160p' /etc/pam.d/sshd
That is a privileged read. Keep the message file's pathname from the previous step (swap in the real one if it has moved from /tmp), then add a line like this at the point where the message makes sense, such as during authentication:
auth optional pam_echo.so file=/tmp/pam-echo.REPLACE
optional means this module's result is not, by itself, required for the stack to succeed, and it does not guarantee any client will actually display the text.
Warning
Do not paste this into a production PAM file without a recovery plan. A malformed PAM edit can prevent logins, and a message file under /tmp is not a durable deployment location. Use a root-owned path with sane permissions for a real setup, keep an already-open root session running while you test, and copy the line into a review buffer rather than trusting an automatic find-and-replace.
4. Rule out the common failure paths first
Before testing a real login, check the module and the message file both exist and are readable:
test -r /usr/lib/x86_64-linux-gnu/security/pam_echo.so && echo 'module exists'
test -r "$message_file" && echo 'message is readable'
- Success returns
PAM_SUCCESS. - Missing file, or the transaction carries
PAM_SILENT: the module returnsPAM_IGNOREand prints nothing. - Memory allocation failure: returns
PAM_BUF_ERR.
A blank terminal therefore does not prove the line was skipped: check the path, the service file, and whether the client itself suppresses informational messages.
Test through the real service, not by invoking the shared object directly. For SSH, open a new connection from a separate terminal and keep the existing session running. For a display manager or anything else, use that service's own safe test path. Never test a password stack with repeated guesses, and never treat an account lockout policy as your test harness.
5. Undo and clean up
Remove the exact pam_echo.so line from the service file using the same privileged editing method you used to add it, then confirm it is gone:
sudo grep -nF 'pam_echo.so' /etc/pam.d/sshd || echo 'pam_echo line removed'
Then delete the disposable message, which only removes the one path held in the shell variable:
rm -- "$message_file"
If the shell was closed, locate the specific temporary file with find /tmp -maxdepth 1 -type f -name 'pam-echo.*' -user "$(id -un)" -print and remove only the file you recognise. For a permanent setup, replace the temporary path with a reviewed root-owned file and write down which PAM service owns the line.
Done means
- Message file ready. It holds the text you intend and the PAM service can read it.
- Module line correct. Right service, module type, control flag and file path.
- Placeholders left alone for PAM to expand, with literal percent signs doubled.
- Tested for real through an actual PAM client, with a separate recovery session open.
- Cleaned up or documented. The temporary setup is gone, or the permanent change is recorded and reviewable.