## http://www.berklix.com/~jhs/src/bsd/fixes/freebsd/ports/gen/mail/mailman/files/options.py.diff
## cd /usr/local/mailman/Mailman/Cgi ; patch -p3 < ~jhs/public_html/src/bsd/fixes/FreeBSD/ports/gen/mail/mailman/files/options.py.diff
##
##	Downloaded Thu Aug 13 14:37:52 CEST 2026 from https://launchpadlibrarian.net/667449948/2.1.39.diff
##	Note this patch is not yet in right place JJLATER
##	After I read below:
##	{
##	To: mailman-users@@@python.org
##	From: "Julian Stacey" <jhs@@@berklix.com>
##	Date: Wed, 12 Aug 2026 10:12:59 +0200
##	Subject: [Mailman-Users] when a non subscribed person unsubscribes: Internal Server
##
##	Hi mailman-users@@@python.org
##	Summary:
##	- Is below normal for mailman-2.1.39 ?
##	- Or do I have a corrupted script ?
##	- Is better error handling possible ?
##
##	If a user who is not subscribed to a list tries to unsubscribe (perhaps
##	he's not sure if he has yet unsubed all in various changes),
##
##	Example:
##		http://mailman.berklix.org/mailman/listinfo/test
##		scroll to end "To unsubscribe"
##		Type dummy@@@domain.nowhere
##		Click unsubscribe
##
##	Receive:
##		http://mailman.berklix.org/mailman/options/test
##		Internal Server Error
##
##		The server encountered an internal error or misconfiguration
##		and was unable to complete your request.
##
##		Please contact the server administrator at webmaster@@@berklix.org
##		to inform them of the time this error occurred, and the
##		actions you performed just before this error.
##
##		More information about this error may be available in the
##		server error log.
##
##	/var/log/httpd-error.log:
##		[cgi:error] [pid 7888] [client browser_home_ip_number:57904] End
##		of script output before headers: options, referer:
##		http://mailman.berklix.org/mailman/listinfo/test
##
##	It would be nicer to see:	"Your email address is not subscribed"
##	that would save time for user, list & httpd owners.
##
##	Is the nasty error message to hinder malicious flood probers trying
##	variant addresses eg @target.com to see who may be subscribed
##	[then probed elsewhere] ?  Adding a few seconds 'man 3 sleep' would
##	hinder robots.
##
##	------------------------------------------------------
##
##	To: mailman-users@@@python.org
##	From: Mark Sapiro <mark@@@msapiro.net>
##	Date: Wed, 12 Aug 2026 10:32:23 -0700 (19:32 CEST)
##
##	On 8/12/26 01:12, Julian Stacey wrote:
##	>
##	> Example:
##	>	http://mailman.berklix.org/mailman/listinfo/test
##	>	scroll to end "To unsubscribe"
##	>	Type dummy@domain.nowhere
##	>	Click unsubscribe
##	>
##	> Receive:
##	>	http://mailman.berklix.org/mailman/options/test
##	>	Internal Server Error
##
##	I'm not seeing this in my Mailman 2.1.39 test installation. I get the
##	member options login page and when I click `Unsubscribe` on that page I
##	get depending on the list's unsubscribe policy, either
##
##	If you are a list member, your unsubscription request has been forwarded
##	to the list administrator for approval.
##
##	or
##
##	If you are a list member, a confirmation email has been sent.
##
##	There are some changes in this area between 2.1.39 and my test
##	installation, but I think those only affect the messages and not the
##	process.
##
##	What do you get in Mailman's logs/error log?
##
##	------------------------------------------------------
##
##	From: Mark Sapiro <mark@@@msapiro.net>
##	Date: Wed, 12 Aug 2026 10:45:41 -0700 (19:45 CEST)
##
##	On 8/12/26 10:32, Mark Sapiro wrote:
##	>
##	> There are some changes in this area between 2.1.39 and my test
##	> installation, but I think those only affect the messages and not the
##	> process.
##
##	I spoke too soon. Your issue is actually
##	<https://bugs.launchpad.net/mailman/+bug/1961762>.
##
##	See <https://bugs.launchpad.net/mailman/+bug/2017813> and
##	<https://bugs.launchpad.net/mailman/+bug/2017813/+attachment/5674724/+files/2.1.39.diff>
##	for a patch to fix this.
##
##	Note that Mailman 2.1 is end of life and there won't be a 2.1.40 release.
##	}
##
##	-----------------


=== modified file 'Mailman/Cgi/options.py'
--- old/Mailman/Cgi/options.py	2021-11-24 03:38:19 +0000
+++ new/Mailman/Cgi/options.py	2023-05-22 19:49:52 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2023 by the Free Software Foundation, Inc.
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -164,13 +164,40 @@
         loginpage(mlist, doc, None, language)
         print doc.Format()
         return
-    # Sanity check the user, but only give the "no such member" error when
-    # using public rosters, otherwise, we'll leak membership information.
+    # Sanity check the user, but we have to give the appropriate error msg
+    # to not potentially leak membership info. This is a kludge here. We
+    # have to check membership here to avoid LP: #1951769, but then we have
+    # to give the appropriate error to avoid LP: #1968443
+    msgc = _('If you are a list member, a confirmation email has been sent.')
+    msgb = _('You already have a subscription pending confirmation')
+    msga = _("""If you are a list member, your unsubscription request has been
+             forwarded to the list administrator for approval.""")
+    msgd = _("""If you are a list member,
+            your password has been emailed to you.""")
     if not mlist.isMember(user):
         if mlist.private_roster == 0:
             doc.addError(_('No such member: %(safeuser)s.'))
-            loginpage(mlist, doc, None, language)
-            print doc.Format()
+            user = None
+        elif cgidata.has_key('login-unsub'):
+            syslog('mischief',
+                   'Unsub attempt of non-member w/ private rosters: %s',
+                   user)
+            if mlist.unsubscribe_policy:
+                doc.addError(msga, tag='')
+            else:
+                doc.addError(msgc, tag='')
+            user = None
+        elif cgidata.has_key('login-remind'):
+            syslog('mischief',
+                   'Reminder attempt of non-member w/ private rosters: %s',
+                   user)
+            doc.addError(msgd, tag='')
+            user = None
+        # We get here with a non-None user in the case of a non-member with
+        # private rosters.  This creates a possible membership leak, but we
+        # fix that a different way. See LP: #2017813.
+        loginpage(mlist, doc, user, language)
+        print doc.Format()
         return

     # Avoid cross-site scripting attacks
@@ -204,10 +231,6 @@
     i18n.set_language(userlang)

     # Are we processing an unsubscription request from the login screen?
-    msgc = _('If you are a list member, a confirmation email has been sent.')
-    msgb = _('You already have a subscription pending confirmation')
-    msga = _("""If you are a list member, your unsubscription request has been
-             forwarded to the list administrator for approval.""")
     if cgidata.has_key('login-unsub'):
         # Because they can't supply a password for unsubscribing, we'll need
         # to do the confirmation dance.
@@ -233,39 +256,20 @@
             finally:
                 mlist.Unlock()
         else:
-            # Not a member
-            if mlist.private_roster == 0:
-                # Public rosters
-                doc.addError(_('No such member: %(safeuser)s.'))
-            else:
-                syslog('mischief',
-                       'Unsub attempt of non-member w/ private rosters: %s',
-                       user)
-                if mlist.unsubscribe_policy:
-                    doc.addError(msga, tag='')
-                else:
-                    doc.addError(msgc, tag='')
+            # Not a member handled above.
+            pass
         loginpage(mlist, doc, user, language)
         print doc.Format()
         return

     # Are we processing a password reminder from the login screen?
-    msg = _("""If you are a list member,
-            your password has been emailed to you.""")
     if cgidata.has_key('login-remind'):
         if mlist.isMember(user):
             mlist.MailUserPassword(user)
-            doc.addError(msg, tag='')
+            doc.addError(msgd, tag='')
         else:
-            # Not a member
-            if mlist.private_roster == 0:
-                # Public rosters
-                doc.addError(_('No such member: %(safeuser)s.'))
-            else:
-                syslog('mischief',
-                       'Reminder attempt of non-member w/ private rosters: %s',
-                       user)
-                doc.addError(msg, tag='')
+            # Not a member handled above.
+            pass
         loginpage(mlist, doc, user, language)
         print doc.Format()
         return
@@ -293,7 +297,9 @@
         # to authenticate via cgi (instead of cookie), then print an error
         # message.
         if cgidata.has_key('password'):
-            doc.addError(_('Authentication failed.'))
+            if mlist.private_roster == 0:
+                # Only add error with public rosters lp: #2015416
+                doc.addError(_('Authentication failed.'))
             remote = os.environ.get('HTTP_FORWARDED_FOR',
                      os.environ.get('HTTP_X_FORWARDED_FOR',
                      os.environ.get('REMOTE_ADDR',
@@ -307,9 +313,11 @@
                 syslog('mischief',
                        'Login failure with private rosters: %s from %s',
                        user, remote)
-                user = None
+                # Don't clear user here. See LP: #2017813.
             # give an HTTP 401 for authentication failure
-            print 'Status: 401 Unauthorized'
+            if mlist.private_roster == 0:
+                # Only add error with public rosters lp: #2015416
+                print 'Status: 401 Unauthorized'
         loginpage(mlist, doc, user, language)
         print doc.Format()
         return

