diff --git a/SOURCES/mailman-CVE-2016-6893.patch b/SOURCES/mailman-CVE-2016-6893.patch
new file mode 100644
index 0000000..bee2601
--- /dev/null
+++ b/SOURCES/mailman-CVE-2016-6893.patch
@@ -0,0 +1,250 @@
+diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py
+index d187332..defc58d 100644
+--- a/Mailman/Cgi/admindb.py
++++ b/Mailman/Cgi/admindb.py
+@@ -39,6 +39,7 @@ from Mailman.ListAdmin import readMessage
+ from Mailman.Cgi import Auth
+ from Mailman.htmlformat import *
+ from Mailman.Logging.Syslog import syslog
++from Mailman.CSRFcheck import csrf_check
+
+ EMPTYSTRING = ''
+ NL = '\n'
+@@ -51,6 +52,9 @@ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
+ EXCERPT_HEIGHT = 10
+ EXCERPT_WIDTH = 76
+
++AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin,
++ mm_cfg.AuthListModerator)
++
+
+
+ def helds_by_sender(mlist):
+@@ -100,6 +104,18 @@ def main():
+ # Make sure the user is authorized to see this page.
+ cgidata = cgi.FieldStorage(keep_blank_values=1)
+
++ # CSRF check
++ safe_params = ['adminpw', 'admlogin', 'msgid', 'sender', 'details']
++ params = cgidata.keys()
++ if set(params) - set(safe_params):
++ csrf_checked = csrf_check(mlist, cgidata.getvalue('csrf_token'))
++ else:
++ csrf_checked = True
++ # if password is present, void cookie to force password authentication.
++ if cgidata.getvalue('adminpw'):
++ os.environ['HTTP_COOKIE'] = ''
++ csrf_checked = True
++
+ if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin,
+ mm_cfg.AuthListModerator,
+ mm_cfg.AuthSiteAdmin),
+@@ -177,7 +193,11 @@ def main():
+ elif not details:
+ # This is a form submission
+ doc.SetTitle(_('%(realname)s Administrative Database Results'))
+- process_form(mlist, doc, cgidata)
++ if csrf_checked:
++ process_form(mlist, doc, cgidata)
++ else:
++ doc.addError(
++ _('The form lifetime has expired. (request forgery check)'))
+ # Now print the results and we're done. Short circuit for when there
+ # are no pending requests, but be sure to save the results!
+ admindburl = mlist.GetScriptURL('admindb', absolute=1)
+@@ -198,7 +218,7 @@ def main():
+ mlist.Save()
+ return
+
+- form = Form(admindburl)
++ form = Form(admindburl, mlist=mlist, contexts=AUTH_CONTEXTS)
+ # Add the instructions template
+ if details == 'instructions':
+ doc.AddItem(Header(
+diff --git a/Mailman/Cgi/edithtml.py b/Mailman/Cgi/edithtml.py
+index ee1ccd0..5c9416e 100644
+--- a/Mailman/Cgi/edithtml.py
++++ b/Mailman/Cgi/edithtml.py
+@@ -30,9 +30,12 @@ from Mailman import Errors
+ from Mailman.Cgi import Auth
+ from Mailman.Logging.Syslog import syslog
+ from Mailman import i18n
++from Mailman.CSRFcheck import csrf_check
+
+ _ = i18n._
+
++AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin)
++
+
+
+ def main():
+@@ -82,6 +85,18 @@ def main():
+ # Must be authenticated to get any farther
+ cgidata = cgi.FieldStorage()
+
++ # CSRF check
++ safe_params = ['VARHELP', 'adminpw', 'admlogin']
++ params = cgidata.keys()
++ if set(params) - set(safe_params):
++ csrf_checked = csrf_check(mlist, cgidata.getvalue('csrf_token'))
++ else:
++ csrf_checked = True
++ # if password is present, void cookie to force password authentication.
++ if cgidata.getvalue('adminpw'):
++ os.environ['HTTP_COOKIE'] = ''
++ csrf_checked = True
++
+ # Editing the html for a list is limited to the list admin and site admin.
+ if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin,
+ mm_cfg.AuthSiteAdmin),
+@@ -126,7 +141,11 @@ def main():
+
+ try:
+ if cgidata.keys():
+- ChangeHTML(mlist, cgidata, template_name, doc)
++ if csrf_checked:
++ ChangeHTML(mlist, cgidata, template_name, doc)
++ else:
++ doc.addError(
++ _('The form lifetime has expired. (request forgery check)'))
+ FormatHTML(mlist, doc, template_name, template_info)
+ finally:
+ doc.AddItem(mlist.GetMailmanFooter())
+@@ -145,7 +164,8 @@ def FormatHTML(mlist, doc, template_name, template_info):
+ doc.AddItem(FontSize("+1", link))
+ doc.AddItem('
')
+ doc.AddItem('
')
+- form = Form(mlist.GetScriptURL('edithtml') + '/' + template_name)
++ form = Form(mlist.GetScriptURL('edithtml') + '/' + template_name,
++ mlist=mlist, contexts=AUTH_CONTEXTS)
+ text = Utils.maketext(template_name, raw=1, mlist=mlist)
+ # MAS: Don't websafe twice. TextArea does it.
+ form.AddItem(TextArea('html_code', text, rows=40, cols=75))
+diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py
+index ae701a7..328d9d5 100644
+--- a/Mailman/Cgi/options.py
++++ b/Mailman/Cgi/options.py
+@@ -33,6 +33,7 @@ from Mailman import MemberAdaptor
+ from Mailman import i18n
+ from Mailman.htmlformat import *
+ from Mailman.Logging.Syslog import syslog
++from Mailman.CSRFcheck import csrf_check
+
+ SLASH = '/'
+ SETLANGUAGE = -1
+@@ -47,6 +48,8 @@ except NameError:
+ True = 1
+ False = 0
+
++AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin,
++ mm_cfg.AuthListModerator, mm_cfg.AuthUser)
+
+
+ def main():
+@@ -88,6 +91,19 @@ def main():
+ # The total contents of the user's response
+ cgidata = cgi.FieldStorage(keep_blank_values=1)
+
++ # CSRF check
++ safe_params = ['displang-button', 'language', 'email', 'password', 'login',
++ 'login-unsub', 'login-remind', 'VARHELP', 'UserOptions']
++ params = cgidata.keys()
++ if set(params) - set(safe_params):
++ csrf_checked = csrf_check(mlist, cgidata.getvalue('csrf_token'))
++ else:
++ csrf_checked = True
++ # if password is present, void cookie to force password authentication.
++ if cgidata.getvalue('password'):
++ os.environ['HTTP_COOKIE'] = ''
++ csrf_checked = True
++
+ # Set the language for the page. If we're coming from the listinfo cgi,
+ # we might have a 'language' key in the cgi data. That was an explicit
+ # preference to view the page in, so we should honor that here. If that's
+@@ -269,6 +285,15 @@ def main():
+ # options. The first set of checks does not require the list to be
+ # locked.
+
++ # Before going further, get the result of CSRF check and do nothing
++ # if it has failed.
++ if csrf_checked == False:
++ doc.addError(
++ _('The form lifetime has expired. (request forgery check)'))
++ options_page(mlist, doc, user, cpuser, userlang)
++ print doc.Format()
++ return
++
+ if cgidata.has_key('logout'):
+ print mlist.ZapCookie(mm_cfg.AuthUser, user)
+ loginpage(mlist, doc, user, language)
+@@ -779,7 +804,8 @@ def options_page(mlist, doc, user, cpuser, userlang, message=''):
+ mlist.FormatButton('othersubs',
+ _('List my other subscriptions')))
+ replacements[''] = (
+- mlist.FormatFormStart('options', user))
++ mlist.FormatFormStart('options', user, mlist=mlist,
++ contexts=AUTH_CONTEXTS, user=user))
+ replacements[''] = user
+ replacements[''] = presentable_user
+ replacements[''] = mlist.FormatButton(
+diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py
+index dad51e7..3afe4bb 100644
+--- a/Mailman/HTMLFormatter.py
++++ b/Mailman/HTMLFormatter.py
+@@ -28,6 +28,8 @@ from Mailman.htmlformat import *
+
+ from Mailman.i18n import _
+
++from Mailman.CSRFcheck import csrf_token
++
+
+ EMPTYSTRING = ''
+ BR = '
'
+@@ -314,12 +316,17 @@ class HTMLFormatter:
+ container.AddItem("")
+ return container
+
+- def FormatFormStart(self, name, extra=''):
++ def FormatFormStart(self, name, extra='',
++ mlist=None, contexts=None, user=None):
+ base_url = self.GetScriptURL(name)
+ if extra:
+ full_url = "%s/%s" % (base_url, extra)
+ else:
+ full_url = base_url
++ if mlist:
++ return ("""\n' % (output, spaces)
+ return output
diff --git a/SOURCES/mailman-CVE-2021-42097.patch b/SOURCES/mailman-CVE-2021-42097.patch
new file mode 100644
index 0000000..cb2351d
--- /dev/null
+++ b/SOURCES/mailman-CVE-2021-42097.patch
@@ -0,0 +1,128 @@
+diff --git a/Mailman/CSRFcheck.py b/Mailman/CSRFcheck.py
+index a3b6885..73b003a 100644
+--- a/Mailman/CSRFcheck.py
++++ b/Mailman/CSRFcheck.py
+@@ -18,11 +18,13 @@
+ """ Cross-Site Request Forgery checker """
+
+ import time
++import urllib
+ import marshal
+ import binascii
+
+ from Mailman import mm_cfg
+-from Mailman.Utils import sha_new
++from Mailman.Logging.Syslog import syslog
++from Mailman.Utils import UnobscureEmail, sha_new
+
+ keydict = {
+ 'user': mm_cfg.AuthUser,
+@@ -37,6 +39,10 @@ keydict = {
+ def csrf_token(mlist, contexts, user=None):
+ """ create token by mailman cookie generation algorithm """
+
++ if user:
++ # Unmunge a munged email address.
++ user = UnobscureEmail(urllib.unquote(user))
++
+ for context in contexts:
+ key, secret = mlist.AuthContextInfo(context, user)
+ if key:
+@@ -49,9 +55,8 @@ def csrf_token(mlist, contexts, user=None):
+ token = binascii.hexlify(marshal.dumps((issued, keymac)))
+ return token
+
+-def csrf_check(mlist, token):
++def csrf_check(mlist, token, options_user=None):
+ """ check token by mailman cookie validation algorithm """
+-
+ try:
+ issued, keymac = marshal.loads(binascii.unhexlify(token))
+ key, received_mac = keymac.split(':', 1)
+@@ -61,6 +66,17 @@ def csrf_check(mlist, token):
+ key, user = key.split('+', 1)
+ else:
+ user = None
++ if user:
++ # This is for CVE-2021-42097. The token is a user token because
++ # of the fix for CVE-2021-42096 but it must match the user for
++ # whom the options page is requested.
++ raw_user = UnobscureEmail(urllib.unquote(user))
++ if options_user and options_user != raw_user:
++ syslog('mischief',
++ 'Form for user %s submitted with CSRF token '
++ 'issued for %s.',
++ options_user, raw_user)
++ return False
+ context = keydict.get(key)
+ key, secret = mlist.AuthContextInfo(context, user)
+ assert key
+diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py
+index 328d9d5..51c06b6 100644
+--- a/Mailman/Cgi/options.py
++++ b/Mailman/Cgi/options.py
+@@ -48,9 +48,6 @@ except NameError:
+ True = 1
+ False = 0
+
+-AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin,
+- mm_cfg.AuthListModerator, mm_cfg.AuthUser)
+-
+
+ def main():
+ doc = Document()
+@@ -95,14 +92,6 @@ def main():
+ safe_params = ['displang-button', 'language', 'email', 'password', 'login',
+ 'login-unsub', 'login-remind', 'VARHELP', 'UserOptions']
+ params = cgidata.keys()
+- if set(params) - set(safe_params):
+- csrf_checked = csrf_check(mlist, cgidata.getvalue('csrf_token'))
+- else:
+- csrf_checked = True
+- # if password is present, void cookie to force password authentication.
+- if cgidata.getvalue('password'):
+- os.environ['HTTP_COOKIE'] = ''
+- csrf_checked = True
+
+ # Set the language for the page. If we're coming from the listinfo cgi,
+ # we might have a 'language' key in the cgi data. That was an explicit
+@@ -131,6 +120,16 @@ def main():
+ user = Utils.LCDomain(Utils.UnobscureEmail(SLASH.join(parts[1:])))
+
+ # Avoid cross-site scripting attacks
++ if set(params) - set(safe_params):
++ csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token'),
++ Utils.UnobscureEmail(urllib.unquote(user)))
++ else:
++ csrf_checked = True
++ # if password is present, void cookie to force password authentication.
++ if cgidata.getfirst('password'):
++ os.environ['HTTP_COOKIE'] = ''
++ csrf_checked = True
++
+ safeuser = Utils.websafe(user)
+ try:
+ Utils.ValidateEmail(user)
+@@ -804,8 +803,9 @@ def options_page(mlist, doc, user, cpuser, userlang, message=''):
+ mlist.FormatButton('othersubs',
+ _('List my other subscriptions')))
+ replacements[''] = (
++ # Always make the CSRF token for the user. CVE-2021-42096
+ mlist.FormatFormStart('options', user, mlist=mlist,
+- contexts=AUTH_CONTEXTS, user=user))
++ contexts=[mm_cfg.AuthUser], user=user))
+ replacements[''] = user
+ replacements[''] = presentable_user
+ replacements[''] = mlist.FormatButton(
+diff --git a/Mailman/SecurityManager.py b/Mailman/SecurityManager.py
+index 4f6aa34..74b7a67 100644
+--- a/Mailman/SecurityManager.py
++++ b/Mailman/SecurityManager.py
+@@ -104,6 +104,7 @@ class SecurityManager:
+ if user is None:
+ # A bad system error
+ raise TypeError, 'No user supplied for AuthUser context'
++ user = Utils.UnobscureEmail(urllib.unquote(user))
+ secret = self.getMemberPassword(user)
+ userdata = urllib.quote(Utils.ObscureEmail(user), safe='')
+ key += 'user+%s' % userdata
diff --git a/SOURCES/mailman-CVE-2021-44227.patch b/SOURCES/mailman-CVE-2021-44227.patch
new file mode 100644
index 0000000..6afb82a
--- /dev/null
+++ b/SOURCES/mailman-CVE-2021-44227.patch
@@ -0,0 +1,82 @@
+diff --git a/Mailman/CSRFcheck.py b/Mailman/CSRFcheck.py
+index 73b003a..4328066 100644
+--- a/Mailman/CSRFcheck.py
++++ b/Mailman/CSRFcheck.py
+@@ -55,7 +55,7 @@ def csrf_token(mlist, contexts, user=None):
+ token = binascii.hexlify(marshal.dumps((issued, keymac)))
+ return token
+
+-def csrf_check(mlist, token, options_user=None):
++def csrf_check(mlist, token, cgi_user=None):
+ """ check token by mailman cookie validation algorithm """
+ try:
+ issued, keymac = marshal.loads(binascii.unhexlify(token))
+@@ -66,12 +66,25 @@ def csrf_check(mlist, token, options_user=None):
+ key, user = key.split('+', 1)
+ else:
+ user = None
++ # Don't allow unprivileged tokens for admin or admindb.
++ if cgi_user == 'admin':
++ if key not in ('admin', 'site'):
++ syslog('mischief',
++ 'admin form submitted with CSRF token issued for %s.',
++ key + '+' + user if user else key)
++ return False
++ elif cgi_user == 'admindb':
++ if key not in ('moderator', 'admin', 'site'):
++ syslog('mischief',
++ 'admindb form submitted with CSRF token issued for %s.',
++ key + '+' + user if user else key)
++ return False
+ if user:
+ # This is for CVE-2021-42097. The token is a user token because
+ # of the fix for CVE-2021-42096 but it must match the user for
+ # whom the options page is requested.
+ raw_user = UnobscureEmail(urllib.unquote(user))
+- if options_user and options_user != raw_user:
++ if cgi_user and cgi_user != raw_user:
+ syslog('mischief',
+ 'Form for user %s submitted with CSRF token '
+ 'issued for %s.',
+diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py
+index 6a391df..7df4726 100644
+--- a/Mailman/Cgi/admin.py
++++ b/Mailman/Cgi/admin.py
+@@ -91,7 +91,8 @@ def main():
+ 'letter', 'chunk', 'findmember']
+ params = cgidata.keys()
+ if set(params) - set(safe_params):
+- csrf_checked = csrf_check(mlist, cgidata.getvalue('csrf_token'))
++ csrf_checked = csrf_check(mlist, cgidata.getvalue('csrf_token'),
++ 'admin')
+ else:
+ csrf_checked = True
+ # if password is present, void cookie to force password authentication.
+diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py
+index defc58d..ea8bf78 100644
+--- a/Mailman/Cgi/admindb.py
++++ b/Mailman/Cgi/admindb.py
+@@ -108,7 +108,8 @@ def main():
+ safe_params = ['adminpw', 'admlogin', 'msgid', 'sender', 'details']
+ params = cgidata.keys()
+ if set(params) - set(safe_params):
+- csrf_checked = csrf_check(mlist, cgidata.getvalue('csrf_token'))
++ csrf_checked = csrf_check(mlist, cgidata.getvalue('csrf_token'),
++ 'admindb')
+ else:
+ csrf_checked = True
+ # if password is present, void cookie to force password authentication.
+diff --git a/Mailman/Cgi/edithtml.py b/Mailman/Cgi/edithtml.py
+index 5c9416e..678e43c 100644
+--- a/Mailman/Cgi/edithtml.py
++++ b/Mailman/Cgi/edithtml.py
+@@ -89,7 +89,8 @@ def main():
+ safe_params = ['VARHELP', 'adminpw', 'admlogin']
+ params = cgidata.keys()
+ if set(params) - set(safe_params):
+- csrf_checked = csrf_check(mlist, cgidata.getvalue('csrf_token'))
++ csrf_checked = csrf_check(mlist, cgidata.getvalue('csrf_token'),
++ 'admin')
+ else:
+ csrf_checked = True
+ # if password is present, void cookie to force password authentication.
diff --git a/SPECS/mailman.spec b/SPECS/mailman.spec
index 6f09ba5..5f306cc 100644
--- a/SPECS/mailman.spec
+++ b/SPECS/mailman.spec
@@ -1,10 +1,11 @@
+%global __python %{__python2}
# Turn off the brp-python-bytecompile script
%global __os_install_post %(echo '%{__os_install_post}' | sed -e 's!/usr/lib[^[:space:]]*/brp-python-bytecompile[[:space:]].*$!!g')
Summary: Mailing list manager with built in Web access
Name: mailman
Version: 2.1.15
-Release: 30%{?dist}
+Release: 30%{?dist}.2
Epoch: 3
Group: Applications/Internet
Source0: ftp://ftp.gnu.org/pub/gnu/mailman/mailman-%{version}.tgz
@@ -48,6 +49,9 @@ Patch27: mailman-2_1-xss_vulnerability.patch
Patch28: mailman-findmember_decode.patch
Patch29: mailman-long_text_description.patch
Patch30: mailman-cve_2018_0618.patch
+Patch31: mailman-CVE-2016-6893.patch
+Patch32: mailman-CVE-2021-42097.patch
+Patch33: mailman-CVE-2021-44227.patch
License: GPLv2+
@@ -581,6 +585,15 @@ exit 0
%dir %attr(775,root,%{mmgroup}) %{lockdir}
%changelog
+* Sun Nov 28 2021 Martin Osvald - 3:2.1.15-30.2
+- Fix for CVE-2021-44227
+- Resolves: #2026866
+
+* Tue Nov 09 2021 Martin Osvald - 3:2.1.15-30.1
+- Fix for CVE-2016-6893
+- Fix for CVE-2021-42097
+- Resolves: #2024884, #2020688
+
* Wed Jul 31 2019 Pavel Zhukov - 3:2.1.15-30
- Resolves: #1599692 - Sanitize input on listinfo page (CVE-2018-0618)