Solution with PAM, policy configured in sudoers
Create /usr/local/bin/use-own-password with the right ownership and permissions. The following shell code will do it:
sudo sh -e -c '
cat > /usr/local/bin/use-own-password << "EOF"
#!/bin/sh -e
From https://superuser.com/a/1869100/432690
case "$1" in
no-op)
exit 0
;;
verify)
IFS= read -r password || true
user="${password%%:}"
[ "$user" = "$password" ] || [ "$user" = "" ] && exit 1
password="${password#:}"
printf "%s\n" "$password" | 2>/dev/null sudo -u "$user"
SUDO_ASKPASS=/usr/local/bin/use-own-password sudo -S -u "$PAM_USER"
/usr/local/bin/use-own-password no-op
;;
esac
EOF
chmod 755 /usr/local/bin/use-own-password
'
Yes, the file shall be executable for everyone. The script by itself is totally harmless. Its ability to log in one user as another comes from its interaction with PAM, when it is run from the inside of PAM.
Next sudoedit /etc/pam.d/sddm and place the following lines before any existing auth … line:
# Allowing sudoers, see https://superuser.com/a/1869100/432690
auth sufficient pam_exec.so expose_authtok quiet /usr/local/bin/use-own-password verify
Then run sudo visudo and configure sudoers:
The solution will not work with requiretty. When in doubt, use Defaults !requiretty.
The solution nests sudos. The outer sudo will be run by root to run the inner sudo as the user whose credentials you want to use. It's crucial that the outer sudo does not ask for password. Fortunately this is the case: root can use sudo freely and I don't think there is even a way to break this.
If and only if userA is a sudoer allowed to run /usr/local/bin/use-own-password no-op as userB then our script will authorize logging into sddm as userB with userA's credentials supplied as userA:passwordA in the password field. Now it's your job to set up sudoers, so only right users can run the script as right users. Basic lines in the sudoers file will be like:
userA ALL=(userB) PASSWD: /usr/local/bin/use-own-password no-op
Using NOPASSWD for this will allow anyone to log into sddm as userB by just claiming they are userA (and using any password). The point is you are not yet logged in, in particular not logged in as userA and there is no mean to verify you are userA except userA's password. Sddm will just pass userA:whatever from the password field to our script and in case of NOPASSWD this will always succeed.
Keep in mind that the last match is used. E.g. you may want to use the above example line for use-own-password and generic userA ALL=(userB) NOPASSWD: ALL for everything else; then you shall place the example line after the generic line, otherwise NOPASSWD from the generic line will apply to sddm. I think you shall place all lines allowing use-own-password at the very end of sudoers (even after @includedir and so).
Also keep in mind you don't need specific lines mentioning use-own-password for the solution to work. A generic line userC ALL=(userD) ALL will allow userC to log into sddm as userD. If you want to prevent specifically this then you need to turn the last ALL into !/usr/local/bin/use-own-password.
Note there are options (rootpw, targetpw, runaspw) that make sudo ask for password other than the one of invoking user. They will interfere, if used.
If you configure everything correctly, you will be able to log into sddm as userB by choosing userB as the user and supplying userA:passwordA as the password. The colon is verbatim, it is a separator.
How it works
Our use-own-password has two modes of operation, they are chosen by the first argument.
First PAM runs the script in the verify mode, supplying the typed password (hopefully userA:passwordA) via stdin. The script extracts the authenticating user's name and their actual password from the password string.
Then the script uses sudo to run another sudo as the authenticating user, just to run another instance of itself as the target user. The other instance is in the no-op mode, where it is equivalent to true, its only purpose is to check if the authenticating user is allowed (by sudoers) to run the script as the target user: the script will succeed if so, sudo will fail in the first place if not.
The inner sudo reads password from its stdin. It is the password extracted from userA:passwordA.
Notes
- A colon in authenticating user's password will not break things. Only the first colon in
userA:passwordA is a separator. Usernames cannot contain :, so there is no ambiguity which colon is the separator.
- A typed password with a colon will always be interpreted as
userX:passwordX first. It may happen that the target user's password contains :; this will not break things because if our script fails then /etc/pam.d/sddm will continue as if nothing happened and it will eventually log the target user in (if the whole password is indeed right).
- It may happen that somebody's password is exactly
userA:passwordA, where passwordA is userA's password and this user is a sudoer with access to our solution. If so, then the original user, while supplying their password, will inadvertently log in using our method and userA's credentials. In many cases there will be no difference, as he or she would log in anyway. Only if /etc/pam.d/sddm would normally disallow the original user or do something extra (e.g. require two-factor authentication) then the user will notice.
- It's possible to use this solution along with the one that uses a master password. Simply there will be two extra entries in
/etc/pam.d/sddm instead of one. This means in the context of your question you, as the admin, can have granular control over other users (through sudoers), while using a convenient master password for your own purpose.
- To revert: