Bug 58127 - dovecot login resets pwdChangeNextLogin flag to 0
Summary: dovecot login resets pwdChangeNextLogin flag to 0
Status: CLOSED FIXED
Alias: None
Product: UCS
Classification: Unclassified
Component: Mail - Dovecot
Version: UCS 5.2
Hardware: Other Linux
: P5 normal
Target Milestone: UCS 5.2-1-errata
Assignee: Florian Best
QA Contact: Johannes Lohmer
URL: https://git.knut.univention.de/univen...
Keywords:
Depends on:
Blocks: 58341
  Show dependency treegraph
 
Reported: 2025-03-28 08:39 CET by Florian Best
Modified: 2025-05-28 12:14 CEST (History)
0 users

See Also:
What kind of report is it?: ---
What type of bug is this?: ---
Who will be affected by this bug?: ---
How will those affected feel about the bug?: ---
User Pain:
Enterprise Customer affected?:
School Customer affected?:
ISV affected?:
Waiting Support:
Flags outvoted (downgraded) after PO Review:
Ticket number:
Bug group (optional):
Customer ID:
Max CVSS v3 score:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Florian Best univentionstaff 2025-03-28 08:39:58 CET
> 1. You set pwdChangeNext=1 for a user in UDM
> 2. If the user was already logged in the last minutes, dovecot will "Cache" that behavior for some seconds, still serving the directory index. That's no problem so far, but important to remember
> 3. If the dovecot cache has been invalidated or dovecot is restarted, the "state" is as follows:
> 
> ```
> # udm users/user list --filter uid=lehrer.s401 | grep -E "DN:|pwdChangeNext"                                                                                                                                                                                        
> DN: uid=lehrer.s401,cn=lehrer,cn=users,ou=S40,dc=…
>   pwdChangeNextLogin: 0
> ```
> 
> -> The user tries to login by IMAP and gets rejected from the PAM Stack, as a password change is required:
> 
> ```
> ~ # curl imaps://$(hostname -f) --user 'lehrer.s401:nixda' -k                                                                                                                                                                                                           root@ucs-ox
> curl: (67) Login denied
> ```
> 
> 4. This failed Login attempt causes the pwdChangeNextLogin to be changed back to 0 -> removes the requirement to change the password for the user, if you try the same commands 1 second later:
> 
> ```
> 
> ~ # udm users/user list --filter uid=lehrer.s401 | grep -E "DN:|pwdChangeNext"                                                                                                                                                                                       
> DN: uid=lehrer.s401,cn=lehrer,cn=users,ou=S40,…
>   pwdChangeNextLogin: 0
> 
> ~ # curl imaps://$(hostname -f) --user 'lehrer.s401:nixda' -k                                                                                                                                                                                                           root@ucs-ox
> * LIST (\HasNoChildren \UnMarked \Sent) "/" "Gesendete Objekte"
> * LIST (\HasNoChildren \UnMarked \Trash) "/" Papierkorb
> * LIST (\HasNoChildren \UnMarked \Drafts) "/" Entw&APw-rfe
> * LIST (\HasNoChildren) "/" confirmed-ham
> * LIST (\HasNoChildren \Junk) "/" confirmed-spam
> * LIST (\HasNoChildren \Junk) "/" Spam
> * LIST (\HasNoChildren) "/" INBOX
> ```
> 
> I had the same issue like a week ago on my test environment but couldn't reproduce it on fresh installed servers, causing me to think about a error in my environment. The clients tellings they had actual wars going on in their office because they suspected team members to not set the pwdChangeNext flag as mail accounts got hijacked over and over again without actual password changes made me pretty certain about a product bug.
Comment 1 Florian Best univentionstaff 2025-03-28 08:52:40 CET
Reproducer:

name="foo"
dn="uid=$name,cn=users,dc=univention,dc=intranet"
pw="Start2020"

udm users/user modify --dn "$dn" --set password="$pw" --set overridePWHistory=1
udm users/user modify --dn "$dn" --set pwdChangeNextLogin=1

udm users/user list --filter uid="$name"| grep -E "DN:|pwd"

cat > repro.py <<-EOF
from PAM import *
from univention.management.console.pam import PamAuth
p = PamAuth()
p.pam = pam()
p.pam.start('dovecot')
p.authenticate('$name', '$pw')
EOF

python3 repro.py
sleep 3
python3 repro.py
sleep 3

udm users/user list --filter uid=$name | grep -E "DN:|pwd"

→ this now says "pwdChangeNextLogin: 0" instead of still "1"
Comment 2 Florian Best univentionstaff 2025-03-28 09:00:37 CET
Dovecot logins happen via the pam stack "dovecot" which uses "pam-krb5".
pam-krb5 detects the expired password and contacts the kerberos server(heimdal) in Samba, which somehow forces a password change without prompting it - resulting in the "pwdLastSet" in Samba being set to "0". The S4-Connector then syncs that to UCS, which makes the corresponding LDAP changes for "pwdChangeNextLogin" → "0".
Comment 3 Florian Best univentionstaff 2025-03-28 09:06:34 CET
The solution is to use the `defer_pwchange` pam-krb5 option:

```
diff --git mail/univention-mail-dovecot/conffiles/etc/pam.d/50_dovecot mail/univention-mail-dovecot/conffiles/etc/pam.d/50_dovecot
index b347ce9a688..6786a4ebad8 100644
--- mail/univention-mail-dovecot/conffiles/etc/pam.d/50_dovecot
+++ mail/univention-mail-dovecot/conffiles/etc/pam.d/50_dovecot
@@ -13,7 +13,7 @@ auth += " ldap_port=%s" % configRegistry.get("ldap/server/port", "7389")
 print(auth)
 print(f'''
 auth     sufficient      pam_sss.so
-auth     required        pam_krb5.so use_first_pass minimum_uid={krb5_minimum_uid}
+auth     required        pam_krb5.so use_first_pass defer_pwchange minimum_uid={krb5_minimum_uid}
 
 account  sufficient      pam_unix.so
 account  required        pam_sss.so
```

I am not sure, why we don't use `account pam_krb5.so` here but only pam_sss.so but I think that is enough.

From https://github.com/rra/pam-krb5/blob/main/README.md

> By default, pam_authenticate intentionally does not follow the PAM standard for handling expired accounts and instead returns failure from pam_authenticate unless the Kerberos libraries are able to change the account password during authentication. Too many applications either do not call pam_acct_mgmt or ignore its exit status. The fully correct PAM behavior (returning success from pam_authenticate and PAM_NEW_AUTHTOK_REQD from pam_acct_mgmt) can be enabled with the defer_pwchange option.

> The defer_pwchange option is unfortunately somewhat tricky to implement. In this case, the calling sequence is: 
>·
>     pam_authenticate
>     pam_acct_mgmt
>     pam_chauthtok
>     pam_setcred
>     pam_open_session

> During the first pam_authenticate, we can't obtain credentials and therefore a ticket cache since the password is expired. But pam_authenticate isn't called again after pam_chauthtok, so pam_chauthtok has to create a ticket cache. We however don't want it to do this for the normal password change (passwd) case.

> What we do is set a flag in our PAM data structure saying that we're processing an expired password, and pam_chauthtok, if it sees that flag, redoes the authentication with password prompting disabled after it finishes changing the password.

> Unfortunately, when handling password changes this way, pam_chauthtok will always have to prompt the user for their current password again even though they just typed it. This is because the saved authentication tokens are cleared after pam_authenticate returns, for security reasons. We could hack around this by saving the password in our PAM data structure, but this would let the application gain access to it (exactly what the clearing is intended to prevent) and breaks a PAM library guarantee. We could also work around this by having pam_authenticate get the kadmin/changepw authenticator in the expired password case and store it for pam_chauthtok, but it doesn't seem worth the hassle.
Comment 4 Florian Best univentionstaff 2025-04-03 15:15:18 CEST
univention-mail-dovecot.yaml
b6db87f4d14f | fix(dovecot): prevent dovecot PAM stack from resetting `pwdChangeNextLogin=1` state during authentication

univention-mail-dovecot (8.1.0)
b6db87f4d14f | fix(dovecot): prevent dovecot PAM stack from resetting `pwdChangeNextLogin=1` state during authentication
Comment 5 Johannes Lohmer univentionstaff 2025-04-03 16:01:18 CEST
errata-yaml ok
debian-package ok
code-review ok
discussed with PO ok
Comment 6 Felix Botner univentionstaff 2025-04-09 15:41:22 CEST
<https://errata.software-univention.de/#/?erratum=5.2x65>