diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e040e1c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/mailman-2.1.15.tgz diff --git a/.mailman.metadata b/.mailman.metadata new file mode 100644 index 0000000..75f3950 --- /dev/null +++ b/.mailman.metadata @@ -0,0 +1 @@ +462ac96331491c76aca0128d8f9ced18c50a75d7 SOURCES/mailman-2.1.15.tgz diff --git a/README.md b/README.md deleted file mode 100644 index 0e7897f..0000000 --- a/README.md +++ /dev/null @@ -1,5 +0,0 @@ -The master branch has no content - -Look at the c7 branch if you are working with CentOS-7, or the c4/c5/c6 branch for CentOS-4, 5 or 6 - -If you find this file in a distro specific branch, it means that no content has been checked in yet diff --git a/SOURCES/httpd-mailman.conf b/SOURCES/httpd-mailman.conf new file mode 100644 index 0000000..58dbc90 --- /dev/null +++ b/SOURCES/httpd-mailman.conf @@ -0,0 +1,24 @@ +# +# httpd configuration settings for use with mailman. +# + +ScriptAlias /mailman/ @MMDIR@/cgi-bin/ + + AllowOverride None + Options ExecCGI + Require all granted + + + +Alias /pipermail/ @VARMMDIR@/archives/public/ + + Options MultiViews FollowSymLinks + AllowOverride None + Require all granted + AddDefaultCharset Off + + +# Uncomment the following line, replacing www.example.com with your server's +# name, to redirect queries to /mailman to the listinfo page (recommended). + +# RedirectMatch ^/mailman[/]*$ http://www.example.com/mailman/listinfo diff --git a/SOURCES/mailman-2.1-build.patch b/SOURCES/mailman-2.1-build.patch new file mode 100644 index 0000000..3c5d99d --- /dev/null +++ b/SOURCES/mailman-2.1-build.patch @@ -0,0 +1,715 @@ +diff --git a/Mailman/Archiver/Makefile.in b/Mailman/Archiver/Makefile.in +index 65e46cb..dc3a1c6 100644 +--- a/Mailman/Archiver/Makefile.in ++++ b/Mailman/Archiver/Makefile.in +@@ -28,7 +28,6 @@ exec_prefix= @exec_prefix@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + + DEFS= @DEFS@ +@@ -47,7 +46,7 @@ pipermail.py + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 ++DIRMODE= 2775 + EXEMODE= 755 + FILEMODE= 644 + INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +@@ -63,8 +62,6 @@ install: + $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(DESTDIR)$(PACKAGEDIR); \ + done + +-finish: +- + clean: + + distclean: +diff --git a/Mailman/Bouncers/Makefile.in b/Mailman/Bouncers/Makefile.in +index 8865a9f..8878cd9 100644 +--- a/Mailman/Bouncers/Makefile.in ++++ b/Mailman/Bouncers/Makefile.in +@@ -28,7 +28,6 @@ exec_prefix= @exec_prefix@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + + DEFS= @DEFS@ +@@ -45,7 +44,7 @@ MODULES= *.py + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 ++DIRMODE= 2775 + EXEMODE= 755 + FILEMODE= 644 + INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +@@ -61,8 +60,6 @@ install: + $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(DESTDIR)$(PACKAGEDIR); \ + done + +-finish: +- + clean: + + distclean: +diff --git a/Mailman/Cgi/Makefile.in b/Mailman/Cgi/Makefile.in +index 47929e9..e20c07e 100644 +--- a/Mailman/Cgi/Makefile.in ++++ b/Mailman/Cgi/Makefile.in +@@ -28,7 +28,6 @@ exec_prefix= @exec_prefix@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + + DEFS= @DEFS@ +@@ -47,7 +46,7 @@ CGI_MODULES= *.py + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 ++DIRMODE= 2775 + EXEMODE= 755 + FILEMODE= 644 + INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +@@ -63,8 +62,6 @@ install: + $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(DESTDIR)$(CGIDIR); \ + done + +-finish: +- + clean: + + distclean: +diff --git a/Mailman/Commands/Makefile.in b/Mailman/Commands/Makefile.in +index 645d78b..12bbc1b 100644 +--- a/Mailman/Commands/Makefile.in ++++ b/Mailman/Commands/Makefile.in +@@ -28,7 +28,6 @@ exec_prefix= @exec_prefix@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + + DEFS= @DEFS@ +@@ -45,7 +44,7 @@ MODULES= *.py + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 ++DIRMODE= 2775 + EXEMODE= 755 + FILEMODE= 644 + INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +@@ -61,8 +60,6 @@ install: + $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(DESTDIR)$(PACKAGEDIR); \ + done + +-finish: +- + clean: + + distclean: +diff --git a/Mailman/Gui/Makefile.in b/Mailman/Gui/Makefile.in +index 000441e..9273b8a 100644 +--- a/Mailman/Gui/Makefile.in ++++ b/Mailman/Gui/Makefile.in +@@ -28,7 +28,6 @@ exec_prefix= @exec_prefix@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + + DEFS= @DEFS@ +@@ -45,7 +44,7 @@ MODULES= *.py + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 ++DIRMODE= 2775 + EXEMODE= 755 + FILEMODE= 644 + INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +@@ -61,8 +60,6 @@ install: + $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(DESTDIR)$(PACKAGEDIR); \ + done + +-finish: +- + clean: + + distclean: +diff --git a/Mailman/Handlers/Makefile.in b/Mailman/Handlers/Makefile.in +index 71e5576..56ee2eb 100644 +--- a/Mailman/Handlers/Makefile.in ++++ b/Mailman/Handlers/Makefile.in +@@ -28,7 +28,6 @@ exec_prefix= @exec_prefix@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + + DEFS= @DEFS@ +@@ -45,7 +44,7 @@ MODULES= *.py + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 ++DIRMODE= 2775 + EXEMODE= 755 + FILEMODE= 644 + INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +@@ -61,8 +60,6 @@ install: + $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(DESTDIR)$(PACKAGEDIR); \ + done + +-finish: +- + clean: + + distclean: +diff --git a/Mailman/Logging/Makefile.in b/Mailman/Logging/Makefile.in +index e185775..ad5c128 100644 +--- a/Mailman/Logging/Makefile.in ++++ b/Mailman/Logging/Makefile.in +@@ -28,7 +28,6 @@ exec_prefix= @exec_prefix@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + + DEFS= @DEFS@ +@@ -45,7 +44,7 @@ MODULES= *.py + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 ++DIRMODE= 2775 + EXEMODE= 755 + FILEMODE= 644 + INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +@@ -61,8 +60,6 @@ install: + $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(DESTDIR)$(PACKAGEDIR); \ + done + +-finish: +- + clean: + + distclean: +diff --git a/Mailman/MTA/Makefile.in b/Mailman/MTA/Makefile.in +index 0338806..e1de296 100644 +--- a/Mailman/MTA/Makefile.in ++++ b/Mailman/MTA/Makefile.in +@@ -28,7 +28,6 @@ exec_prefix= @exec_prefix@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + + DEFS= @DEFS@ +@@ -45,7 +44,7 @@ MODULES= *.py + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 ++DIRMODE= 2775 + EXEMODE= 755 + FILEMODE= 644 + INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +@@ -61,8 +60,6 @@ install: + $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(DESTDIR)$(PACKAGEDIR); \ + done + +-finish: +- + clean: + + distclean: +diff --git a/Mailman/Makefile.in b/Mailman/Makefile.in +index 93c7efd..45439f0 100644 +--- a/Mailman/Makefile.in ++++ b/Mailman/Makefile.in +@@ -20,8 +20,6 @@ + + # Variables set by configure + +-VERSION= @VERSION@ +- + VPATH= @srcdir@ + srcdir= @srcdir@ + bindir= @bindir@ +@@ -30,7 +28,6 @@ exec_prefix= @exec_prefix@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + + DEFS= @DEFS@ +@@ -48,7 +45,7 @@ SUBDIRS= Cgi Logging Archiver Handlers Bouncers Queue MTA Gui Commands + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 ++DIRMODE= 2775 + EXEMODE= 755 + FILEMODE= 644 + INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +@@ -79,12 +76,6 @@ install: install-here + (cd $$d; $(MAKE) DESTDIR=$(DESTDIR) install); \ + done + +-finish: +- @for d in $(SUBDIRS); \ +- do \ +- (cd $$d; $(MAKE) DESTDIR=$(DESTDIR) finish); \ +- done +- + clean: + for d in $(SUBDIRS); \ + do \ +diff --git a/Mailman/Queue/Makefile.in b/Mailman/Queue/Makefile.in +index 179f3d9..e0e97ad 100644 +--- a/Mailman/Queue/Makefile.in ++++ b/Mailman/Queue/Makefile.in +@@ -28,7 +28,6 @@ exec_prefix= @exec_prefix@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + + DEFS= @DEFS@ +@@ -45,7 +44,7 @@ MODULES= *.py + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 ++DIRMODE= 2775 + EXEMODE= 755 + FILEMODE= 644 + INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +@@ -61,8 +60,6 @@ install: + $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(DESTDIR)$(PACKAGEDIR); \ + done + +-finish: +- + clean: + + distclean: +diff --git a/Makefile.in b/Makefile.in +index 40e04f5..eb40844 100644 +--- a/Makefile.in ++++ b/Makefile.in +@@ -61,11 +61,10 @@ SUBDIRS= bin cron misc Mailman scripts src templates messages tests + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 ++DIRMODE= 2775 + EXEMODE= 755 + FILEMODE= 644 + INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +-DIRSETGID= chmod g+s + + DATE = $(shell python -c 'import time; print time.strftime("%d-%b-%Y"),') + LANGPACK = README-I18N.en templates messages +@@ -85,14 +84,24 @@ install: doinstall update + + doinstall: $(SUBDIRS) + @echo "Creating architecture independent directories..." ++ dir=$(DESTDIR)$(prefix); \ ++ if test ! -d $$dir; then \ ++ echo "Creating directory hierarchy $$dir"; \ ++ $(INSTALL) -d -m $(DIRMODE) $$dir; \ ++ else true; \ ++ fi; ++ dir=$(DESTDIR)$(var_prefix); \ ++ if test ! -d $$dir; then \ ++ echo "Creating directory hierarchy $$dir"; \ ++ $(INSTALL) -d -m $(DIRMODE) $$dir; \ ++ else true; \ ++ fi; + @for d in $(VAR_DIRS); \ + do \ + dir=$(DESTDIR)$(var_prefix)/$$d; \ + if test ! -d $$dir; then \ + echo "Creating directory hierarchy $$dir"; \ +- $(srcdir)/mkinstalldirs $$dir; \ +- chmod $(DIRMODE) $$dir; \ +- $(DIRSETGID) $$dir; \ ++ $(INSTALL) -d -m $(DIRMODE) $$dir; \ + else true; \ + fi; \ + done +@@ -102,9 +111,7 @@ doinstall: $(SUBDIRS) + dir=$(DESTDIR)$(prefix)/$$d; \ + if test ! -d $$dir; then \ + echo "Creating directory hierarchy $$dir"; \ +- $(srcdir)/mkinstalldirs $$dir; \ +- chmod $(DIRMODE) $$dir; \ +- $(DIRSETGID) $$dir; \ ++ $(INSTALL) -d -m $(DIRMODE) $$dir; \ + else true; \ + fi; \ + done +@@ -114,9 +121,7 @@ doinstall: $(SUBDIRS) + dir=$(DESTDIR)$(exec_prefix)/$$d; \ + if test ! -d $$dir; then \ + echo "Creating directory hierarchy $$dir"; \ +- $(srcdir)/mkinstalldirs $$dir; \ +- chmod $(DIRMODE) $$dir; \ +- $(DIRSETGID) $$dir; \ ++ $(INSTALL) -d -m $(DIRMODE) $$dir; \ + else true; \ + fi; \ + done +diff --git a/bin/Makefile.in b/bin/Makefile.in +index 22c24b0..d795333 100644 +--- a/bin/Makefile.in ++++ b/bin/Makefile.in +@@ -28,7 +28,6 @@ exec_prefix= @exec_prefix@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + + DEFS= @DEFS@ +@@ -56,7 +55,7 @@ BUILDDIR= ../build/bin + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 ++DIRMODE= 2775 + EXEMODE= 755 + FILEMODE= 644 + INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +@@ -72,8 +71,6 @@ install: + $(INSTALL) -m $(EXEMODE) $(BUILDDIR)/$$f $(DESTDIR)$(SCRIPTSDIR); \ + done + +-finish: +- + clean: + + distclean: +diff --git a/cron/Makefile.in b/cron/Makefile.in +index 2a7c8fd..c14d119 100644 +--- a/cron/Makefile.in ++++ b/cron/Makefile.in +@@ -28,7 +28,6 @@ exec_prefix= @exec_prefix@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + + DEFS= @DEFS@ +@@ -68,8 +67,6 @@ install: + $(INSTALL) -m $(EXEMODE) $(BUILDDIR)/$$f $(DESTDIR)$(CRONDIR); \ + done + +-finish: +- + clean: + + distclean: +diff --git a/messages/Makefile.in b/messages/Makefile.in +index 2bbec3f..0b8b7d6 100644 +--- a/messages/Makefile.in ++++ b/messages/Makefile.in +@@ -32,7 +32,6 @@ exec_prefix= @exec_prefix@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + + DEFS= @DEFS@ +@@ -43,7 +42,6 @@ OPT= @OPT@ + CFLAGS= $(OPT) $(DEFS) + PACKAGEDIR= $(prefix)/messages + SHELL= /bin/sh +-DIRSETGID= chmod g+s + MSGFMT= @PYTHON@ ../build/bin/msgfmt.py + MSGMERGE= msgmerge + +@@ -60,7 +58,7 @@ TARGETS= $(MOFILES) + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 ++DIRMODE= 2775 + EXEMODE= 755 + FILEMODE= 644 + INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +@@ -90,9 +88,8 @@ doinstall: mofiles + do \ + dir=$(DESTDIR)$(prefix)/$$d; \ + echo "Creating language directory $$dir"; \ +- $(srcdir)/../mkinstalldirs $$dir; \ +- chmod $(DIRMODE) $$dir; \ +- $(DIRSETGID) $$dir; \ ++ $(INSTALL) -d -m $(DIRMODE) `dirname $$dir`; \ ++ $(INSTALL) -d -m $(DIRMODE) $$dir; \ + done + @for d in $(LANGUAGES); \ + do \ +@@ -113,8 +110,6 @@ doinstall: mofiles + + mofiles: $(MOFILES) + +-finish: +- + clean: + -rm -f */LC_MESSAGES/mailman.mo + +diff --git a/misc/Makefile.in b/misc/Makefile.in +index 5125cd8..af99c04 100644 +--- a/misc/Makefile.in ++++ b/misc/Makefile.in +@@ -30,7 +30,6 @@ var_prefix= @VAR_PREFIX@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + PYTHON= @PYTHON@ + +@@ -62,7 +61,7 @@ PACKAGES= $(EMAILPKG) $(JACODECSPKG) $(KOCODECSPKG) + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 ++DIRMODE= 2775 + EXEMODE= 755 + FILEMODE= 644 + DATAMODE= 664 +@@ -101,8 +100,6 @@ install-packages: + (cd $(PKGDIR)/$$p ; umask 02 ; PYTHONPATH=$(PYTHONLIBDIR) $(PYTHON) $(SETUPCMD)); \ + done + +-finish: +- + clean: + + distclean: +diff --git a/scripts/Makefile.in b/scripts/Makefile.in +index 49b82ec..cea250c 100644 +--- a/scripts/Makefile.in ++++ b/scripts/Makefile.in +@@ -28,7 +28,6 @@ exec_prefix= @exec_prefix@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + + DEFS= @DEFS@ +@@ -49,7 +48,7 @@ SCRIPTS= bounces confirm driver join leave owner post request + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 ++DIRMODE= 2775 + EXEMODE= 755 + FILEMODE= 644 + INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +@@ -68,8 +67,6 @@ install: + $(INSTALL) -m $(FILEMODE) $(srcdir)/join $(DESTDIR)$(SCRIPTSDIR)/subscribe + $(INSTALL) -m $(FILEMODE) $(srcdir)/leave $(DESTDIR)$(SCRIPTSDIR)/unsubscribe + +-finish: +- + clean: + + distclean: +diff --git a/src/Makefile.in b/src/Makefile.in +index ce52356..7ed820b 100644 +--- a/src/Makefile.in ++++ b/src/Makefile.in +@@ -28,7 +28,6 @@ bindir= @bindir@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + PYTHON= @PYTHON@ + +@@ -65,10 +64,9 @@ COMMON_FLAGS= -DPREFIX="\"$(prefix)\"" \ + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 +-EXEMODE= 755 ++DIRMODE= 2775 ++EXEMODE= 2755 + INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +-DIRSETGID= chmod g+s + + # Fixed definitions + +@@ -112,20 +110,10 @@ install: all + do \ + exe=$(DESTDIR)$(CGIDIR)/$$f$(CGIEXT); \ + $(INSTALL_PROGRAM) $$f $$exe; \ +- $(DIRSETGID) $$exe; \ + done + for f in $(MAIL_PROGS); \ + do \ + $(INSTALL_PROGRAM) $$f $(DESTDIR)$(MAILDIR); \ +- $(DIRSETGID) $(DESTDIR)$(MAILDIR)/$$f; \ +- done +- +-finish: +- -for f in $(SUID_CGI_PROGS); \ +- do \ +- exe=$(DESTDIR)$(CGIDIR)/$$f$(CGIEXT); \ +- chown $(MAILMAN_USER) $$exe; \ +- chmod u+s $$exe; \ + done + + clean: +diff --git a/templates/Makefile.in b/templates/Makefile.in +index 3dfee3a..b23d7c1 100644 +--- a/templates/Makefile.in ++++ b/templates/Makefile.in +@@ -28,7 +28,6 @@ exec_prefix= @exec_prefix@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + TRUE= @TRUE@ + +@@ -48,7 +47,7 @@ LANGUAGES= ar ast ca cs da de el en es et eu fi fr gl he hr hu ia it ja \ + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 ++DIRMODE= 2775 + EXEMODE= 755 + FILEMODE= 644 + INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +@@ -61,15 +60,13 @@ all: + install: + for d in $(LANGUAGES); \ + do \ +- $(srcdir)/../mkinstalldirs $(DESTDIR)$(TEMPLATEDIR)/$$d; \ ++ $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(TEMPLATEDIR)/$$d; \ + for f in $(srcdir)/$$d/*.html $(srcdir)/$$d/*.txt; \ + do \ + $(INSTALL) -m $(FILEMODE) $$f $(DESTDIR)$(TEMPLATEDIR)/$$d; \ + done; \ + done + +-finish: +- + clean: + + distclean: +diff --git a/tests/Makefile.in b/tests/Makefile.in +index 26d6e31..ca92425 100644 +--- a/tests/Makefile.in ++++ b/tests/Makefile.in +@@ -28,7 +28,6 @@ exec_prefix= @exec_prefix@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + + DEFS= @DEFS@ +@@ -46,7 +45,7 @@ EXECS= $(srcdir)/onebounce.py $(srcdir)/fblast.py + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 ++DIRMODE= 2775 + EXEMODE= 755 + FILEMODE= 644 + INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +@@ -71,8 +70,6 @@ install: + (cd $$d; $(MAKE) DESTDIR=$(DESTDIR) install); \ + done + +-finish: +- + clean: + + distclean: +diff --git a/tests/bounces/Makefile.in b/tests/bounces/Makefile.in +index 5798097..f7014cb 100644 +--- a/tests/bounces/Makefile.in ++++ b/tests/bounces/Makefile.in +@@ -28,7 +28,6 @@ exec_prefix= @exec_prefix@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + + DEFS= @DEFS@ +@@ -46,7 +45,7 @@ BOUNCE_FILES= $(srcdir)/*.txt + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 ++DIRMODE= 2775 + EXEMODE= 755 + FILEMODE= 644 + INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +@@ -60,8 +59,6 @@ install: + $(INSTALL) -m $(FILEMODE) $$f $(DESTDIR)$(BOUNCEDIR); \ + done + +-finish: +- + clean: + + distclean: +diff --git a/tests/msgs/Makefile.in b/tests/msgs/Makefile.in +index 736d721..edef5ae 100644 +--- a/tests/msgs/Makefile.in ++++ b/tests/msgs/Makefile.in +@@ -28,7 +28,6 @@ exec_prefix= @exec_prefix@ + DESTDIR= + + CC= @CC@ +-CHMOD= @CHMOD@ + INSTALL= @INSTALL@ + + DEFS= @DEFS@ +@@ -46,7 +45,7 @@ MSG_FILES= $(srcdir)/*.txt + # Modes for directories and executables created by the install + # process. Default to group-writable directories but + # user-only-writable for executables. +-DIRMODE= 775 ++DIRMODE= 2775 + EXEMODE= 755 + FILEMODE= 644 + INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +@@ -60,8 +59,6 @@ install: + $(INSTALL) -m $(FILEMODE) $$f $(DESTDIR)$(MSGSDIR); \ + done + +-finish: +- + clean: + + distclean: diff --git a/SOURCES/mailman-2.1-mailmanctl-status.patch b/SOURCES/mailman-2.1-mailmanctl-status.patch new file mode 100644 index 0000000..87a2d8e --- /dev/null +++ b/SOURCES/mailman-2.1-mailmanctl-status.patch @@ -0,0 +1,189 @@ +diff --git a/bin/mailmanctl b/bin/mailmanctl +index 613f909..3d59d57 100644 +--- a/bin/mailmanctl ++++ b/bin/mailmanctl +@@ -36,7 +36,7 @@ in the file data/master-qrunner.pid but you normally don't need to use this + pid directly. The `start', `stop', `restart', and `reopen' commands handle + everything for you. + +-Usage: %(PROGRAM)s [options] [ start | stop | restart | reopen ] ++Usage: %(PROGRAM)s [options] [ start | stop | restart | reopen | status ] + + Options: + +@@ -90,6 +90,9 @@ Commands: + + reopen - This will close all log files, causing them to be re-opened the + next time a message is written to them ++ ++ status - This returns a string indicating the status of the master ++ qrunner + """ + + import sys +@@ -190,6 +193,52 @@ def qrunner_state(): + return 0 + return 1 + ++def mailman_status(): ++ # return status, pid ++ # ++ # These status values match the /etc/init.d status values ++ # (at least on Red Hat), try to return equivalent status if possible ++ # status is 0 if running, ++ # status is 1 if dead but pid file exists ++ # status is 2 if dead but subsys locked ++ # status is 3 if stopped (pid returned will be 0) ++ # ++ # ++ # We want any user to be able to query the status and this presents ++ # few interesting permission problems and is why we don't use ++ # qrunner_state(). The pidfile is only readable by the mailman owner ++ # and group, however the lockfile is world readable. So we will ++ # get the master pid from the lockfile. We try to determine if the ++ # master process exists by sending it a signal. If we don't have ++ # permission to signal the process, but the process exists we'll ++ # get a EPERM error, if the process does not exist then we'll get ++ # a ESRCH error. ++ ++ try: ++ hostname, pid, tempfile = get_lock_data() ++ except IOError, e: ++ if e.errno == errno.ENOENT: ++ # Lock file didn't exist, can't be running ++ return 3, 0 ++ else: ++ raise ++ if hostname <> socket.gethostname(): ++ # not running on this host ++ return 3, 0 ++ # Find out if the process exists by calling kill with a signal 0. ++ try: ++ os.kill(pid, 0) ++ except OSError, e: ++ if e.errno == errno.ESRCH: ++ # process does not exist ++ return 1, pid ++ elif e.errno == errno.EPERM: ++ # we don't have permission signal the process but it exists ++ return 0, pid ++ else: ++ raise ++ return 0, pid ++ + + def acquire_lock_1(force): + # Be sure we can acquire the master qrunner lock. If not, it means some +@@ -337,13 +386,15 @@ def main(): + command = COMMASPACE.join(args) + usage(1, _('Bad command: %(command)s')) + ++ command = args[0].lower() ++ + if checkprivs: + check_privs() + else: +- print _('Warning! You may encounter permission problems.') ++ if command != 'status': ++ print _('Warning! You may encounter permission problems.') + + # Handle the commands +- command = args[0].lower() + if command == 'stop': + # Sent the master qrunner process a SIGINT, which is equivalent to + # giving cron/qrunner a ctrl-c or KeyboardInterrupt. This will +@@ -362,6 +413,14 @@ def main(): + if not quiet: + print _('Re-opening all log files') + kill_watcher(signal.SIGHUP) ++ elif command == 'status': ++ status, pid = mailman_status() ++ if not quiet: ++ if status == 0: ++ print _("mailman (pid %(pid)d) is running...") ++ else: ++ print _("mailman is stopped") ++ sys.exit(status) + elif command == 'start': + # First, complain loudly if there's no site list. + check_for_site_list() +diff --git a/misc/mailman.in b/misc/mailman.in +index 2f5fbc7..bce344c 100644 +--- a/misc/mailman.in ++++ b/misc/mailman.in +@@ -36,19 +36,70 @@ PYTHON=@PYTHON@ + MAILMANHOME=@prefix@ + MAILMANCTL=$MAILMANHOME/bin/mailmanctl + ++# Source function library. ++. /etc/rc.d/init.d/functions ++ ++RETVAL=0 ++prog="mailman" ++ ++function start() ++{ ++ echo -n $"Starting $prog: " ++ daemon $PYTHON $MAILMANCTL -s -q start ++ RETVAL=$? ++ [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog ++ echo ++ return $RETVAL ++} ++ ++function stop() ++{ ++ echo -n $"Shutting down $prog: " ++ daemon $PYTHON $MAILMANCTL -q stop ++ RETVAL=$? ++ [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog ++ echo ++ return $RETVAL ++} ++ ++function restart() ++{ ++ stop ++ start ++ RETVAL=$? ++ return $RETVAL ++} ++ + case "$1" in + 'start') +- #rm -f $MAILMANHOME/locks/* +- $PYTHON $MAILMANCTL -s -q start ++ start ++ RETVAL=$? + ;; + + 'stop') +- $PYTHON $MAILMANCTL -q stop ++ stop ++ RETVAL=$? + ;; + + 'restart') +- $PYTHON $MAILMANCTL -q restart ++ restart ++ RETVAL=$? ++ ;; ++ ++'condrestart') ++ $PYTHON $MAILMANCTL -q -u status ++ retval=$? ++ if [ $retval -eq 0 ] ++ then ++ restart ++ RETVAL=$? ++ fi ++ ;; ++ ++'status') ++ $PYTHON $MAILMANCTL -u status ++ RETVAL=$? + ;; + + esac +-exit 0 ++exit $RETVAL diff --git a/SOURCES/mailman-2.1.10-ctypefix.patch b/SOURCES/mailman-2.1.10-ctypefix.patch new file mode 100644 index 0000000..256309f --- /dev/null +++ b/SOURCES/mailman-2.1.10-ctypefix.patch @@ -0,0 +1,15 @@ +diff -ruN mailman-2.1.12-a/bin/withlist mailman-2.1.12-b/bin/withlist +--- mailman-2.1.12-a/bin/withlist 2009-02-23 22:23:35.000000000 +0100 ++++ mailman-2.1.12-b/bin/withlist 2009-07-28 12:19:51.000000000 +0200 +@@ -128,7 +128,10 @@ + from Mailman import Errors + from Mailman import MailList + from Mailman import Utils +-from Mailman.i18n import _ ++from Mailman import i18n ++ ++_ = i18n._ ++C_ = i18n.C_ + + try: + True, False diff --git a/SOURCES/mailman-2.1.11-cron.patch b/SOURCES/mailman-2.1.11-cron.patch new file mode 100644 index 0000000..1614a29 --- /dev/null +++ b/SOURCES/mailman-2.1.11-cron.patch @@ -0,0 +1,223 @@ +diff --git a/cron/bumpdigests b/cron/bumpdigests +index 57cc45e..4002731 100755 +--- a/cron/bumpdigests ++++ b/cron/bumpdigests +@@ -1,4 +1,4 @@ +-#! @PYTHON@ ++#! @PYTHON@ -S + # + # Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc. + # +diff --git a/cron/checkdbs b/cron/checkdbs +index e776f15..c4d8179 100755 +--- a/cron/checkdbs ++++ b/cron/checkdbs +@@ -1,4 +1,4 @@ +-#! @PYTHON@ ++#! @PYTHON@ -S + # + # Copyright (C) 1998-2011 by the Free Software Foundation, Inc. + # +diff --git a/cron/crontab.in.in b/cron/crontab.in.in +index 540dfc1..45c5022 100644 +--- a/cron/crontab.in.in ++++ b/cron/crontab.in.in +@@ -1,27 +1,50 @@ ++# ++# -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- ++# ------------------ EDIT THE CORRECT FILE ------------------------- ++# ++# This file is copied to /etc/cron.d/mailman from ++# @prefix@/cron/crontab.in when the mailman service is started via its ++# init.d script and the file /etc/cron.d/mailman is removed when the ++# service is stopped. Therefore any edits made directly to ++# /etc/cron.d/mailman will be lost anytime the mailman service ++# restarts. ++# ++# To make changes edit the master copy @prefix@/cron/crontab.in and then ++# restart the service to pick up the changes (/sbin/service mailman restart). ++# ++# The reason this is done this way is because the mailman cron jobs ++# should only be invoked if the mailman service is enabled and not ++# just as a consequence of installing the rpm as was the case ++# previously. The file /etc/cron.d/mailman cannot simply be linked to ++# the master copy in @prefix@/cron because for security reasons cron ++# will not process crontab files that are links or writeable by ++# anybody else but root, thus the file must be copied into /etc/cron.d ++# with the right ownership and permissions. ++# + # At 8AM every day, mail reminders to admins as to pending requests. + # They are less likely to ignore these reminders if they're mailed + # early in the morning, but of course, this is local time... ;) +-0 8 * * * @PYTHON@ -S @prefix@/cron/checkdbs ++0 8 * * * @MAILMAN_USER@ @prefix@/cron/checkdbs + # + # At 9AM, send notifications to disabled members that are due to be + # reminded to re-enable their accounts. +-0 9 * * * @PYTHON@ -S @prefix@/cron/disabled ++0 9 * * * @MAILMAN_USER@ @prefix@/cron/disabled + # + # Noon, mail digests for lists that do periodic as well as threshhold delivery. +-0 12 * * * @PYTHON@ -S @prefix@/cron/senddigests ++0 12 * * * @MAILMAN_USER@ @prefix@/cron/senddigests + # + # 5 AM on the first of each month, mail out password reminders. +-0 5 1 * * @PYTHON@ -S @prefix@/cron/mailpasswds ++0 5 1 * * @MAILMAN_USER@ @prefix@/cron/mailpasswds + # + # Every 5 mins, try to gate news to mail. You can comment this one out + # if you don't want to allow gating, or don't have any going on right now, + # or want to exclusively use a callback strategy instead of polling. +-0,5,10,15,20,25,30,35,40,45,50,55 * * * * @PYTHON@ -S @prefix@/cron/gate_news ++0,5,10,15,20,25,30,35,40,45,50,55 * * * * @MAILMAN_USER@ @prefix@/cron/gate_news + # + # At 3:27am every night, regenerate the gzip'd archive file. Only + # turn this on if the internal archiver is used and + # GZIP_ARCHIVE_TXT_FILES is false in mm_cfg.py +-27 3 * * * @PYTHON@ -S @prefix@/cron/nightly_gzip ++27 3 * * * @MAILMAN_USER@ @prefix@/cron/nightly_gzip + # + # At 4:30AM daily, cull old entries from the 'bad' and 'shunt' queues. +-30 4 * * * @PYTHON@ -S @prefix@/cron/cull_bad_shunt ++30 4 * * * @MAILMAN_USER@ @prefix@/cron/cull_bad_shunt +diff --git a/cron/disabled b/cron/disabled +index ac62582..971563d 100755 +--- a/cron/disabled ++++ b/cron/disabled +@@ -1,4 +1,4 @@ +-#! @PYTHON@ ++#! @PYTHON@ -S + # + # Copyright (C) 2001-2007 by the Free Software Foundation, Inc. + # +diff --git a/cron/gate_news b/cron/gate_news +index c66c09e..247c834 100755 +--- a/cron/gate_news ++++ b/cron/gate_news +@@ -1,4 +1,4 @@ +-#! @PYTHON@ ++#! @PYTHON@ -S + # + # Copyright (C) 1998-2011 by the Free Software Foundation, Inc. + # +diff --git a/cron/mailpasswds b/cron/mailpasswds +index 5745265..4d0cd3a 100755 +--- a/cron/mailpasswds ++++ b/cron/mailpasswds +@@ -1,4 +1,4 @@ +-#! @PYTHON@ ++#! @PYTHON@ -S + # + # Copyright (C) 1998-2003 by the Free Software Foundation, Inc. + # +diff --git a/cron/nightly_gzip b/cron/nightly_gzip +index 0a0f4e3..7dba3b1 100755 +--- a/cron/nightly_gzip ++++ b/cron/nightly_gzip +@@ -1,4 +1,4 @@ +-#! @PYTHON@ ++#! @PYTHON@ -S + # + # Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc. + # +diff --git a/cron/senddigests b/cron/senddigests +index edf27a2..c64adc1 100755 +--- a/cron/senddigests ++++ b/cron/senddigests +@@ -1,4 +1,4 @@ +-#! @PYTHON@ ++#! @PYTHON@ -S + # + # Copyright (C) 1998-2007 by the Free Software Foundation, Inc. + # +diff --git a/misc/mailman.in b/misc/mailman.in +index bce344c..7129f14 100644 +--- a/misc/mailman.in ++++ b/misc/mailman.in +@@ -24,18 +24,48 @@ + # On Debian, type "update-rc.d mailman defaults" + # On RedHat, and derivatives, install with "chkconfig --add mailman" + # +-# chkconfig: 2345 98 12 ++# chkconfig: - 98 12 + # description: Mailman is the GNU Mailing List Manager, a program that \ + # manages electronic mail discussion groups. For more \ + # on GNU Mailman see http://www.list.org + # processname: mailmanctl + # config: @prefix@/Mailman/mm_cfg.py +-# pidfile: @prefix@/data/master-qrunner.pid ++# pidfile: @PID_DIR@/master-qrunner.pid + + PYTHON=@PYTHON@ + MAILMANHOME=@prefix@ + MAILMANCTL=$MAILMANHOME/bin/mailmanctl + ++# We used to install the mailman cron jobs when the mailman rpm was ++# installed, irrespective of whether mailman was actually being ++# run. Although the cron jobs didn't create any problems if someone ++# wasn't running mailman some users complained about the cron log file ++# filling up, resource usage, and power consumption since systems ++# wouldn't really idle. It really only makes sense to run the mailman ++# cron jobs if the mailman service is turned on and not just merely ++# having the rpm installed. This init.d script is an obvious place to ++# install or remove the cron jobs based on the service being enabled ++# or not. ++ ++SRC_CRON_SCRIPT=$MAILMANHOME/cron/crontab.in ++DST_CRON_SCRIPT=/etc/cron.d/mailman ++ ++function InstallCron() ++{ ++ install -m644 -o root -g root $SRC_CRON_SCRIPT $DST_CRON_SCRIPT ++} ++ ++function RemoveCron() ++{ ++cat > $DST_CRON_SCRIPT <[^>]*)>|\(expanded from: [^>)]*)>?\)')), ++ _c(r'^ *(\(expanded from: )?[^\s@]+@[^\s@>]+?)>?\)?\s*$')), + # robanal.demon.co.uk + (_c('this message was created automatically by mail delivery software'), + _c('original message follows'), +diff --git a/Mailman/Bouncers/Yahoo.py b/Mailman/Bouncers/Yahoo.py +index b3edf4f..08ede54 100644 +--- a/Mailman/Bouncers/Yahoo.py ++++ b/Mailman/Bouncers/Yahoo.py +@@ -20,9 +20,15 @@ import re + import email + from email.Utils import parseaddr + +-tcre = re.compile(r'message\s+from\s+yahoo\.\S+', re.IGNORECASE) ++tcre = (re.compile(r'message\s+from\s+yahoo\.\S+', re.IGNORECASE), ++ re.compile(r'Sorry, we were unable to deliver your message to ' ++ r'the following address(\(es\))?\.', ++ re.IGNORECASE), ++ ) + acre = re.compile(r'<(?P[^>]*)>:') +-ecre = re.compile(r'--- Original message follows') ++ecre = (re.compile(r'--- Original message follows'), ++ re.compile(r'--- Below this line is a copy of the message'), ++ ) + + + +@@ -36,18 +42,26 @@ def process(msg): + # simple state machine + # 0 == nothing seen + # 1 == tag line seen ++ # 2 == end line seen + state = 0 + for line in email.Iterators.body_line_iterator(msg): + line = line.strip() +- if state == 0 and tcre.match(line): +- state = 1 ++ if state == 0: ++ for cre in tcre: ++ if cre.match(line): ++ state = 1 ++ break + elif state == 1: + mo = acre.match(line) + if mo: + addrs.append(mo.group('addr')) + continue +- mo = ecre.match(line) +- if mo: +- # we're at the end of the error response +- break ++ for cre in ecre: ++ mo = cre.match(line) ++ if mo: ++ # we're at the end of the error response ++ state = 2 ++ break ++ elif state == 2: ++ break + return addrs +diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in +old mode 100644 +new mode 100755 +index 4fe63db..8e42f54 +--- a/Mailman/Defaults.py.in ++++ b/Mailman/Defaults.py.in +@@ -505,6 +505,7 @@ GLOBAL_PIPELINE = [ + # (outgoing) path, finally leaving the message in the outgoing queue. + 'AfterDelivery', + 'Acknowledge', ++ 'WrapMessage', + 'ToOutgoing', + ] + +@@ -914,6 +915,29 @@ DEFAULT_DEFAULT_MEMBER_MODERATION = No + # moderators? + DEFAULT_FORWARD_AUTO_DISCARDS = Yes + ++# Shall dmarc_moderation_action be applied to messages From: domains with ++# a DMARC policy of quarantine as well as reject? This sets the default for ++# the list setting that controls it. ++DEFAULT_DMARC_QUARANTINE_MODERATION_ACTION = Yes ++ ++# Default action for posts whose From: address domain has a DMARC policy of ++# reject or quarantine. See DEFAULT_FROM_IS_LIST below. Whatever is set as ++# the default here precludes the list owner from setting a lower value. ++# 0 = Accept ++# 1 = Munge From ++# 2 = Wrap Message ++# 3 = Reject ++# 4 = Discard ++DEFAULT_DMARC_MODERATION_ACTION = 0 ++ ++# Parameters for DMARC DNS lookups. If you are seeing 'DNSException: ++# Unable to query DMARC policy ...' entries in your error log, you may need ++# to adjust these. ++# The time to wait for a response from a name server before timeout. ++DMARC_RESOLVER_TIMEOUT = seconds(3) ++# The total time to spend trying to get an answer to the question. ++DMARC_RESOLVER_LIFETIME = seconds(5) ++ + # What shold happen to non-member posts which are do not match explicit + # non-member actions? + # 0 = Accept +@@ -950,6 +974,25 @@ DEFAULT_SEND_WELCOME_MSG = Yes + # Send goodbye messages to unsubscribed members? + DEFAULT_SEND_GOODBYE_MSG = Yes + ++# Some list posts and mail to the -owner address may contain DomainKey or ++# DomainKeys Identified Mail (DKIM) signature headers . ++# Various list transformations to the message such as adding a list header or ++# footer or scrubbing attachments or even reply-to munging can break these ++# signatures. It is generally felt that these signatures have value, even if ++# broken and even if the outgoing message is resigned. However, some sites ++# may wish to remove these headers by setting this to Yes. ++REMOVE_DKIM_HEADERS = No ++ ++# The following is a three way setting. It sets the default for the list's ++# from_is_list policy which is applied to all posts except those for which a ++# dmarc_moderation_action other than accept applies. ++# 0 -> Do not rewrite the From: or wrap the message. ++# 1 -> Rewrite the From: header of posts replacing the posters address with ++# that of the list. Also see REMOVE_DKIM_HEADERS above. ++# 2 -> Do not modify the From: of the message, but wrap the message in an outer ++# message From the list address. ++DEFAULT_FROM_IS_LIST = 0 ++ + # Wipe sender information, and make it look like the list-admin + # address sends all messages + DEFAULT_ANONYMOUS_LIST = No +diff --git a/Mailman/Gui/General.py b/Mailman/Gui/General.py +index 8271a30..05dc9ba 100644 +--- a/Mailman/Gui/General.py ++++ b/Mailman/Gui/General.py +@@ -153,6 +153,72 @@ class General(GUIBase): + (listname %%05d) -> (listname 00123) + """)), + ++ ('from_is_list', mm_cfg.Radio, ++ (_('No'), _('Munge From'), _('Wrap Message')), 0, ++ _("""Replace the From: header address with the list's posting ++ address to mitigate issues stemming from the original From: ++ domain's DMARC or similar policies."""), ++ _("""Several protocols now in wide use attempt to ensure that use ++ of the domain in the author's address (ie, in the From: header ++ field) is authorized by that domain. These protocols may be ++ incompatible with common list features such as footers, causing ++ participating email services to bounce list traffic merely ++ because of the address in the From: field. This has resulted ++ in members being unsubscribed despite being perfectly able to ++ receive mail. ++

++ The following actions are applied to all list messages when ++ selected here. To apply these actions only to messages where the ++ domain in the From: header is determined to use such a protocol, ++ see the ++ dmarc_moderation_action settings under Privacy options... ++ -> Sender filters. ++

Settings:

++

++
No
++
Do nothing special. This is appropriate for anonymous lists. ++ It is appropriate for dedicated announcement lists, unless the ++ From: address of authorized posters might be in a domain with a ++ DMARC or similar policy. It is also appropriate if you choose to ++ use dmarc_moderation_action other than Accept for this list.
++
Munge From
++
This action replaces the poster's address in the From: header ++ with the list's posting address and adds the poster's address to ++ the addresses in the original Reply-To: header.
++
Wrap Message
++
Just wrap the message in an outer message with the From: ++ header containing the list's posting address and with the original ++ From: address added to the addresses in the original Reply-To: ++ header and with Content-Type: message/rfc822. This is effectively ++ a one message MIME format digest.
++
++

The transformations for anonymous_list are applied before ++ any of these actions. It is not useful to apply actions other ++ than No to an anonymous list, and if you do so, the result may ++ be surprising. ++

The Reply-To: header munging actions below interact with these ++ actions as follows: ++

first_strip_reply_to = Yes will remove all the incoming ++ Reply-To: addresses but will still add the poster's address to ++ Reply-To: for all three settings of reply_goes_to_list which ++ respectively will result in just the poster's address, the ++ poster's address and the list posting address or the poster's ++ address and the explicit reply_to_address in the outgoing ++ Reply-To: header. If first_strip_reply_to = No the poster's ++ address in the original From: header, if not already included in ++ the Reply-To:, will be added to any existing Reply-To: ++ address(es). ++

These actions, whether selected here or via ++ dmarc_moderation_action, do not apply to messages in digests ++ or archives or sent to usenet via the Mail<->News gateways. ++

If ++ dmarc_moderation_action applies to this message with an ++ action other than Accept, that action rather than this is ++ applied""")), ++ + ('anonymous_list', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _("""Hide the sender of a message, replacing it with the list + address (Removes From, Sender and Reply-To fields)""")), +diff --git a/Mailman/Gui/NonDigest.py b/Mailman/Gui/NonDigest.py +old mode 100644 +new mode 100755 +diff --git a/Mailman/Gui/Privacy.py b/Mailman/Gui/Privacy.py +index 75eff2b..5d717bb 100644 +--- a/Mailman/Gui/Privacy.py ++++ b/Mailman/Gui/Privacy.py +@@ -158,6 +158,11 @@ class Privacy(GUIBase): + ] + + adminurl = mlist.GetScriptURL('admin', absolute=1) ++ ++ if mlist.dmarc_quarantine_moderation_action: ++ quarantine = _('/Quarantine') ++ else: ++ quarantine = '' + sender_rtn = [ + _("""When a message is posted to the list, a series of + moderation steps are taken to decide whether a moderator must +@@ -235,6 +240,59 @@ class Privacy(GUIBase): + >rejection notice to + be sent to moderated members who post to this list.""")), + ++ ('dmarc_moderation_action', mm_cfg.Radio, ++ (_('Accept'), _('Munge From'), _('Wrap Message'), _('Reject'), ++ _('Discard')), 0, ++ _("""Action to take when anyone posts to the ++ list from a domain with a DMARC Reject%(quarantine)s Policy."""), ++ ++ _("""

  • Munge From -- applies the from_is_list Munge From ++ transformation to these messages. ++ ++

  • Wrap Message -- applies the from_is_list Wrap ++ Message transformation to these messages. ++ ++

  • Reject -- this automatically rejects the message by ++ sending a bounce notice to the post's author. The text of the ++ bounce notice can be configured by you. ++ ++

  • Discard -- this simply discards the message, with ++ no notice sent to the post's author. ++
++ ++

This setting takes precedence over the from_is_list setting ++ if the message is From: an affected domain and the setting is ++ other than Accept.""")), ++ ++ ('dmarc_quarantine_moderation_action', mm_cfg.Radio, ++ (_('No'), _('Yes')), 0, ++ _("""Shall the above dmarc_moderation_action apply to messages ++ From: domains with DMARC p=quarantine as well as p=reject"""), ++ ++ _("""

  • No -- this applies dmarc_moderation_action to ++ only those posts From: a domain with DMARC p=reject. This is ++ appropriate if you are concerned about bounced messages, but ++ want to apply dmarc_moderation_action to as few messages as ++ possible. ++

  • Yes -- this applies dmarc_moderation_action to ++ posts From: a domain with DMARC p=reject or p=quarantine. ++

If a message is From: a domain with DMARC p=quarantine ++ and dmarc_moderation_action is not applied (this set to No) ++ the message will likely not bounce, but will be delivered to ++ recipients' spam folders or other hard to find places.""")), ++ ++ ('dmarc_moderation_notice', mm_cfg.Text, (10, WIDTH), 1, ++ _("""Text to include in any ++ rejection notice to ++ be sent to anyone who posts to this list from a domain ++ with a DMARC Reject%(quarantine)s Policy.""")), ++ + _('Non-member filters'), + + ('accept_these_nonmembers', mm_cfg.EmailListEx, (10, WIDTH), 1, +@@ -399,7 +457,7 @@ class Privacy(GUIBase): + case, each rule is matched in turn, with processing stopped after + the first match. + +- Note that headers are collected from all the attachments ++ Note that headers are collected from all the attachments + (except for the mailman administrivia message) and + matched against the regular expressions. With this feature, + you can effectively sort out messages with dangerous file +@@ -442,6 +500,11 @@ class Privacy(GUIBase): + # an option. + if property == 'subscribe_policy' and not mm_cfg.ALLOW_OPEN_SUBSCRIBE: + val += 1 ++ if (property == 'dmarc_moderation_action' and ++ val < mm_cfg.DEFAULT_DMARC_MODERATION_ACTION): ++ doc.addError(_("""dmarc_moderation_action must be >= the configured ++ default value.""")) ++ val = mm_cfg.DEFAULT_DMARC_MODERATION_ACTION + setattr(mlist, property, val) + + # We need to handle the header_filter_rules widgets specially, but +diff --git a/Mailman/Handlers/AvoidDuplicates.py b/Mailman/Handlers/AvoidDuplicates.py +index 038034c..549d8e7 100644 +--- a/Mailman/Handlers/AvoidDuplicates.py ++++ b/Mailman/Handlers/AvoidDuplicates.py +@@ -24,6 +24,7 @@ warning header, or pass it through, depending on the user's preferences. + + from email.Utils import getaddresses, formataddr + from Mailman import mm_cfg ++from Mailman.Handlers.CookHeaders import change_header + + COMMASPACE = ', ' + +@@ -95,6 +96,10 @@ def process(mlist, msg, msgdata): + # Set the new list of recipients + msgdata['recips'] = newrecips + # RFC 2822 specifies zero or one CC header +- del msg['cc'] + if ccaddrs: +- msg['Cc'] = COMMASPACE.join([formataddr(i) for i in ccaddrs.values()]) ++ change_header('Cc', ++ COMMASPACE.join([formataddr(i) for i in ccaddrs.values()]), ++ mlist, msg, msgdata) ++ else: ++ del msg['cc'] ++ +diff --git a/Mailman/Handlers/CookHeaders.py b/Mailman/Handlers/CookHeaders.py +old mode 100644 +new mode 100755 +index 8e7e668..c556967 +--- a/Mailman/Handlers/CookHeaders.py ++++ b/Mailman/Handlers/CookHeaders.py +@@ -64,13 +64,23 @@ def uheader(mlist, s, header_name=None, continuation_ws='\t', maxlinelen=None): + charset = 'us-ascii' + return Header(s, charset, maxlinelen, header_name, continuation_ws) + ++def change_header(name, value, mlist, msg, msgdata, delete=True, repl=True): ++ if ((msgdata.get('from_is_list') == 2 or ++ (msgdata.get('from_is_list') == 0 and mlist.from_is_list == 2)) and ++ not msgdata.get('_fasttrack') ++ ) or name.lower() in ('from', 'reply-to'): ++ msgdata.setdefault('add_header', {})[name] = value ++ elif repl or not msg.has_key(name): ++ if delete: ++ del msg[name] ++ msg[name] = value ++ + + + def process(mlist, msg, msgdata): + # Set the "X-Ack: no" header if noack flag is set. + if msgdata.get('noack'): +- del msg['x-ack'] +- msg['X-Ack'] = 'no' ++ change_header('X-Ack', 'no', mlist, msg, msgdata) + # Because we're going to modify various important headers in the email + # message, we want to save some of the information in the msgdata + # dictionary for later. Specifically, the sender header will get waxed, +@@ -87,7 +97,8 @@ def process(mlist, msg, msgdata): + pass + # Mark message so we know we've been here, but leave any existing + # X-BeenThere's intact. +- msg['X-BeenThere'] = mlist.GetListEmail() ++ change_header('X-BeenThere', mlist.GetListEmail(), ++ mlist, msg, msgdata, delete=False) + # Add Precedence: and other useful headers. None of these are standard + # and finding information on some of them are fairly difficult. Some are + # just common practice, and we'll add more here as they become necessary. +@@ -101,12 +112,31 @@ def process(mlist, msg, msgdata): + # known exploits in a particular version of Mailman and we know a site is + # using such an old version, they may be vulnerable. It's too easy to + # edit the code to add a configuration variable to handle this. +- if not msg.has_key('x-mailman-version'): +- msg['X-Mailman-Version'] = mm_cfg.VERSION ++ change_header('X-Mailman-Version', mm_cfg.VERSION, ++ mlist, msg, msgdata, repl=False) + # We set "Precedence: list" because this is the recommendation from the + # sendmail docs, the most authoritative source of this header's semantics. +- if not msg.has_key('precedence'): +- msg['Precedence'] = 'list' ++ change_header('Precedence', 'list', ++ mlist, msg, msgdata, repl=False) ++ # Do we change the from so the list takes ownership of the email ++ if (msgdata.get('from_is_list') or mlist.from_is_list) and not fasttrack: ++ realname, email = parseaddr(msg['from']) ++ if not realname: ++ if mlist.isMember(email): ++ realname = mlist.getMemberName(email) or email ++ else: ++ realname = email ++ # Remove domain from realname if it looks like an email address ++ realname = re.sub(r'@([^ .]+\.)+[^ .]+$', '---', realname) ++ # Remember the original From: here for adding to Reply-To: below. ++ o_from = parseaddr(msg['from']) ++ change_header('From', ++ formataddr(('%s via %s' % (realname, mlist.real_name), ++ mlist.GetListEmail())), ++ mlist, msg, msgdata) ++ else: ++ # Use this as a flag ++ o_from = None + # Reply-To: munging. Do not do this if the message is "fast tracked", + # meaning it is internally crafted and delivered to a specific user. BAW: + # Yuck, I really hate this feature but I've caved under the sheer pressure +@@ -136,18 +166,23 @@ def process(mlist, msg, msgdata): + orig = msg.get_all('reply-to', []) + for pair in getaddresses(orig): + add(pair) ++ # We also need to put the old From: in Reply-To: in all cases. ++ if o_from: ++ add(o_from) + # Set Reply-To: header to point back to this list. Add this last + # because some folks think that some MUAs make it easier to delete + # addresses from the right than from the left. + if mlist.reply_goes_to_list == 1: + i18ndesc = uheader(mlist, mlist.description, 'Reply-To') + add((str(i18ndesc), mlist.GetListEmail())) +- del msg['reply-to'] + # Don't put Reply-To: back if there's nothing to add! + if new: + # Preserve order +- msg['Reply-To'] = COMMASPACE.join( +- [formataddr(pair) for pair in new]) ++ change_header('Reply-To', ++ COMMASPACE.join([formataddr(pair) for pair in new]), ++ mlist, msg, msgdata) ++ else: ++ del msg['reply-to'] + # The To field normally contains the list posting address. However + # when messages are fully personalized, that header will get + # overwritten with the address of the recipient. We need to get the +@@ -158,18 +193,31 @@ def process(mlist, msg, msgdata): + # above code? + # Also skip Cc if this is an anonymous list as list posting address + # is already in From and Reply-To in this case. +- if mlist.personalize == 2 and mlist.reply_goes_to_list <> 1 \ +- and not mlist.anonymous_list: ++ # We do add the Cc in cases where From: header munging is being done ++ # because even though the list address is in From:, the Reply-To: ++ # poster will override it. Brain dead MUAs may then address the list ++ # twice on a 'reply all', but reasonable MUAs should do the right ++ # thing. ++ if (mlist.personalize == 2 and mlist.reply_goes_to_list <> 1 and ++ not mlist.anonymous_list): + # Watch out for existing Cc headers, merge, and remove dups. Note + # that RFC 2822 says only zero or one Cc header is allowed. + new = [] + d = {} +- for pair in getaddresses(msg.get_all('cc', [])): +- add(pair) ++ # AvoidDuplicates may have set a new Cc: in msgdata.add_header, ++ # so check that. ++ if (msgdata.has_key('add_header') and ++ msgdata['add_header'].has_key('Cc')): ++ for pair in getaddresses([msgdata['add_header']['Cc']]): ++ add(pair) ++ else: ++ for pair in getaddresses(msg.get_all('cc', [])): ++ add(pair) + i18ndesc = uheader(mlist, mlist.description, 'Cc') + add((str(i18ndesc), mlist.GetListEmail())) +- del msg['Cc'] +- msg['Cc'] = COMMASPACE.join([formataddr(pair) for pair in new]) ++ change_header('Cc', ++ COMMASPACE.join([formataddr(pair) for pair in new]), ++ mlist, msg, msgdata) + # Add list-specific headers as defined in RFC 2369 and RFC 2919, but only + # if the message is being crafted for a specific list (e.g. not for the + # password reminders). +@@ -191,8 +239,7 @@ def process(mlist, msg, msgdata): + # without desc we need to ensure the MUST brackets + listid_h = '<%s>' % listid + # We always add a List-ID: header. +- del msg['list-id'] +- msg['List-Id'] = listid_h ++ change_header('List-Id', listid_h, mlist, msg, msgdata) + # For internally crafted messages, we also add a (nonstandard), + # "X-List-Administrivia: yes" header. For all others (i.e. those coming + # from list posts), we add a bunch of other RFC 2369 headers. +@@ -219,13 +266,12 @@ def process(mlist, msg, msgdata): + # First we delete any pre-existing headers because the RFC permits only + # one copy of each, and we want to be sure it's ours. + for h, v in headers.items(): +- del msg[h] + # Wrap these lines if they are too long. 78 character width probably + # shouldn't be hardcoded, but is at least text-MUA friendly. The + # adding of 2 is for the colon-space separator. + if len(h) + 2 + len(v) > 78: + v = CONTINUATION.join(v.split(', ')) +- msg[h] = v ++ change_header(h, v, mlist, msg, msgdata) + + + +@@ -302,8 +348,7 @@ def prefix_subject(mlist, msg, msgdata): + h = u' '.join([prefix, subject]) + h = h.encode('us-ascii') + h = uheader(mlist, h, 'Subject', continuation_ws=ws) +- del msg['subject'] +- msg['Subject'] = h ++ change_header('Subject', h, mlist, msg, msgdata) + ss = u' '.join([recolon, subject]) + ss = ss.encode('us-ascii') + ss = uheader(mlist, ss, 'Subject', continuation_ws=ws) +@@ -321,8 +366,7 @@ def prefix_subject(mlist, msg, msgdata): + # TK: Subject is concatenated and unicode string. + subject = subject.encode(cset, 'replace') + h.append(subject, cset) +- del msg['subject'] +- msg['Subject'] = h ++ change_header('Subject', h, mlist, msg, msgdata) + ss = uheader(mlist, recolon, 'Subject', continuation_ws=ws) + ss.append(subject, cset) + msgdata['stripped_subject'] = ss +diff --git a/Mailman/Handlers/Moderate.py b/Mailman/Handlers/Moderate.py +index a362d96..2f1f38f 100644 +--- a/Mailman/Handlers/Moderate.py ++++ b/Mailman/Handlers/Moderate.py +@@ -21,6 +21,7 @@ + import re + from email.MIMEMessage import MIMEMessage + from email.MIMEText import MIMEText ++from email.Utils import parseaddr + + from Mailman import mm_cfg + from Mailman import Utils +@@ -47,9 +48,34 @@ class ModeratedMemberPost(Hold.ModeratedPost): + + + def process(mlist, msg, msgdata): +- if msgdata.get('approved') or msgdata.get('fromusenet'): ++ if msgdata.get('approved'): + return +- # First of all, is the poster a member or not? ++ # Before anything else, check DMARC if necessary. ++ msgdata['from_is_list'] = 0 ++ dn, addr = parseaddr(msg.get('from')) ++ if addr and mlist.dmarc_moderation_action > 0: ++ if Utils.IsDMARCProhibited(mlist, addr): ++ # Note that for dmarc_moderation_action, 0 = Accept, ++ # 1 = Munge, 2 = Wrap, 3 = Reject, 4 = Discard ++ if mlist.dmarc_moderation_action == 1: ++ msgdata['from_is_list'] = 1 ++ elif mlist.dmarc_moderation_action == 2: ++ msgdata['from_is_list'] = 2 ++ elif mlist.dmarc_moderation_action == 3: ++ # Reject ++ text = mlist.dmarc_moderation_notice ++ if text: ++ text = Utils.wrap(text) ++ else: ++ text = Utils.wrap(_( ++"""You are not allowed to post to this mailing list From: a domain which ++publishes a DMARC policy of reject or quarantine, and your message has been ++automatically rejected. If you think that your messages are being rejected in ++error, contact the mailing list owner at %(listowner)s.""")) ++ raise Errors.RejectMessage, text ++ elif mlist.dmarc_moderation_action == 4: ++ raise Errors.DiscardMessage ++ # Then, is the poster a member or not? + for sender in msg.get_senders(): + if mlist.isMember(sender): + break +@@ -105,7 +131,7 @@ def process(mlist, msg, msgdata): + # moderation configuration variables. Handle by way of generic non-member + # action. + assert 0 <= mlist.generic_nonmember_action <= 4 +- if mlist.generic_nonmember_action == 0: ++ if mlist.generic_nonmember_action == 0 or msgdata.get('fromusenet'): + # Accept + return + elif mlist.generic_nonmember_action == 1: +diff --git a/Mailman/Handlers/Tagger.py b/Mailman/Handlers/Tagger.py +index 0d3ce49..2117290 100644 +--- a/Mailman/Handlers/Tagger.py ++++ b/Mailman/Handlers/Tagger.py +@@ -24,6 +24,7 @@ import email.Iterators + + from Mailman import Utils + from Mailman.Logging.Syslog import syslog ++from Mailman.Handlers.CookHeaders import change_header + + CRNL = '\r\n' + EMPTYSTRING = '' +@@ -60,8 +61,9 @@ def process(mlist, msg, msgdata): + break + if hits: + msgdata['topichits'] = hits.keys() +- msg['X-Topics'] = NLTAB.join(hits.keys()) +- ++ change_header('X-Topics', NLTAB.join(hits.keys()), ++ mlist, msg, msgdata, delete=False) ++ + + + def scanbody(msg, numlines=None): +diff --git a/Mailman/Handlers/WrapMessage.py b/Mailman/Handlers/WrapMessage.py +new file mode 100644 +index 0000000..9678f6f +--- /dev/null ++++ b/Mailman/Handlers/WrapMessage.py +@@ -0,0 +1,72 @@ ++# Copyright (C) 2013-2014 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 ++# as published by the Free Software Foundation; either version 2 ++# of the License, or (at your option) any later version. ++# ++# This program is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++# GNU General Public License for more details. ++# ++# You should have received a copy of the GNU General Public License ++# along with this program; if not, write to the Free Software ++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, ++# USA. ++ ++"""Wrap the message in an outer message/rfc822 part and transfer/add ++some headers from the original. ++ ++Also, in the case of Munge From, replace the From: and Reply-To: in the ++original message. ++""" ++ ++import copy ++ ++from Mailman import mm_cfg ++from Mailman.Utils import unique_message_id ++from Mailman.Message import Message ++ ++# Headers from the original that we want to keep in the wrapper. ++KEEPERS = ('to', ++ 'in-reply-to', ++ 'references', ++ 'x-mailman-approved-at', ++ ) ++ ++ ++ ++def process(mlist, msg, msgdata): ++ # This is the negation of we're wrapping because dmarc_moderation_action ++ # is wrap this message or from_is_list applies and is wrap. ++ if not (msgdata.get('from_is_list') == 2 or ++ (mlist.from_is_list == 2 and msgdata.get('from_is_list') == 0)): ++ # Now see if we need to add a From: and/or Reply-To: without wrapping. ++ a_h = msgdata.get('add_header') ++ if a_h: ++ if a_h.get('From'): ++ del msg['from'] ++ msg['From'] = a_h.get('From') ++ if a_h.get('Reply-To'): ++ del msg['reply-to'] ++ msg['Reply-To'] = a_h.get('Reply-To') ++ return ++ ++ # There are various headers in msg that we don't want, so we basically ++ # make a copy of the msg, then delete almost everything and set/copy ++ # what we want. ++ omsg = copy.deepcopy(msg) ++ for key in msg.keys(): ++ if key.lower() not in KEEPERS: ++ del msg[key] ++ msg['MIME-Version'] = '1.0' ++ msg['Content-Type'] = 'message/rfc822' ++ msg['Content-Disposition'] = 'inline' ++ msg['Message-ID'] = unique_message_id(mlist) ++ # Add the headers from CookHeaders. ++ for k, v in msgdata['add_header'].items(): ++ msg[k] = v ++ # And set the payload. ++ msg.set_payload(omsg.as_string()) ++ +diff --git a/Mailman/MailList.py b/Mailman/MailList.py +old mode 100644 +new mode 100755 +index 6083fb1..f948b69 +--- a/Mailman/MailList.py ++++ b/Mailman/MailList.py +@@ -346,6 +346,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin, + self.bounce_matching_headers = \ + mm_cfg.DEFAULT_BOUNCE_MATCHING_HEADERS + self.header_filter_rules = [] ++ self.from_is_list = mm_cfg.DEFAULT_FROM_IS_LIST + self.anonymous_list = mm_cfg.DEFAULT_ANONYMOUS_LIST + internalname = self.internal_name() + self.real_name = internalname[0].upper() + internalname[1:] +@@ -386,6 +387,10 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin, + # 2==Discard + self.member_moderation_action = 0 + self.member_moderation_notice = '' ++ self.dmarc_moderation_action = mm_cfg.DEFAULT_DMARC_MODERATION_ACTION ++ self.dmarc_quarantine_moderation_action = ( ++ mm_cfg.DEFAULT_DMARC_QUARANTINE_MODERATION_ACTION) ++ self.dmarc_moderation_notice = '' + self.accept_these_nonmembers = [] + self.hold_these_nonmembers = [] + self.reject_these_nonmembers = [] +@@ -712,7 +717,14 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin, + def CheckVersion(self, stored_state): + """Auto-update schema if necessary.""" + if self.data_version >= mm_cfg.DATA_FILE_VERSION: +- return ++ # Some lists could have been created by newer Mailman version than ++ # this one. We are adding just few variables, so check for these ++ # variables explicitely. ++ if (hasattr(self, "from_is_list") ++ and hasattr(self, "dmarc_moderation_action") ++ and hasattr(self, "dmarc_moderation_notice") ++ and hasattr(self, "dmarc_quarantine_moderation_action")): ++ return + # Initialize any new variables + self.InitVars() + # Then reload the database (but don't recurse). Force a reload even +@@ -1025,7 +1030,8 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin, + # And send an acknowledgement to the user... + if userack: + self.SendUnsubscribeAck(emailaddr, userlang) +- # ...and to the administrator ++ # ...and to the administrator in the correct language. (LP: #1308655) ++ i18n.set_language(self.preferred_language) + if admin_notif: + realname = self.real_name + subject = _('%(realname)s unsubscribe notification') +diff --git a/Mailman/Message.py b/Mailman/Message.py +index 84e4aa2..13e7ff2 100644 +--- a/Mailman/Message.py ++++ b/Mailman/Message.py +@@ -61,6 +61,43 @@ class Generator(email.Generator.Generator): + + + ++class Generator(email.Generator.Generator): ++ """Generates output from a Message object tree, keeping signatures. ++ ++ Headers will by default _not_ be folded in attachments. ++ """ ++ def __init__(self, outfp, mangle_from_=True, ++ maxheaderlen=78, children_maxheaderlen=0): ++ email.Generator.Generator.__init__(self, outfp, ++ mangle_from_=mangle_from_, maxheaderlen=maxheaderlen) ++ self.__children_maxheaderlen = children_maxheaderlen ++ ++ def clone(self, fp): ++ """Clone this generator with maxheaderlen set for children""" ++ return self.__class__(fp, self._mangle_from_, ++ self.__children_maxheaderlen, self.__children_maxheaderlen) ++ ++ # This is the _handle_message method with the fix for bug 7970. ++ def _handle_message(self, msg): ++ s = StringIO() ++ g = self.clone(s) ++ # The payload of a message/rfc822 part should be a multipart sequence ++ # of length 1. The zeroth element of the list should be the Message ++ # object for the subpart. Extract that object, stringify it, and ++ # write it out. ++ # Except, it turns out, when it's a string instead, which happens when ++ # and only when HeaderParser is used on a message of mime type ++ # message/rfc822. Such messages are generated by, for example, ++ # Groupwise when forwarding unadorned messages. (Issue 7970.) So ++ # in that case we just emit the string body. ++ payload = msg.get_payload() ++ if isinstance(payload, list): ++ g.flatten(msg.get_payload(0), unixfrom=False) ++ payload = s.getvalue() ++ self._fp.write(payload) ++ ++ ++ + class Message(email.Message.Message): + def __init__(self): + # We need a version number so that we can optimize __setstate__() +@@ -243,6 +280,20 @@ class Message(email.Message.Message): + return fp.getvalue() + + ++ def as_string(self, unixfrom=False, mangle_from_=True): ++ """Return entire formatted message as a string using ++ Mailman.Message.Generator. ++ ++ Operates like email.Message.Message.as_string, only ++ using Mailman's Message.Generator class. Only the top headers will ++ get folded. ++ """ ++ fp = StringIO() ++ g = Generator(fp, mangle_from_=mangle_from_) ++ g.flatten(self, unixfrom=unixfrom) ++ return fp.getvalue() ++ ++ + + class UserNotification(Message): + """Class for internally crafted messages.""" +diff --git a/Mailman/Utils.py b/Mailman/Utils.py +index c8275df..8021942 100644 +--- a/Mailman/Utils.py ++++ b/Mailman/Utils.py +@@ -71,6 +71,14 @@ except NameError: + True = 1 + False = 0 + ++try: ++ import dns.resolver ++ import dns.rdatatype ++ from dns.exception import DNSException ++ dns_resolver = True ++except ImportError: ++ dns_resolver = False ++ + EMPTYSTRING = '' + UEMPTYSTRING = u'' + NL = '\n' +@@ -1047,3 +1055,91 @@ def suspiciousHTML(html): + else: + return False + ++ ++ ++ ++# This takes an email address, and returns True if DMARC policy is p=reject ++# or possibly quarantine. ++def IsDMARCProhibited(mlist, email): ++ if not dns_resolver: ++ return False ++ ++ email = email.lower() ++ at_sign = email.find('@') ++ if at_sign < 1: ++ return False ++ dmarc_domain = '_dmarc.' + email[at_sign+1:] ++ ++ try: ++ resolver = dns.resolver.Resolver() ++ resolver.timeout = float(mm_cfg.DMARC_RESOLVER_TIMEOUT) ++ resolver.lifetime = float(mm_cfg.DMARC_RESOLVER_LIFETIME) ++ txt_recs = resolver.query(dmarc_domain, dns.rdatatype.TXT) ++ except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): ++ return False ++ except DNSException, e: ++ syslog('error', ++ 'DNSException: Unable to query DMARC policy for %s (%s). %s', ++ email, dmarc_domain, e.__class__) ++ return False ++ else: ++# people are already being dumb, don't trust them to provide honest DNS ++# where the answer section only contains what was asked for, nor to include ++# CNAMEs before the values they point to. ++ full_record = "" ++ results_by_name = {} ++ cnames = {} ++ want_names = set([dmarc_domain + '.']) ++ for txt_rec in txt_recs.response.answer: ++ if txt_rec.rdtype == dns.rdatatype.CNAME: ++ cnames[txt_rec.name.to_text()] = ( ++ txt_rec.items[0].target.to_text()) ++ if txt_rec.rdtype != dns.rdatatype.TXT: ++ continue ++ results_by_name.setdefault(txt_rec.name.to_text(), []).append( ++ "".join(txt_rec.items[0].strings)) ++ expands = list(want_names) ++ seen = set(expands) ++ while expands: ++ item = expands.pop(0) ++ if item in cnames: ++ if cnames[item] in seen: ++ continue # cname loop ++ expands.append(cnames[item]) ++ seen.add(cnames[item]) ++ want_names.add(cnames[item]) ++ want_names.discard(item) ++ ++ if len(want_names) != 1: ++ syslog('error', ++ """multiple DMARC entries in results for %s, ++ processing each to be strict""", ++ dmarc_domain) ++ for name in want_names: ++ if name not in results_by_name: ++ continue ++ dmarcs = filter(lambda n: n.startswith('v=DMARC1;'), ++ results_by_name[name]) ++ if len(dmarcs) == 0: ++ return False ++ if len(dmarcs) > 1: ++ syslog('error', ++ """RRset of TXT records for %s has %d v=DMARC1 entries; ++ testing them all""", ++ dmarc_domain, len(dmarc)) ++ for entry in dmarcs: ++ if re.search(r'\bp=reject\b', entry, re.IGNORECASE): ++ syslog('vette', ++ 'DMARC lookup for %s (%s) found p=reject in %s = %s', ++ email, dmarc_domain, name, entry) ++ return True ++ ++ if (mlist.dmarc_quarantine_moderation_action and ++ re.search(r'\bp=quarantine\b', entry, re.IGNORECASE)): ++ syslog('vette', ++ 'DMARC lookup for %s (%s) found p=quarantine in %s = %s', ++ email, dmarc_domain, name, entry) ++ return True ++ ++ return False ++ +diff --git a/Mailman/Version.py b/Mailman/Version.py +index 05e6500..af4a2df 100644 +--- a/Mailman/Version.py ++++ b/Mailman/Version.py +@@ -37,7 +37,7 @@ HEX_VERSION = ((MAJOR_REV << 24) | (MINOR_REV << 16) | (MICRO_REV << 8) | + (REL_LEVEL << 4) | (REL_SERIAL << 0)) + + # config.pck schema version number +-DATA_FILE_VERSION = 100 ++DATA_FILE_VERSION = 101 + + # qfile/*.db schema version number + QFILE_SCHEMA_VERSION = 3 +diff --git a/Mailman/versions.py b/Mailman/versions.py +old mode 100644 +new mode 100755 +index 81fafd5..138e770 +--- a/Mailman/versions.py ++++ b/Mailman/versions.py +@@ -313,6 +313,9 @@ def UpdateOldVars(l, stored_state): + pass + else: + l.digest_members[k] = 0 ++ # from_is_list was called author_is_list in 2.1.16rc2 (only). ++ PreferStored('author_is_list', 'from_is_list', ++ mm_cfg.DEFAULT_FROM_IS_LIST) + + + +@@ -383,6 +386,11 @@ def NewVars(l): + # the current GUI description model. So, 0==Hold, 1==Reject, 2==Discard + add_only_if_missing('member_moderation_action', 0) + add_only_if_missing('member_moderation_notice', '') ++ add_only_if_missing('dmarc_moderation_action', ++ mm_cfg.DEFAULT_DMARC_MODERATION_ACTION) ++ add_only_if_missing('dmarc_quarantine_moderation_action', ++ mm_cfg.DEFAULT_DMARC_QUARANTINE_MODERATION_ACTION) ++ add_only_if_missing('dmarc_moderation_notice', '') + add_only_if_missing('new_member_options', + mm_cfg.DEFAULT_NEW_MEMBER_OPTIONS) + # Emergency moderation flag +diff --git a/contrib/majordomo2mailman.pl b/contrib/majordomo2mailman.pl +index c874862..770dc57 100644 +--- a/contrib/majordomo2mailman.pl ++++ b/contrib/majordomo2mailman.pl +@@ -480,6 +480,7 @@ sub init_defaultmmconf { + 'max_num_recipients', "10", + 'forbidden_posters', "[]", + 'bounce_matching_headers', "\"\"\"\n\"\"\"\n", ++ 'from_is_list', "0", + 'anonymous_list', "0", + 'nondigestable', "1", + 'digestable', "1", diff --git a/SOURCES/mailman-2.1.12-init-not-on.patch b/SOURCES/mailman-2.1.12-init-not-on.patch new file mode 100644 index 0000000..226f814 --- /dev/null +++ b/SOURCES/mailman-2.1.12-init-not-on.patch @@ -0,0 +1,13 @@ +diff -up mailman-2.1.12/misc/mailman.in.not-on mailman-2.1.12/misc/mailman.in +--- mailman-2.1.12/misc/mailman.in.not-on 2009-12-22 13:09:58.000000000 +0100 ++++ mailman-2.1.12/misc/mailman.in 2009-12-22 13:10:28.000000000 +0100 +@@ -36,8 +36,7 @@ + # Required-Start: $local_fs $remote_fs $network $named + # Should-Start: httpd + # Required-Stop: $local_fs $remote_fs $network +-# Default-Start: 3 4 5 +-# Default-Stop: 0 1 6 ++# Default-Stop: 0 1 3 4 5 6 + # Short-Description: start and stop Mailman + # Description: Mailman is the GNU mailing list manager. + ### END INIT INFO diff --git a/SOURCES/mailman-2.1.12-initcleanup.patch b/SOURCES/mailman-2.1.12-initcleanup.patch new file mode 100644 index 0000000..707a5a5 --- /dev/null +++ b/SOURCES/mailman-2.1.12-initcleanup.patch @@ -0,0 +1,65 @@ +diff -up mailman-2.1.12/misc/mailman.in.initcleanup mailman-2.1.12/misc/mailman.in +--- mailman-2.1.12/misc/mailman.in.initcleanup 2009-10-05 09:09:35.000000000 -0400 ++++ mailman-2.1.12/misc/mailman.in 2009-10-05 17:53:56.000000000 -0400 +@@ -91,6 +91,8 @@ function start() + then + touch /var/lock/subsys/$prog + InstallCron ++ else ++ RETVAL=6 + fi + echo + return $RETVAL +@@ -98,6 +100,8 @@ function start() + + function stop() + { ++ if [ -f /var/lock/subsys/$prog ] ++ then + echo -n $"Shutting down $prog: " + mailman-update-cfg + daemon $MAILMANCTL -q stop +@@ -108,6 +112,10 @@ function stop() + RemoveCron + fi + echo ++ else ++ echo $"$prog already stopped." ++ RETVAL=0 ++ fi + return $RETVAL + } + +@@ -135,7 +143,7 @@ case "$1" in + RETVAL=$? + ;; + +-'condrestart') ++'condrestart'|'try-restart') + $MAILMANCTL -q -u status + retval=$? + if [ $retval -eq 0 ] +@@ -146,13 +154,20 @@ case "$1" in + ;; + + 'status') +- $MAILMANCTL -u status ++ output=$($MAILMANCTL -u status) + RETVAL=$? ++ if [ $RETVAL -eq 3 -a -f /var/lock/subsys/$prog ] ++ then ++ echo $"$prog dead but subsys locked" ++ RETVAL=2 ++ else ++ echo $output ++ fi + ;; + + *) +- echo $"Usage: $prog {start|stop|restart|force-reload|condrestart|status}" +- RETVAL=3 ++ echo $"Usage: $prog {start|stop|restart|force-reload|condrestart|try-restart|status}" ++ RETVAL=2 + ;; + + esac diff --git a/SOURCES/mailman-2.1.12-mmcfg.patch b/SOURCES/mailman-2.1.12-mmcfg.patch new file mode 100644 index 0000000..c68e4a4 --- /dev/null +++ b/SOURCES/mailman-2.1.12-mmcfg.patch @@ -0,0 +1,19 @@ +diff -ruN mailman-2.1.12-a/misc/mailman.in mailman-2.1.12-b/misc/mailman.in +--- mailman-2.1.12-a/misc/mailman.in 2009-07-28 12:19:53.000000000 +0200 ++++ mailman-2.1.12-b/misc/mailman.in 2009-07-28 12:19:55.000000000 +0200 +@@ -84,6 +84,7 @@ + function start() + { + echo -n $"Starting $prog: " ++ mailman-update-cfg + daemon $MAILMANCTL -s -q start + RETVAL=$? + if [ $RETVAL -eq 0 ] +@@ -98,6 +99,7 @@ + function stop() + { + echo -n $"Shutting down $prog: " ++ mailman-update-cfg + daemon $MAILMANCTL -q stop + RETVAL=$? + if [ $RETVAL -eq 0 ] diff --git a/SOURCES/mailman-2.1.12-multimail.patch b/SOURCES/mailman-2.1.12-multimail.patch new file mode 100644 index 0000000..5264a77 --- /dev/null +++ b/SOURCES/mailman-2.1.12-multimail.patch @@ -0,0 +1,387 @@ +diff -ruN mailman-2.1.12-a/configure.in mailman-2.1.12-b/configure.in +--- mailman-2.1.12-a/configure.in 2009-02-23 22:23:35.000000000 +0100 ++++ mailman-2.1.12-b/configure.in 2009-07-28 12:19:47.000000000 +0200 +@@ -249,26 +249,101 @@ + fi + + # new macro for finding group names +-AC_DEFUN([MM_FIND_GROUP_NAME], [ ++# returns a comma separated list of quoted group names ++# the list is returned in the same order as specified with any duplicates removed ++# the filter flag must be "yes" or "no", e.g. this is permcheck ++# "no" ==> none existing groups are not filtered out ++# "yes" ==> only those groups that are in the group database are included ++# in the list ++AC_DEFUN(MM_FIND_GROUP_LIST, [ + # $1 == variable name +-# $2 == user id to check for ++# $2 == white space separated list of groups to check, ++# list may contain mix of id's and names ++# $3 == filter, if == 'yes' then remove any non-existing groups + AC_SUBST($1) + changequote(,) + if test -z "$$1" + then + cat > conftest.py < conftest.py < conftest.py <gr_name) == 0) break; ++ } ++ ++ if (i >= numgroups) { ++ char *groupset = NULL; ++ size_t size = 0; ++ ++ for (i = 0; i < numgroups; i++) { ++ size += strlen(parentgroups[i]) + 2; ++ } ++ ++ groupset = malloc(size); ++ ++ if (groupset) { ++ groupset[0] = 0; ++ for (i = 0; i < numgroups; i++) { ++ strcat(groupset, parentgroups[i]); ++ if (i < numgroups-1) strcat(groupset, ", "); ++ } ++ } + +- if (strcmp(parentgroup, mygroup->gr_name)) + fatal(ident, GROUP_MISMATCH, +- "Group mismatch error. Mailman expected the %s\n" +- "wrapper script to be executed as group \"%s\", but\n" +- "the system's %s server executed the %s script as\n" +- "group \"%s\". Try tweaking the %s server to run the\n" +- "script as group \"%s\", or re-run configure, \n" +- "providing the command line option `%s=%s'.", +- wrapper, parentgroup, server, wrapper, mygroup->gr_name, +- server, parentgroup, option, mygroup->gr_name); ++ "Group mismatch error. Mailman expected the %s wrapper script to be\n" ++ "executed as one of the following groups:\n" ++ "[%s],\n" ++ "but the system's %s server executed the %s script as group: \"%s\".\n" ++ "Try tweaking the %s server to run the script as one of these groups:\n" ++ "[%s],\n" ++ "or re-run configure providing the command line option:\n" ++ "'%s=%s'.", ++ wrapper, groupset, server, wrapper, mygroup->gr_name, ++ server, groupset, option, mygroup->gr_name); ++ } + } + + +diff -ruN mailman-2.1.12-a/src/common.h mailman-2.1.12-b/src/common.h +--- mailman-2.1.12-a/src/common.h 2009-02-23 22:23:35.000000000 +0100 ++++ mailman-2.1.12-b/src/common.h 2009-07-28 12:19:47.000000000 +0200 +@@ -33,7 +33,7 @@ + #define GID_T GETGROUPS_T + + extern void fatal(const char*, int, char*, ...); +-extern void check_caller(const char*, const char*); ++extern void check_caller(const char* ident, const char**, size_t); + extern int run_script(const char*, int, char**, char**); + + /* Global variable used as a flag. */ +@@ -51,7 +51,7 @@ + #define MAIL_USAGE_ERROR 5 + #define MAIL_ILLEGAL_COMMAND 6 + #define ADDALIAS_USAGE_ERROR 7 +-#define GROUP_NAME_NOT_FOUND 8 ++#define GROUP_ID_NOT_FOUND 8 + + + /* +diff -ruN mailman-2.1.12-a/src/mail-wrapper.c mailman-2.1.12-b/src/mail-wrapper.c +--- mailman-2.1.12-a/src/mail-wrapper.c 2009-02-23 22:23:35.000000000 +0100 ++++ mailman-2.1.12-b/src/mail-wrapper.c 2009-07-28 12:19:47.000000000 +0200 +@@ -23,9 +23,9 @@ + /* Group name that your mail programs run as. See your mail server's + * documentation for details. + */ +-#define LEGAL_PARENT_GROUP MAIL_GROUP ++#define LEGAL_PARENT_GROUPS MAIL_GROUP + +-const char* parentgroup = LEGAL_PARENT_GROUP; ++const char* parentgroups[] = {LEGAL_PARENT_GROUPS}; + const char* logident = "Mailman mail-wrapper"; + + +@@ -74,7 +74,7 @@ + fatal(logident, MAIL_ILLEGAL_COMMAND, + "Illegal command: %s", argv[1]); + +- check_caller(logident, parentgroup); ++ check_caller(logident, parentgroups, sizeof(parentgroups) / sizeof(parentgroups[0])); + + /* If we got here, everything must be OK */ + status = run_script(argv[1], argc, argv, env); +diff -ruN mailman-2.1.12-a/src/Makefile.in mailman-2.1.12-b/src/Makefile.in +--- mailman-2.1.12-a/src/Makefile.in 2009-02-23 22:23:35.000000000 +0100 ++++ mailman-2.1.12-b/src/Makefile.in 2009-07-28 12:19:47.000000000 +0200 +@@ -49,9 +49,9 @@ + + SHELL= /bin/sh + +-MAIL_FLAGS= -DMAIL_GROUP="\"$(MAIL_GROUP)\"" ++MAIL_FLAGS= -DMAIL_GROUP='$(MAIL_GROUP)' + +-CGI_FLAGS= -DCGI_GROUP="\"$(CGI_GROUP)\"" ++CGI_FLAGS= -DCGI_GROUP='$(CGI_GROUP)' + + HELPFUL= -DHELPFUL + diff --git a/SOURCES/mailman-2.1.12-newlist-ja.patch b/SOURCES/mailman-2.1.12-newlist-ja.patch new file mode 100644 index 0000000..4d9eea0 --- /dev/null +++ b/SOURCES/mailman-2.1.12-newlist-ja.patch @@ -0,0 +1,13 @@ +diff --git a/bin/newlist b/bin/newlist +index 3a3fd95..1d72cd5 100755 +--- a/bin/newlist ++++ b/bin/newlist +@@ -247,7 +248,7 @@ def main(): + try: + msg = Message.UserNotification( + owner_mail, siteowner, +- C_('Your new mailing list: %(listname)s'), ++ _('Your new mailing list: %(listname)s'), + text, mlist.preferred_language) + msg.send(mlist) + finally: diff --git a/SOURCES/mailman-2.1.13-FHS.patch b/SOURCES/mailman-2.1.13-FHS.patch new file mode 100644 index 0000000..ebe5478 --- /dev/null +++ b/SOURCES/mailman-2.1.13-FHS.patch @@ -0,0 +1,243 @@ +diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in +index fb3f4f3..7dde965 100644 +--- a/Mailman/Defaults.py.in ++++ b/Mailman/Defaults.py.in +@@ -1431,20 +1431,22 @@ AuthListPoster = 6 # List poster (Approved: header in posts only) + + # Useful directories + LIST_DATA_DIR = os.path.join(VAR_PREFIX, 'lists') +-LOG_DIR = os.path.join(VAR_PREFIX, 'logs') +-LOCK_DIR = os.path.join(VAR_PREFIX, 'locks') ++LOG_DIR = '@LOG_DIR@' ++LOCK_DIR = '@LOCK_DIR@' ++CONFIG_DIR = '@CONFIG_DIR@' + DATA_DIR = os.path.join(VAR_PREFIX, 'data') ++PID_DIR = '@PID_DIR@' + SPAM_DIR = os.path.join(VAR_PREFIX, 'spam') + WRAPPER_DIR = os.path.join(EXEC_PREFIX, 'mail') + BIN_DIR = os.path.join(PREFIX, 'bin') + SCRIPTS_DIR = os.path.join(PREFIX, 'scripts') +-TEMPLATE_DIR = os.path.join(PREFIX, 'templates') ++TEMPLATE_DIR = '@TEMPLATE_DIR@' + MESSAGES_DIR = os.path.join(PREFIX, 'messages') + PUBLIC_ARCHIVE_FILE_DIR = os.path.join(VAR_PREFIX, 'archives', 'public') + PRIVATE_ARCHIVE_FILE_DIR = os.path.join(VAR_PREFIX, 'archives', 'private') + + # Directories used by the qrunner subsystem +-QUEUE_DIR = os.path.join(VAR_PREFIX, 'qfiles') ++QUEUE_DIR = '@QUEUE_DIR@' + INQUEUE_DIR = os.path.join(QUEUE_DIR, 'in') + OUTQUEUE_DIR = os.path.join(QUEUE_DIR, 'out') + CMDQUEUE_DIR = os.path.join(QUEUE_DIR, 'commands') +@@ -1458,9 +1460,9 @@ RETRYQUEUE_DIR = os.path.join(QUEUE_DIR, 'retry') + MAILDIR_DIR = os.path.join(QUEUE_DIR, 'maildir') + + # Other useful files +-PIDFILE = os.path.join(DATA_DIR, 'master-qrunner.pid') +-SITE_PW_FILE = os.path.join(DATA_DIR, 'adm.pw') +-LISTCREATOR_PW_FILE = os.path.join(DATA_DIR, 'creator.pw') ++PIDFILE = os.path.join(PID_DIR, 'master-qrunner.pid') ++SITE_PW_FILE = os.path.join(CONFIG_DIR, 'adm.pw') ++LISTCREATOR_PW_FILE = os.path.join(CONFIG_DIR, 'creator.pw') + + # Import a bunch of version numbers + from Version import * +diff --git a/Mailman/MTA/Postfix.py b/Mailman/MTA/Postfix.py +index 801ddc0..8506b9b 100644 +--- a/Mailman/MTA/Postfix.py ++++ b/Mailman/MTA/Postfix.py +@@ -32,8 +32,8 @@ from Mailman.MTA.Utils import makealiases + from Mailman.Logging.Syslog import syslog + + LOCKFILE = os.path.join(mm_cfg.LOCK_DIR, 'creator') +-ALIASFILE = os.path.join(mm_cfg.DATA_DIR, 'aliases') +-VIRTFILE = os.path.join(mm_cfg.DATA_DIR, 'virtual-mailman') ++ALIASFILE = os.path.join(mm_cfg.CONFIG_DIR, 'aliases') ++VIRTFILE = os.path.join(mm_cfg.CONFIG_DIR, 'virtual-mailman') + + try: + True, False +diff --git a/Makefile.in b/Makefile.in +index eb40844..289aeee 100644 +--- a/Makefile.in ++++ b/Makefile.in +@@ -28,6 +28,12 @@ bindir= @bindir@ + prefix= @prefix@ + exec_prefix= @exec_prefix@ + var_prefix= @VAR_PREFIX@ ++configdir= @CONFIG_DIR@ ++lockdir= @LOCK_DIR@ ++logdir= @LOG_DIR@ ++piddir= @PID_DIR@ ++queuedir= @QUEUE_DIR@ ++templatedir= @TEMPLATE_DIR@ + DESTDIR= + + CC= @CC@ +@@ -41,8 +47,11 @@ DEFS= @DEFS@ + OPT= @OPT@ + CFLAGS= @CFLAGS@ $(OPT) $(DEFS) + ++FHS_DIRS= \ ++ ${configdir} ${lockdir} ${logdir} ${piddir} ${queuedir} ${templatedir} ++ + VAR_DIRS= \ +- logs archives lists locks data spam qfiles \ ++ archives lists data spam \ + archives/private archives/public + + ARCH_INDEP_DIRS= \ +@@ -105,6 +114,15 @@ doinstall: $(SUBDIRS) + else true; \ + fi; \ + done ++ @for d in $(FHS_DIRS); \ ++ do \ ++ dir=$(DESTDIR)/$$d; \ ++ if test ! -d $$dir; then \ ++ echo "Creating directory $$dir"; \ ++ $(INSTALL) -d -m $(DIRMODE) $$dir; \ ++ else true; \ ++ fi; \ ++ done + chmod o-r $(DESTDIR)$(var_prefix)/archives/private + @for d in $(ARCH_INDEP_DIRS); \ + do \ +diff --git a/bin/check_perms b/bin/check_perms +index 137ebfb..1f45f84 100755 +--- a/bin/check_perms ++++ b/bin/check_perms +@@ -183,7 +183,8 @@ def checkall(): + print _('checking mode for %(prefix)s') + dirs = {} + for d in (mm_cfg.PREFIX, mm_cfg.EXEC_PREFIX, mm_cfg.VAR_PREFIX, +- mm_cfg.LOG_DIR): ++ mm_cfg.CONFIG_DIR, mm_cfg.DATA_DIR, mm_cfg.LOCK_DIR, ++ mm_cfg.LOG_DIR, mm_cfg.QUEUE_DIR, mm_cfg.PID_DIR): + dirs[d] = True + for d in dirs.keys(): + try: +diff --git a/configure.in b/configure.in +index f2eb1c0..c01129c 100644 +--- a/configure.in ++++ b/configure.in +@@ -305,6 +305,72 @@ else + prefixcheck=$VAR_PREFIX + fi + ++# Get the configuration file directory ++AC_SUBST(CONFIG_DIR) ++AC_MSG_CHECKING(for --with-config-dir) ++AC_ARG_WITH(config-dir, dnl ++[ --with-config-dir specify directory for configuration data other than [VAR_]PREFIX/data]) ++case "$with_config_dir" in ++ yes|no|"") CONFIG_DIR="$VAR_PREFIX/data";; ++ *) CONFIG_DIR=$with_config_dir;; ++esac ++AC_MSG_RESULT($CONFIG_DIR) ++ ++# Get the lock directory ++AC_SUBST(LOCK_DIR) ++AC_MSG_CHECKING(for --with-lock-dir) ++AC_ARG_WITH(lock-dir, dnl ++[ --with-lock-dir specify directory for lock files other than [VAR_]PREFIX/locks]) ++case "$with_lock_dir" in ++ yes|no|"") LOCK_DIR="$VAR_PREFIX/locks";; ++ *) LOCK_DIR=$with_lock_dir;; ++esac ++AC_MSG_RESULT($LOCK_DIR) ++ ++# Get the log directory ++AC_SUBST(LOG_DIR) ++AC_MSG_CHECKING(for --with-log-dir) ++AC_ARG_WITH(log-dir, dnl ++[ --with-log-dir specify directory for log files other than [VAR_]PREFIX/logs]) ++case "$with_log_dir" in ++ yes|no|"") LOG_DIR="$VAR_PREFIX/logs";; ++ *) LOG_DIR=$with_log_dir;; ++esac ++AC_MSG_RESULT($LOG_DIR) ++ ++# Get the pid directory ++AC_SUBST(PID_DIR) ++AC_MSG_CHECKING(for --with-pid-dir) ++AC_ARG_WITH(pid-dir, dnl ++[ --with-pid-dir specify directory for the pid file other than [VAR_]PREFIX/data]) ++case "$with_pid_dir" in ++ yes|no|"") PID_DIR="$VAR_PREFIX/data";; ++ *) PID_DIR=$with_pid_dir;; ++esac ++AC_MSG_RESULT($PID_DIR) ++ ++# Get the queue directory ++AC_SUBST(QUEUE_DIR) ++AC_MSG_CHECKING(for --with-queue-dir) ++AC_ARG_WITH(queue-dir, dnl ++[ --with-queue-dir specify directory for queue files other than [VAR_]PREFIX/qfiles]) ++case "$with_queue_dir" in ++ yes|no|"") QUEUE_DIR="$VAR_PREFIX/qfiles";; ++ *) QUEUE_DIR=$with_queue_dir;; ++esac ++AC_MSG_RESULT($QUEUE_DIR) ++ ++# Get the template directory ++AC_SUBST(TEMPLATE_DIR) ++AC_MSG_CHECKING(for --with-template-dir) ++AC_ARG_WITH(template-dir, dnl ++[ --with-template-dir specify directory for template files other than [VAR_]PREFIX/templates]) ++case "$with_template_dir" in ++ yes|no|"") TEMPLATE_DIR="$VAR_PREFIX/templates";; ++ *) TEMPLATE_DIR=$with_template_dir;; ++esac ++AC_MSG_RESULT($TEMPLATE_DIR) ++ + # new macro for finding group names + # returns a comma separated list of quoted group names + # the list is returned in the same order as specified with any duplicates removed +diff --git a/misc/Makefile.in b/misc/Makefile.in +index af99c04..df50a06 100644 +--- a/misc/Makefile.in ++++ b/misc/Makefile.in +@@ -27,6 +27,12 @@ bindir= @bindir@ + prefix= @prefix@ + exec_prefix= @exec_prefix@ + var_prefix= @VAR_PREFIX@ ++configdir= @CONFIG_DIR@ ++lockdir= @LOCK_DIR@ ++logdir= @LOG_DIR@ ++piddir= @PID_DIR@ ++queuedir= @QUEUE_DIR@ ++MAILMAN_GROUP= @MAILMAN_GROUP@ + DESTDIR= + + CC= @CC@ +@@ -87,7 +93,7 @@ install-other: + $(INSTALL) -m $(FILEMODE) paths.py $$dir; \ + done + $(INSTALL) -m $(EXEMODE) mailman $(DESTDIR)$(SCRIPTSDIR) +- $(INSTALL) -m $(FILEMODE) sitelist.cfg $(DESTDIR)$(DATADIR) ++ $(INSTALL) -m $(FILEMODE) sitelist.cfg $(DESTDIR)$(configdir) + + install-packages: + if [ -z "$(EMAILPKG)" -a -d $(DESTDIR)$(PYTHONLIBDIR)/email ] ; \ +diff --git a/templates/Makefile.in b/templates/Makefile.in +index b23d7c1..16930a2 100644 +--- a/templates/Makefile.in ++++ b/templates/Makefile.in +@@ -25,6 +25,7 @@ srcdir= @srcdir@ + bindir= @bindir@ + prefix= @prefix@ + exec_prefix= @exec_prefix@ ++template_dir= @TEMPLATE_DIR@ + DESTDIR= + + CC= @CC@ +@@ -37,7 +38,7 @@ DEFS= @DEFS@ + + OPT= @OPT@ + CFLAGS= $(OPT) $(DEFS) +-TEMPLATEDIR= $(prefix)/templates ++TEMPLATEDIR= $(template_dir) + + SHELL= /bin/sh + diff --git a/SOURCES/mailman-2.1.13-archive-reply.patch b/SOURCES/mailman-2.1.13-archive-reply.patch new file mode 100644 index 0000000..7e1779b --- /dev/null +++ b/SOURCES/mailman-2.1.13-archive-reply.patch @@ -0,0 +1,21 @@ +diff -up mailman-2.1.13/templates/en/article.html.archive-reply mailman-2.1.13/templates/en/article.html +--- mailman-2.1.13/templates/en/article.html.archive-reply 2009-12-22 19:00:43.000000000 +0100 ++++ mailman-2.1.13/templates/en/article.html 2010-03-25 13:50:02.000000000 +0100 +@@ -4,7 +4,7 @@ + %(title)s + + +- ++ + +