From 0778735e66cfc25ff24886bbd8faf0c76a9b1bc9 Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Tue, 12 Dec 2017 10:48:30 -0800 Subject: [PATCH] Add multi-site support This adds optional support for creating multiple sites which appear as independent mailman installations, though they all rely on the underlying operating-system provided install. Story: 2001382 Task: 6091 Depends-On: Ic92726dc341af5802ad803d239bd547ef5068043 Change-Id: I3a31465882ec95d822d590045216ec751c7cd22e --- lib/puppet/provider/mailman_list/mailman.rb | 132 ++++++++++++++++++++ lib/puppet/type/mailman_list.rb | 30 +++++ manifests/init.pp | 79 +++++++----- manifests/site.pp | 93 ++++++++++++++ templates/mailman.init.erb | 116 +++++++++++++++++ templates/mailman_multihost.vhost.erb | 62 +++++++++ templates/mm_cfg_multihost.py.erb | 40 ++++++ templates/mm_site_cfg.py.erb | 119 ++++++++++++++++++ 8 files changed, 637 insertions(+), 34 deletions(-) create mode 100644 lib/puppet/provider/mailman_list/mailman.rb create mode 100644 lib/puppet/type/mailman_list.rb create mode 100644 manifests/site.pp create mode 100644 templates/mailman.init.erb create mode 100644 templates/mailman_multihost.vhost.erb create mode 100644 templates/mm_cfg_multihost.py.erb create mode 100644 templates/mm_site_cfg.py.erb diff --git a/lib/puppet/provider/mailman_list/mailman.rb b/lib/puppet/provider/mailman_list/mailman.rb new file mode 100644 index 0000000..cfd07aa --- /dev/null +++ b/lib/puppet/provider/mailman_list/mailman.rb @@ -0,0 +1,132 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# Copyright (C) 2017 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Puppet maillist provider for mailman mailing lists. +# Based on the 'mailman' package provider in puppet 2.7, except this one +# does not muck with aliases. + +require 'puppet/provider/parsedfile' + +Puppet::Type.type(:mailman_list).provide(:mailman) do + + defaultfor :kernel => 'Linux' + + if [ "CentOS", "RedHat", "Fedora" ].any? { |os| Facter.value(:operatingsystem) == os } + commands :list_lists => "/usr/lib/mailman/bin/list_lists", :rmlist => "/usr/lib/mailman/bin/rmlist", :newlist => "/usr/lib/mailman/bin/newlist" + commands :mailman => "/usr/lib/mailman/mail/mailman" + else + # This probably won't work for non-Debian installs, but this path is sure not to be in the PATH. + commands :list_lists => "list_lists", :rmlist => "rmlist", :newlist => "newlist" + commands :mailman => "/var/lib/mailman/mail/mailman" + end + + mk_resource_methods + + # Return a list of existing mailman instances. + def self.instances + ret = [] + Dir.entries('/srv/mailman').each do |entry| + if (entry == '.' || entry == '..') then next end + path = File.join('/srv/mailman', entry) + if !File.directory?(path) then next end + if !File.exists?(File.join(path, 'lists')) then next end + ENV['MAILMAN_SITE_DIR'] = path + list_lists('--bare').split("\n").each do |line| + ret << new(:ensure => :present, :name => line.strip+'@'+entry) + end + end + return ret + end + + def self.prefetch(lists) + instances.each do |prov| + if list = lists[prov.name] || lists[prov.name.downcase] + list.provider = prov + end + end + end + + def setenv + r = self.name.split('@') + ENV['MAILMAN_SITE_DIR'] = File.join('/srv/mailman', r[1]) + print "Mailman install dir", ENV['MAILMAN_SITE_DIR'], "\n" + return r[0] + end + + def create + print "create ", self.name, "\n" + name = setenv + args = [] + if val = @resource[:mailserver] + args << "--emailhost" << val + end + if val = @resource[:webserver] + args << "--urlhost" << val + end + + args << name + if val = @resource[:admin] + args << val + else + raise ArgumentError, "Mailman lists require an administrator email address" + end + if val = @resource[:password] + args << val + else + raise ArgumentError, "Mailman lists require an administrator password" + end + newlist(*args) + puts "done" + end + + def destroy(purge = false) + puts "destroy", self.name + name = setenv + args = [] + args << "--archives" if purge + args << name + rmlist(*args) + end + + def exists? + properties[:ensure] != :absent + end + + def flush + @property_hash.clear + end + + def properties + if @property_hash.empty? + @property_hash = query || {:ensure => :absent} + @property_hash[:ensure] = :absent if @property_hash.empty? + end + @property_hash.dup + end + + def purge + destroy(true) + end + + def query + self.class.instances.each do |list| + if list.name == self.name or list.name.downcase == self.name + return list.properties + end + end + nil + end +end diff --git a/lib/puppet/type/mailman_list.rb b/lib/puppet/type/mailman_list.rb new file mode 100644 index 0000000..a61fcb6 --- /dev/null +++ b/lib/puppet/type/mailman_list.rb @@ -0,0 +1,30 @@ +require 'puppet/provider/parsedfile' + +Puppet::Type.newtype(:mailman_list) do + ensurable + newparam(:name) do + desc "The name of the mailing list." + end + + newparam(:install) do + desc "The mailmain installation to use." + end + newparam(:admin) do + desc "The email address of the administrator." + end + newparam(:description) do + desc "The description of the mailing list." + end + newparam(:mailserver) do + desc "The FQDN of the mailing list host." + end + newparam(:password) do + desc "The admin password for the list." + end + newparam(:provider) do + desc "The backend to use for this mailman_list." + end + newparam(:webserver) do + desc "The FQDN of the host providing web archives." + end +end diff --git a/manifests/init.pp b/manifests/init.pp index 5c0f0bf..d9cc717 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -2,7 +2,7 @@ # class mailman( $vhost_name = $::fqdn, - + $multihost = false, ) { include ::httpd @@ -11,49 +11,51 @@ class mailman( ensure => installed, } - ::httpd::vhost { $vhost_name: - port => 80, - docroot => '/var/www/', - priority => '50', - template => 'mailman/mailman.vhost.erb', + if ($multihost) { + file { '/etc/mailman/mm_cfg.py': + ensure => present, + owner => 'root', + group => 'root', + mode => '0444', + content => template('mailman/mm_cfg_multihost.py.erb'), + replace => true, + require => Package['mailman'], + } + } else { + ::httpd::vhost { $vhost_name: + port => 80, + docroot => '/var/www/', + priority => '50', + template => 'mailman/mailman.vhost.erb', + } + file { '/etc/mailman/mm_cfg.py': + ensure => present, + owner => 'root', + group => 'root', + mode => '0444', + content => template('mailman/mm_cfg.py.erb'), + replace => true, + require => Package['mailman'], + } + service { 'mailman': + ensure => running, + hasrestart => true, + hasstatus => false, + subscribe => File['/etc/mailman/mm_cfg.py'], + require => Package['mailman'], + } } + httpd_mod { 'rewrite': ensure => present, before => Service['httpd'], } + httpd_mod { 'cgid': ensure => present, before => Service['httpd'], } - file { '/var/www/index.html': - ensure => present, - source => 'puppet:///modules/mailman/index.html', - owner => 'root', - group => 'root', - replace => true, - mode => '0444', - require => Httpd::Vhost[$vhost_name], - } - - file { '/etc/mailman/mm_cfg.py': - ensure => present, - owner => 'root', - group => 'root', - mode => '0444', - content => template('mailman/mm_cfg.py.erb'), - replace => true, - require => Package['mailman'], - } - - service { 'mailman': - ensure => running, - hasrestart => true, - hasstatus => false, - subscribe => File['/etc/mailman/mm_cfg.py'], - require => Package['mailman'], - } - file { '/etc/mailman/en': ensure => directory, owner => 'root', @@ -63,4 +65,13 @@ class mailman( require => Package['mailman'], source => 'puppet:///modules/mailman/html-templates-en', } + + file { '/var/www/index.html': + ensure => present, + source => 'puppet:///modules/mailman/index.html', + owner => 'root', + group => 'root', + replace => true, + mode => '0444', + } } diff --git a/manifests/site.pp b/manifests/site.pp new file mode 100644 index 0000000..5667f23 --- /dev/null +++ b/manifests/site.pp @@ -0,0 +1,93 @@ +# Copyright (C) 2017 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define mailman::site ($default_email_host, $default_url_host) +{ + include ::httpd + + $root = "/srv/mailman/${name}" + $dirs = [ + "${root}/", + "${root}/etc", + "${root}/lists", + "${root}/logs", + "${root}/locks", + "${root}/data", + "${root}/spam", + "${root}/mail", + "${root}/run", + "${root}/archives", + "${root}/archives/public", + "${root}/archives/private", + "${root}/qfiles", + "${root}/qfiles/in", + "${root}/qfiles/out", + "${root}/qfiles/commands", + "${root}/qfiles/bounces", + "${root}/qfiles/news", + "${root}/qfiles/archive", + "${root}/qfiles/shunt", + "${root}/qfiles/virgin", + "${root}/qfiles/bad", + "${root}/qfiles/retry", + "${root}/qfiles/maildir", + ] + + file { $dirs: + ensure => directory, + owner => 'list', + group => 'list', + mode => '2775', + } + + file { "/srv/mailman/${name}/etc/mm_cfg_local.py": + ensure => present, + content => template('mailman/mm_site_cfg.py.erb'), + } + + if ! defined(File['/etc/mailman/sites']) { + file { '/etc/mailman/sites': + ensure => present, + } + } + + file_line { "mailman_site_file_${name}": + require => File['/etc/mailman/sites'], + path => '/etc/mailman/sites', + line => "${default_email_host}: /srv/mailman/${name}", + } + + ::httpd::vhost { $default_url_host: + port => 80, + docroot => '/var/www/', + priority => '50', + template => 'mailman/mailman_multihost.vhost.erb', + } + + file { "/etc/init.d/mailman-${name}": + ensure => present, + content => template('mailman/mailman.init.erb'), + owner => 'root', + group => 'root', + mode => '0755', + } + + service { "mailman-${name}": + enable => true, + hasrestart => true, + hasstatus => false, + require => File["/etc/init.d/mailman-${name}"], + } +} diff --git a/templates/mailman.init.erb b/templates/mailman.init.erb new file mode 100644 index 0000000..3c7b2ff --- /dev/null +++ b/templates/mailman.init.erb @@ -0,0 +1,116 @@ +#! /bin/sh +# +# mailman-<%= @name %> starts up the master queue runner for mailman +# +# Based on skeleton originally by Miquel van Smoorenburg and Ian Murdock, +# customisations by Tollef Fog Heen and Thijs Kinkhorst for Debian. +# +### BEGIN INIT INFO +# Provides: mailman-qrunner-<%= @name %> +# Required-Start: $syslog $local_fs $remote_fs $named $network +# Required-Stop: $syslog $local_fs $remote_fs $named $network +# Should-Start: +# Should-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: +# Short-Description: Mailman Master Queue Runner +# Description: Starts and stops the Mailman queue runners, used to +# manage the various message queues within the Mailman +# mailing list manager. +### END INIT INFO + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/lib/mailman/bin/mailmanctl +MAILMAN_SITE_DIR=/srv/mailman/<%= @name %> +PIDFILE=$MAILMAN_SITE_DIR/run/mailman.pid + + +test -x $DAEMON || exit 0 + +set -e + +if ! [ -d /var/run/mailman ]; then + install -d -o list -g list /var/run/mailman +fi + +if ! [ -d /var/lock/mailman ]; then + install -d -o root -g list -m 2775 /var/lock/mailman +fi + +. /lib/lsb/init-functions + +# In rare upgrading cycles python might not be available at some point. +# Do not break the upgrade in that case. +if ! [ -x /usr/bin/python ]; then + log_warning_msg "Python interpreter not available, exiting." + exit 0; +fi + +# Just a newline. +nl=' +' + +case "$1" in + start) + SITE_LIST=$( sed -rne "s/^[[:space:]]*MAILMAN_SITE_LIST[[:space:]]*=[[:space:]]*(['\"])([^'\"]+)\\1/\\2/p" /etc/mailman/mm_cfg.py ) + [ -n "$SITE_LIST" ] || SITE_LIST='mailman' + case "$nl$(/var/lib/mailman/bin/list_lists -b)$nl" in + (*$nl$SITE_LIST$nl*) ;; + (*) + log_warning_msg "Site list for mailman missing (looking for list named '${SITE_LIST}')." + log_warning_msg "Please create it; until then, mailman will refuse to start." + exit 0 ;; + esac + log_daemon_msg "Starting Mailman master qrunner" "mailmanctl" + if $DAEMON -s -q start; then + log_end_msg 0 + else + log_end_msg 1 + fi + ;; + stop) + log_daemon_msg "Stopping Mailman master qrunner" "mailmanctl" + if $DAEMON -q stop; then + rm -f $PIDFILE + log_end_msg 0 + else + log_end_msg 1 + fi + ;; + reload) + log_begin_msg "Reloading Mailman master qrunner configuration" + if $DAEMON -q restart; then + log_end_msg 0 + else + log_end_msg 1 + fi + ;; + restart|force-reload) + PID=`cat $PIDFILE 2>/dev/null` || true + log_daemon_msg "Restarting Mailman master qrunner" "mailmanctl" + $DAEMON -q stop + if test -n "$PID" && kill -0 $PID 2>/dev/null ; then + log_action_begin_msg "Waiting" + for cnt in `seq 1 5`; do + sleep 1 + kill -0 $PID 2>/dev/null || break + done; + if kill -0 $PID 2>/dev/null ; then + log_action_end_msg 1 + else + log_action_end_msg 0 + fi + fi + if $DAEMON -q start; then + log_end_msg 0 + else + log_end_msg 1 + fi + ;; + *) + echo "Usage: /etc/init.d/mailman {start|stop|restart|reload|force-reload}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/templates/mailman_multihost.vhost.erb b/templates/mailman_multihost.vhost.erb new file mode 100644 index 0000000..7075bea --- /dev/null +++ b/templates/mailman_multihost.vhost.erb @@ -0,0 +1,62 @@ + + ServerName <%= @default_url_host %> + + ErrorLog ${APACHE_LOG_DIR}/<%= @default_url_host %>-error.log + + # Possible values include: debug, info, notice, warn, error, crit, + # alert, emerg. + LogLevel warn + + CustomLog ${APACHE_LOG_DIR}/<%= @default_url_host %>-access.log combined + + DocumentRoot /var/www + +RewriteEngine on +RewriteRule ^/$ /cgi-bin/mailman/listinfo [R] + +# We can find mailman here: +ScriptAlias /cgi-bin/mailman/ /usr/lib/cgi-bin/mailman/ +# And the public archives: +Alias /pipermail/ /var/lib/mailman/archives/public/ +# Logos: +Alias /images/mailman/ /usr/share/images/mailman/ + +# Use this if you don't want the "cgi-bin" component in your URL: +# In case you want to access mailman through a shorter URL you should enable +# this: +#ScriptAlias /mailman/ /usr/lib/cgi-bin/mailman/ +# In this case you need to set the DEFAULT_URL_PATTERN in +# /etc/mailman/mm_cfg.py to http://%s/mailman/ for the cookie +# authentication code to work. Note that you need to change the base +# URL for all the already-created lists as well. + + + AllowOverride None + Options ExecCGI + AddHandler cgi-script .cgi + SetEnv MAILMAN_SITE_DIR /srv/mailman/<%= @name %> + Order allow,deny + Allow from all + = 2.4> + Require all granted + + + + Options FollowSymlinks + AllowOverride None + Order allow,deny + Allow from all + = 2.4> + Require all granted + + + + AllowOverride None + Order allow,deny + Allow from all + = 2.4> + Require all granted + + + + diff --git a/templates/mm_cfg_multihost.py.erb b/templates/mm_cfg_multihost.py.erb new file mode 100644 index 0000000..208ec04 --- /dev/null +++ b/templates/mm_cfg_multihost.py.erb @@ -0,0 +1,40 @@ +import os +import sys + +sys.path.insert(0, os.path.join(os.environ['MAILMAN_SITE_DIR'], 'etc')) +from mm_cfg_local import * + +VAR_PREFIX = os.environ['MAILMAN_SITE_DIR'] + +# 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') +DATA_DIR = os.path.join(VAR_PREFIX, 'data') +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') +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') +INQUEUE_DIR = os.path.join(QUEUE_DIR, 'in') +OUTQUEUE_DIR = os.path.join(QUEUE_DIR, 'out') +CMDQUEUE_DIR = os.path.join(QUEUE_DIR, 'commands') +BOUNCEQUEUE_DIR = os.path.join(QUEUE_DIR, 'bounces') +NEWSQUEUE_DIR = os.path.join(QUEUE_DIR, 'news') +ARCHQUEUE_DIR = os.path.join(QUEUE_DIR, 'archive') +SHUNTQUEUE_DIR = os.path.join(QUEUE_DIR, 'shunt') +VIRGINQUEUE_DIR = os.path.join(QUEUE_DIR, 'virgin') +BADQUEUE_DIR = os.path.join(QUEUE_DIR, 'bad') +RETRYQUEUE_DIR = os.path.join(QUEUE_DIR, 'retry') +MAILDIR_DIR = os.path.join(QUEUE_DIR, 'maildir') + +# Other useful files +PIDFILE = os.path.join(VAR_PREFIX, 'run', 'mailman.pid') +SITE_PW_FILE = os.path.join(DATA_DIR, 'adm.pw') +LISTCREATOR_PW_FILE = os.path.join(DATA_DIR, 'creator.pw') diff --git a/templates/mm_site_cfg.py.erb b/templates/mm_site_cfg.py.erb new file mode 100644 index 0000000..5491e91 --- /dev/null +++ b/templates/mm_site_cfg.py.erb @@ -0,0 +1,119 @@ +# -*- python -*- + +# Copyright (C) 1998,1999,2000 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 + + +"""This is the module which takes your site-specific settings. + +From a raw distribution it should be copied to mm_cfg.py. If you +already have an mm_cfg.py, be careful to add in only the new settings +you want. The complete set of distributed defaults, with annotation, +are in ./Defaults. In mm_cfg, override only those you want to +change, after the + + from Defaults import * + +line (see below). + +Note that these are just default settings - many can be overridden via the +admin and user interfaces on a per-list or per-user basis. + +Note also that some of the settings are resolved against the active list +setting by using the value as a format string against the +list-instance-object's dictionary - see the distributed value of +DEFAULT_MSG_FOOTER for an example.""" + + +####################################################### +# Here's where we get the distributed defaults. # + +from Mailman.Defaults import * + +############################################################## +# Put YOUR site-specific configuration below, in mm_cfg.py . # +# See Defaults.py for explanations of the values. # + +#------------------------------------------------------------- +# The name of the list Mailman uses to send password reminders +# and similar. Don't change if you want mailman-owner to be +# a valid local part. +MAILMAN_SITE_LIST = 'mailman' + +#------------------------------------------------------------- +# If you change these, you have to configure your http server +# accordingly (Alias and ScriptAlias directives in most httpds) +DEFAULT_URL_PATTERN = 'http://%s/cgi-bin/mailman/' +PRIVATE_ARCHIVE_URL = '/cgi-bin/mailman/private' +IMAGE_LOGOS = '/images/mailman/' + +#------------------------------------------------------------- +# Default domain for email addresses of newly created MLs +DEFAULT_EMAIL_HOST = '<%= @default_email_host %>' +#------------------------------------------------------------- +# Default host for web interface of newly created MLs +DEFAULT_URL_HOST = '<%= @default_url_host %>' +#------------------------------------------------------------- +# Required when setting any of its arguments. +add_virtualhost(DEFAULT_URL_HOST, DEFAULT_EMAIL_HOST) + +#------------------------------------------------------------- +# The default language for this server. +DEFAULT_SERVER_LANGUAGE = 'en' + +#------------------------------------------------------------- +# Iirc this was used in pre 2.1, leave it for now +USE_ENVELOPE_SENDER = 0 # Still used? + +#------------------------------------------------------------- +# Unset send_reminders on newly created lists +DEFAULT_SEND_REMINDERS = 0 + +#------------------------------------------------------------- +# Uncomment this if you configured your MTA such that it +# automatically recognizes newly created lists. +# (see /usr/share/doc/mailman/README.Exim4.Debian or +# /usr/share/mailman/postfix-to-mailman.py) +MTA=None # Misnomer, suppresses alias output on newlist + +#------------------------------------------------------------- +# Uncomment if you use Postfix virtual domains (but not +# postfix-to-mailman.py), but be sure to see +# /usr/share/doc/mailman/README.Debian first. +# MTA='Postfix' + +#------------------------------------------------------------- +# Uncomment if you want to filter mail with SpamAssassin. For +# more information please visit this website: +# http://www.jamesh.id.au/articles/mailman-spamassassin/ +# GLOBAL_PIPELINE.insert(1, 'SpamAssassin') + +# Note - if you're looking for something that is imported from mm_cfg, but you +# didn't find it above, it's probably in /usr/lib/mailman/Mailman/Defaults.py. + +# Enable VERP, but let Exim create the VERP addresses since it's +# more efficient. --jeblair + +VERP_PASSWORD_REMINDERS = 1 +VERP_PERSONALIZED_DELIVERIES = 1 +VERP_CONFIRMATIONS = 1 +VERP_DELIVERY_INTERVAL = 0 + +# Make membership viewable by admin only by default (lp bug 1021493) +# Private_roster == 0: anyone can see, 1: members only, 2: admin only. +DEFAULT_PRIVATE_ROSTER = 2 +