From 7f4f431eaa75bbf4b2dba764b61f5e9ca01b4cb5 Mon Sep 17 00:00:00 2001
From: Mark Sapiro <mark@msapiro.net>
Date: Sat, 9 Jul 2022 17:06:49 -0700
Subject: Fixed a possible list membership leak via the user options CGI.

---
 Mailman/Cgi/options.py | 60 +++++++++++++++++++++++++-------------------------
 NEWS                   |  7 +++++-
 2 files changed, 36 insertions(+), 31 deletions(-)

diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py
index 4b1850f2..b2775256 100644
--- a/Mailman/Cgi/options.py
+++ b/Mailman/Cgi/options.py
@@ -164,12 +164,35 @@ def main():
         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.'))
             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
         loginpage(mlist, doc, user, language)
         print doc.Format()
         return
@@ -205,10 +228,6 @@ def main():
     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.
@@ -234,39 +253,20 @@ def main():
             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
diff --git a/NEWS b/NEWS
index a3538f4c..8c1f36c9 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,10 @@ Here is a history of user visible changes to Mailman.
 
 2.1.40 (xx-xxx-xxxx)
 
+  i18n
+
+    - The German translation of `Esperanto` is fixed.  (LP: #1966685)
+
   Bug Fixes and other patches
 
     - Test for a valid header following a Unix From_ line in bin/cleanarch
@@ -14,7 +18,8 @@ Here is a history of user visible changes to Mailman.
     - A 500 Internal Server Error when requesting the options page for a
       non-member address on a list with private rosters is avoided.
       (LP: #1961762)
-    - The German translation of Esperanto is fixed.  (LP: #1966685)
+    - A possible list membership leak via the user options CGI is fixed.
+      (LP: #1968443)
 
 2.1.39 (13-Dec-2021)
 
-- 
cgit v1.2.3