Skip to content

Fix Python 3 behavioral bugs in handlers and SecurityManager#34

Open
thegushi wants to merge 13 commits into
jaredmauch:mainfrom
thegushi:fix/python3-handler-bugs
Open

Fix Python 3 behavioral bugs in handlers and SecurityManager#34
thegushi wants to merge 13 commits into
jaredmauch:mainfrom
thegushi:fix/python3-handler-bugs

Conversation

@thegushi
Copy link
Copy Markdown
Collaborator

Several Python 3 behavioral regressions in handlers, exposed by the test suite once the test infrastructure issues were cleaned up.

CookHeaders — leading space in subject prefix:

  • In the us-ascii path, ' '.join([recolon, prefix, subject]) prepended a space when recolon was empty. Filter empty strings before joining.
  • In the Header path (used when cset != 'us-ascii', e.g. English now uses utf-8), old_style=True initialized the Header with an empty recolon as the first chunk, causing the same leading-space bug. Fix both old_style sub-cases to skip empty chunks.

Decorate — base64-encoding ASCII content:

  • Python 3's email library base64-encodes content when the charset is utf-8. The non-multipart path was encoding the payload to bytes then calling set_payload(bytes, 'utf-8'), triggering base64. Fix: verify encodability without converting to bytes; pass str to set_payload.
  • The multipart path used MIMEText(text, 'plain', lcset) where lcset='utf-8', causing the same issue. Fix: prefer the message's own charset for ASCII content.
  • Both paths now prefer the message's original charset (mcset) over the list charset (lcset), falling back to lcset only when mcset can't encode the decorated content.

SecurityManager — md5 binary digest not recognized:

  • verify_password couldn't handle the legacy md5 binary digest format (16 raw bytes), returning False, False because the bytes couldn't be decoded as ASCII. Add an explicit check for 16-byte binary digests before the ASCII decode attempt.

Tests:

  • English list charset is now utf-8 (per Defaults.py.in); update test_ack_* charset assertions and use get_payload(decode=True) for payload comparison.
  • Fix hold notification cookie extraction to decode the base64/utf-8 payload before splitting.
  • test_list_admin_upgrade: md5 passwords now upgrade to PBKDF2 (not SHA1); update assertions accordingly.

thegushi added 13 commits May 11, 2026 23:04
iconv -o is a GNU iconv extension not supported on BSD. Replace the
subprocess call with Python's own open() encoding support, which is
portable and removes the iconv dependency entirely.
Replace file -bi encoding detection with a Python-native UTF-8 open
attempt. If the file opens cleanly as UTF-8, skip it; otherwise
convert from the known locale encoding. No external tools needed.
Detects legacy SHA1 hex digest passwords that need upgrading to
the PBKDF2 format introduced by hash_password().
sha_new() requires bytes in Python 3 but was receiving a str.
Switch to hash_password() so new lists get PBKDF2 hashes from
the start rather than legacy SHA1. Fixes jaredmauch#24.
'Hit enter to notify %(listname)s owner...' was never interpolated.
Replace print()+readline() with input() which handles both the prompt
and waiting for Enter in one call.
check_perms and Mailman/MTA/Postfix.py both had Python 2-style
print C_('...') % locals(), statements where the migration to Python 3
dropped the % locals() substitution, leaving literal %(varname)s in
error output. Also fix two bare print -> print() in Postfix.py.
BSD make does not have a wildcard function -- it treats the argument
as a variable name with spaces, generating warnings. Use $(POFILES)
in messages/ (already defined) and drop the dependency list in
templates/ (stamp file is sufficient for a clean build).
distutils was removed in Python 3.12. The configure check was using
distutils.sysconfig solely to verify Python development headers exist.
Replace with the sysconfig stdlib module (available since Python 3.2)
using sysconfig.get_path('include') in place of get_python_inc().
CookHeaders: filter empty recolon/subject before joining to avoid
leading space in subject prefix (e.g. ' [XTEST] foo' -> '[XTEST] foo').

Decorate: prefer the message's own charset over the list charset when
building the decorated payload. Passing bytes to set_payload() with a
utf-8 charset causes Python 3's email library to base64-encode ASCII
content unnecessarily. Verifying encodability without assigning keeps
the payload as str, which set_payload() handles without adding CTE.

Utils.verify_password: handle legacy md5 binary digest (16 raw bytes)
stored before the SHA1 upgrade path existed. The function was rejecting
these because they can't be decoded as ASCII.

test_handlers: English list charset is now utf-8 (per Defaults.py.in),
so acknowledgement messages are utf-8-encoded. Update charset assertions
and use get_payload(decode=True) for content comparison. Fix hold
notification cookie extraction to decode base64/utf-8 payload first.
CookHeaders: also fix leading space in Header path (non-us-ascii cset).
When old_style=True and recolon is empty, the Header was initialized with
an empty first chunk causing a leading space. When old_style=False, skip
appending recolon when it is empty.

Decorate multipart path: same charset fix as non-multipart — prefer mcset
over lcset for MIMEText parts to avoid base64-encoding ASCII content.

test_security_mgr: md5->PBKDF2 upgrade now works; update test to check
for PBKDF2 format instead of the old SHA1 hexdigest intermediate format,
and verify the upgraded password still authenticates.
test_multipart: Python 3's email generator adds Content-Transfer-Encoding:
7bit to multipart outer message and plain sub-messages; update expected
string to match actual Python 3.11 output.

Utils.verify_password: add legacy crypt(3) as a final fallback format so
crypt-stored passwords can still authenticate and trigger the PBKDF2 upgrade
path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant