diff options
author | Guilhem Moulin <guilhem@fripost.org> | 2014-01-14 08:06:54 +0100 |
---|---|---|
committer | Guilhem Moulin <guilhem@fripost.org> | 2015-06-07 02:51:38 +0200 |
commit | ab83789bd70d294623e62e0b366b6b649cb5b0af (patch) | |
tree | 6d000972a52e04a0a9828843cb252a5dbe3828e9 /roles | |
parent | 97352d1452917fdcd81da0e209aed6e735c00961 (diff) |
Mailing lists (using mlmmj).
Right now the list server cannot be hosted with a MX, due to bug 51:
http://mlmmj.org/bugs/bug.php?id=51
Web archive can be compiled with MHonArc, but the web server
configuration is not there yet.
Diffstat (limited to 'roles')
-rw-r--r-- | roles/common-LDAP/templates/etc/ldap/database.ldif.j2 | 2 | ||||
-rw-r--r-- | roles/common/files/etc/postfix/master.cf | 7 | ||||
-rw-r--r-- | roles/lists/files/etc/cron.d/mlmmj | 1 | ||||
-rw-r--r-- | roles/lists/files/etc/mhonarc.rc | 421 | ||||
l--------- | roles/lists/files/etc/postfix/virtual/mailbox_domains.cf | 1 | ||||
-rw-r--r-- | roles/lists/files/etc/postfix/virtual/transport_lists_maps.cf | 7 | ||||
-rwxr-xr-x | roles/lists/files/usr/local/bin/mhonarc-scan.sh | 114 | ||||
-rwxr-xr-x | roles/lists/files/usr/local/bin/mlmmj-newlist.sh | 139 | ||||
-rw-r--r-- | roles/lists/files/var/lib/mlmmj/static/css/fripost.css | 63 | ||||
-rw-r--r-- | roles/lists/handlers/main.yml | 6 | ||||
-rw-r--r-- | roles/lists/tasks/mail.yml | 35 | ||||
-rw-r--r-- | roles/lists/tasks/main.yml | 2 | ||||
-rw-r--r-- | roles/lists/tasks/mlmmj.yml | 78 | ||||
-rw-r--r-- | roles/lists/templates/etc/postfix/main.cf.j2 | 76 |
14 files changed, 951 insertions, 1 deletions
diff --git a/roles/common-LDAP/templates/etc/ldap/database.ldif.j2 b/roles/common-LDAP/templates/etc/ldap/database.ldif.j2 index b4c2c4f..6e5961b 100644 --- a/roles/common-LDAP/templates/etc/ldap/database.ldif.j2 +++ b/roles/common-LDAP/templates/etc/ldap/database.ldif.j2 @@ -97,6 +97,8 @@ olcSyncrepl: rid=000 binddn="cn=MX-replicate,ou=services,o=mailHosting,dc=fripost,dc=org" credentials=mx {% elif 'lists' in group_names %} +# XXX: mlmmj is not compatible with the MX, see +# http://mlmmj.org/bugs/bug.php?id=51 olcSyncrepl: rid=001 provider=ldap://{{ LDAP_provider }} type=refreshAndPersist diff --git a/roles/common/files/etc/postfix/master.cf b/roles/common/files/etc/postfix/master.cf index 325af1b..3833446 100644 --- a/roles/common/files/etc/postfix/master.cf +++ b/roles/common/files/etc/postfix/master.cf @@ -40,14 +40,19 @@ anvil unix - - - - 1 anvil scache unix - - - - 1 scache 127.0.0.1:16132 inet n - - - - smtpd 127.0.0.1:2526 inet n - - - - smtpd -127.0.0.1:2527 inet n - - - - smtpd +2527 inet n - - - - smtpd + -o mynetworks=0.0.0.0/0 127.0.0.1:2580 inet n - - - - smtpd 127.0.0.1:2599 inet n - - - - smtpd -o cleanup_service_name=cleanup-catchall cleanup-catchall unix n - - - 0 cleanup -o virtual_alias_maps=cdb:$config_directory/virtual/reserved_alias_maps,ldap:$config_directory/virtual/alias_maps.cf,ldap:/etc/postfix-mx/virtual/catchall_maps.cf +127.0.0.1:smtp inet n - - - - smtpd + -o inet_interfaces=127.0.0.1 reserved-alias unix - n n - - pipe flags=Rhu user=nobody argv=/usr/local/sbin/reserved-alias.pl ${sender} ${original_recipient} @fripost.org +mlmmj unix - n n - - pipe + flags=Rhu user=mlmmj argv=/usr/bin/mlmmj-receive -L /var/spool/mlmmj/${domain}/${user} amavisfeed unix - - n - 2 lmtp -o lmtp_destination_recipient_limit=1000 -o lmtp_send_xforward_command=yes diff --git a/roles/lists/files/etc/cron.d/mlmmj b/roles/lists/files/etc/cron.d/mlmmj new file mode 100644 index 0000000..2f34265 --- /dev/null +++ b/roles/lists/files/etc/cron.d/mlmmj @@ -0,0 +1 @@ +0 */2 * * * mlmmj /usr/bin/test -x /usr/bin/mlmmj-maintd && /usr/bin/mlmmj-maintd -F -d /var/spool/mlmmj diff --git a/roles/lists/files/etc/mhonarc.rc b/roles/lists/files/etc/mhonarc.rc new file mode 100644 index 0000000..bcb967e --- /dev/null +++ b/roles/lists/files/etc/mhonarc.rc @@ -0,0 +1,421 @@ +<Include> +/usr/share/doc/mhonarc/examples/utf-8-encode.mrc +/usr/share/doc/mhonarc/examples/secure.mrc +/usr/share/doc/mhonarc/examples/def-mime.mrc.gz +</Include> + +<NoPrintXComments> + +<FieldOrder> +from +to +subject +date +</FieldOrder> + +<LabelStyles> +-default- +</LabelStyles> + +<MIMEArgs> +text/plain; fancyquote +text/html; disableflowed +text/x-html; disableflowed +</MIMEArgs> + +<MIMEIncs> +text +image +message +application/pgp-encrypted +application/pgp-signature +</MIMEIncs> + +<NOFOLREFS> +<TSlice> +3:7:1 +</TSlice> + +<POSIXSTRFTIME> +<LocalDateFmt> +%a %b %d %T %Z %Y +</LocalDateFmt> + +<ModifybodyAddresses> + +<AddressModifyCode> +s#\@(\w+)([.-]\w+)#'@'.('x' x length($1)).$2#e; +</AddressModifyCode> + +<MailToURL> +mailto:$TOADDRNAME$.AT.$TOADDRDOMAIN$ +</MailToURL> + + + +<!-- Have attachments written to a sub-directory --> +<AttachmentDir> +attachments +</AttachmentDir> + +<!-- Define base URL to attachment directory. --> +<AttachmentURL> +attachments +</AttachmentURL> + +<CheckNoArchive> + +<DEFINEVAR> +ListHeader + <p class="muted credit pull-right"> + <a title="$ListName$'s home page" href="$ListPage$">$ListName$</a>'s archives + $DirDate$ + ($NUMOFMSG$ messages) + </p> +</DEFINEVAR> + +<!-- Messages --> + +<MsgPgBegin> +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>$SUBJECTNA$</title> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content=""> + <meta name="author" content=""> + + <link href="/static/css/bootstrap.min.css" rel="stylesheet"> + <link href="/static/css/fripost.css" rel="stylesheet"> + </head> + + <body> +</MsgPgBegin> + +<MsgPgEnd> + </div> + </div> + <div id="footer"> + $MsgNav$ + <p class="muted credit pull-right"> + Last update by <a href="http://mhonarc.org">MHonArc</a> on $LOCALDATE$. + </p> + </div> + </body> +</html> +</MsgPgEnd> + + +<PrevButton chop> +<a title="Date prev" href="$MSG(PREV)$" class="glyphicon glyphicon-backward"></a> +</PrevButton> +<PrevButtonIA chop> +<span class="glyphicon glyphicon-backward"></span> +</PrevButtonIA> + +<NextButton chop> +<a title="Date next" href="$MSG(NEXT)$" class="glyphicon glyphicon-forward"></a> +</NextButton> +<NextButtonIA chop> +<span class="glyphicon glyphicon-forward"></span> +</NextButtonIA> + +<TPrevInButton chop> +<a title="Thread prev" href="$MSG(TPREVIN)$" class="glyphicon glyphicon-backward"></a> +</TPrevInButton> +<TPrevInButtonIA chop> +<span class="glyphicon glyphicon-backward"></span> +</TPrevInButtonIA> + +<TNextInButton chop> +<a title="Thread next" href="$MSG(TNEXTIN)$" class="glyphicon glyphicon-forward"></a> +</TNextInButton> +<TNextInButtonIA chop> +<span class="glyphicon glyphicon-forward"></span> +</TNextInButtonIA> + +<DEFINEVAR> +MsgNav +$BUTTON(PREV)$ <a title="Date index" href="$IDXFNAME$#$MSGNUM$">Date</a> $BUTTON(NEXT)$ $BUTTON(TPREVIN)$ <a title="Thread index" href="$TIDXFNAME$#$MSGNUM$">Thread</a> $BUTTON(TNEXTIN)$ +</DEFINEVAR> + +<TSubjectBeg> +<li class="dummy small">(possible follow-ups)</li> +</TSubjectBeg> + +<TContBegin> +<li><strong>$SUBJECTNA$</strong> <span class="dummy small">(continued)</span></li> +</TContBegin> + +<TopLinks> + <div id="header"> + $MsgNav$ + $ListHeader$ + </div> + <div id="wrap"> + <div id="main" class="container"> +</TopLinks> + +<SubjectHeader> +<h1 class="msg subject">$SUBJECTNA$</h1> +</SubjectHeader> + +<FieldsBeg> +<div class="well msg header"> + <table> +</FieldsBeg> + +<LabelBeg> +<tr> + <td> +</LabelBeg> + +<LabelEnd> + </td> +</LabelEnd> + +<FldBeg> + <td> +</FldBeg> + +<FldEnd> + </td> +</tr> +</FldEnd> + +<FieldsEnd> + </table> +</div> +</FieldsEnd> + + +<HeadBodySep> +<div class="msg body"> +</HeadBodySep> + +<MsgBodyEnd> +</div> +<div class="well msg footer"> +</MsgBodyEnd> + +<FolUpBegin> +<strong>Follow-Ups ($NUMFOLUP$):</strong> +<ul> +</FolUpBegin> + +<FolupLiTXT> +<li><strong>$SUBJECT$</strong>, <em>$FROMNAME$</em></li> +</FolupLiTXT> + +<FolUpEnd> +</ul> +<hr> +</FolUpEnd> + +<TSliceBeg> +<strong>References:</strong> +<ul> +</TSliceBeg> + +<TSliceTopBeginCur> +<li><strong>$SUBJECTNA$</strong>, <em>$FROMNAME$</em> +</TSliceTopBeginCur> + +<TSliceLiTxtCur> +<li><strong>$SUBJECTNA$</strong>, <em>$FROMNAME$</em> +</TSliceLiTxtCur> + +<TSliceSingleTxtCur> +<li><strong>$SUBJECTNA$</strong>, <em>$FROMNAME$</em> +</TSliceSingleTxtCur> + +<TSliceEnd> +</ul> +</TSliceEnd> + +<RefsBegin> +<strong>References:</strong> +<ul> +</RefsBegin> + +<RefsLiTxt> +<li><strong>$SUBJECT$</strong>, <em>$FROMNAME$</em></li> +</RefsLiTxt> + +<RefsEnd> +</ul> +<hr> +</RefsEnd> + +<BotLinks> +$TSLICE$ +</div> +</BotLinks> + + + +<!-- Thread index --> + +<TLEVELS> +5 +</TLEVELS> + +<MULTIPG> +<IDXSIZE> +250 +</IDXSIZE> + +<TFirstPgLink chop> +<a title="First page" href="$PG(TFIRST)$" class="glyphicon glyphicon-fast-backward"></a> +</TFirstPgLink> + +<TPrevPgLink chop> +<a title="Previous page" href="$PG(TPREV)$" class="glyphicon glyphicon-backward"></a> +</TPrevPgLink> +<TPrevPgLinkIA chop> +<span class="glyphicon glyphicon-backward"></span> +</TPrevPgLinkIA> + +<TNextPgLink chop> +<a title="Next page" href="$PG(TNEXT)$" class="glyphicon glyphicon-forward"></a> +</TNextPgLink> +<TNextPgLinkIA chop> +<span class="glyphicon glyphicon-forward"></span> +</TNextPgLinkIA> + +<TLastPgLink chop> +<a title="Last page" href="$PG(TLAST)$" class="glyphicon glyphicon-fast-forward"></a> +</TLastPgLink> + +<DEFINEVAR> +TNav +$PGLINK(TFIRST)$$PGLINK(TPREV)$ Page $PAGENUM$/$NUMOFPAGES$ $PGLINK(TNEXT)$$PGLINK(TLAST)$ +[Indexes: <a href="$IDXFNAME$">Date</a> Thread] +</DEFINEVAR> + +<THead> + <div id="wrap"> + <div id="main" class="container"> + <ul class="index"> +</THead> +<TFoot> + </ul> + </div> + </div> +</TFoot> + +<TIdxPgBegin> +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>$TIDXTITLE$</title> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content=""> + <meta name="author" content=""> + + <link href="/static/css/bootstrap.min.css" rel="stylesheet"> + <link href="/static/css/fripost.css" rel="stylesheet"> + </head> + + <body> + <div id="header"> + $TNav$ + $ListHeader$ + </div> +</TIdxPgBegin> + +<NoDoc> +<TIdxPgEnd> + <div id="footer"> + $TNav$ + <p class="muted credit pull-right"> + Page generated by <a href="http://mhonarc.org">MHonArc</a> on $LOCALDATE$. + </p> + </div> + </body> +</html> +</TIdxPgEnd> + +<LiTemplate> +<li><strong>$SUBJECT$</strong>, +<em>$FROMNAME$</em> +</li> +</LiTemplate> + + + + +<!-- Date index --> + +<FirstPgLink chop> +<a title="First page" href="$PG(FIRST)$" class="glyphicon glyphicon-fast-backward"></a> +</FirstPgLink> + +<PrevPgLink chop> +<a title="Previous page" href="$PG(PREV)$" class="glyphicon glyphicon-backward"></a> +</PrevPgLink> +<PrevPgLinkIA chop> +<span class="glyphicon glyphicon-backward"></span> +</PrevPgLinkIA> + +<NextPgLink chop> +<a title="Next page" href="$PG(NEXT)$" class="glyphicon glyphicon-forward"></a> +</NextPgLink> +<NextPgLinkIA chop> +<span class="glyphicon glyphicon-forward"></span> +</NextPgLinkIA> + +<LastPgLink chop> +<a title="Last page" href="$PG(LAST)$" class="glyphicon glyphicon-fast-forward"></a> +</LastPgLink> + +<DEFINEVAR> +Nav +$PGLINK(FIRST)$$PGLINK(PREV)$ Page $PAGENUM$/$NUMOFPAGES$ $PGLINK(NEXT)$$PGLINK(LAST)$ +[Indexes: Date <a href="$TIDXFNAME$">Thread</a>] +</DEFINEVAR> + +<ListBegin> + <div id="wrap"> + <div id="main" class="container"> + <ul class="index"> +</ListBegin> +<ListEnd> + </ul> + </div> + </div> +</ListEnd> + +<IdxPgBegin> +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>$IDXTITLE$</title> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content=""> + <meta name="author" content=""> + + <link href="/static/css/bootstrap.min.css" rel="stylesheet"> + <link href="/static/css/fripost.css" rel="stylesheet"> + </head> + + <body> + <div id="header"> + $Nav$ + $ListHeader$ + </div> +</IdxPgBegin> + +<IdxPgEnd> + <div id="footer"> + $Nav$ + <p class="muted credit pull-right"> + Last update by <a href="http://mhonarc.org">MHonArc</a> on $LOCALDATE$. + </p> + </div> + </body> +</html> +</IdxPgEnd> diff --git a/roles/lists/files/etc/postfix/virtual/mailbox_domains.cf b/roles/lists/files/etc/postfix/virtual/mailbox_domains.cf new file mode 120000 index 0000000..05f7ed9 --- /dev/null +++ b/roles/lists/files/etc/postfix/virtual/mailbox_domains.cf @@ -0,0 +1 @@ +../../../../../MX/templates/etc/postfix/virtual/mailbox_domains.cf.j2
\ No newline at end of file diff --git a/roles/lists/files/etc/postfix/virtual/transport_lists_maps.cf b/roles/lists/files/etc/postfix/virtual/transport_lists_maps.cf new file mode 100644 index 0000000..50631e5 --- /dev/null +++ b/roles/lists/files/etc/postfix/virtual/transport_lists_maps.cf @@ -0,0 +1,7 @@ +server_host = ldapi://%2Fprivate%2Fldapi/ +version = 3 +search_base = fvl=%u,fvd=%d,ou=virtual,o=mailHosting,dc=fripost,dc=org +scope = base +bind = none +query_filter = (&(objectClass=FripostVirtualList)(fvl=%u)) +result_attribute = fripostListManager diff --git a/roles/lists/files/usr/local/bin/mhonarc-scan.sh b/roles/lists/files/usr/local/bin/mhonarc-scan.sh new file mode 100755 index 0000000..d0ea2af --- /dev/null +++ b/roles/lists/files/usr/local/bin/mhonarc-scan.sh @@ -0,0 +1,114 @@ +#!/bin/sh + +# Convert a list archive into HTML. +# +# Copyright © 2014 Guilhem Moulin <guilhem@fripost.org> +# +# 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 3 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, see <http://www.gnu.org/licenses/>. + +set -ue + +fail () { + echo Error: "$@" >&2 + exit 1 +} + +[ $# -eq 1 ] || { echo "Usage: $0 listdir"; exit; } +listdir="${1%/}" +[ -d "$listdir" ] || fail "No such directory: $listdir" + +localpart="${listdir##*/}" +domainpart="${listdir%/$localpart}" +domainpart="${domainpart##*/}" + +# Determine the rotation period +rotation= +[ -s "$listdir/control/archiverotate" ] && read rotation <"$listdir/control/archiverotate" + +# Subdir format (/!\ shouldn't be empty, and shouldn't contain spaces!), +# and archive date format. +case "${rotation:-month}" in + '') subdirf='/' listpage='./'; archivef=;; + year) subdirf="%Y"; listpage='../'; archivef="for %Y";; + month) subdirf="%Y/%m"; listpage='../../'; archivef="for %B %Y";; + day) subdirf="%Y/%m/%d"; listpage='../../../'; archivef="for %a, %d %b %Y";; + *) fail "$rotation: unknown rotation period" +esac + +# Look up for the send date in an email. Fall back to the creation date +# if not found. +printDate () { + local filename date + while read filename; do + if ! [ "$rotation" ]; then + # don't bother looking for a date + date=0 + else + # stop as soon as the header is over + date=$(sed -nr '/^Date:\s*(\S.*)$/I {s//\1/p;q}; /^([^[:cntrl:][:space:]]+:|\s)/ !q' \ + "$filename") + [ "$date" ] || date=@$(stat -c '%Y' "$filename") + fi + echo $(date -d "$date" +"%s $subdirf") "$filename" + done +} + +# Process a (single) subdirectory +process () { + local list="$1" subdir="$2" date="$3" + [ -s "$list" ] || return 0 + + [ -d "$listdir/webarchive/$subdir" ] || mkdir -p "$listdir/webarchive/$subdir" + # TODO: add a line to the index file + xargs -a"$list" mhonarc -definevar ListName="'$localpart'" \ + -definevar ListPage="'${listpage}index.html'" \ + -definevar DirDate="'$date'" \ + -rcfile /etc/mhonarc.rc \ + -add \ + -quiet \ + -outdir "$listdir/webarchive/$subdir" \ + || exit 1 + # empty the list + echo -n >"$list" +} + +# Process all found emails +processM () { + local cursubdir= date= + local timestamp subdir filename + + while read timestamp subdir filename; do + if [ "$cursubdir" != "$subdir" ]; then + process "$list" "$cursubdir" "$date" + cursubdir="$subdir" + date="$(date -d "@$timestamp" +"$archivef")" + fi + echo "$filename" >>"$list" + done + process "$list" "$cursubdir" "$date" +} + +# The span of emails we'll touch during the current instance +now=$(date +'%s') +list=$(mktemp) || exit 1 +trap 'rm -f "$list"' EXIT + +from=0 +if [ -s "$listdir/.webarchive.date" ]; then + read from <"$listdir/.webarchive.date" + from=$(( $from - 30 )) # remove 30s to fight race conditions +fi + +find "$listdir/archive/" -type f -a -newermt @"$from" | printDate | sort -n -k1,1 | processM +echo "$now" > "$listdir/.webarchive.date" diff --git a/roles/lists/files/usr/local/bin/mlmmj-newlist.sh b/roles/lists/files/usr/local/bin/mlmmj-newlist.sh new file mode 100755 index 0000000..a1fcc70 --- /dev/null +++ b/roles/lists/files/usr/local/bin/mlmmj-newlist.sh @@ -0,0 +1,139 @@ +#!/bin/sh + +# Add new lists (with a common options) to be managed by mlmmj. +# Incoming e-mails need to be handed over (piped) to mlmmj-receive(1) by +# the MTA, see http://mlmmj.org/docs/readme-postfix/ to configure the +# MTA. +# +# Copyright © 2014 Guilhem Moulin <guilhem@fripost.org> +# +# 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 3 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, see <http://www.gnu.org/licenses/>. + +set -ue + +fail () { + echo Error: "$@" >&2 + exit 1 +} + +spool=/var/spool/mlmmj # mlmmj's how private directory +lib=/var/lib/mlmmj # shared by mlmmj and (partially) by the web server +webhost=https://lists.fripost.org +umask 0022 + +[ $# -ge 2 -a $# -le 3 ] || { echo "Usage: $0 list owner [language]"; exit; } +list="$1" +owner="$2" +lang="${3:-en}" + +localpart="${list%@*}" +domainpart="${list##*@}" +[ "$localpart" = "$list" ] && [ "$domainpart" = "$list" ] \ +&& fail "$list is not fully-qualified" + +[ -d "$spool/$domainpart/$localpart" -o -d "$lib/$domainpart/$localpart" ] \ +&& fail "$list exists" + +ls -1 /usr/share/mlmmj/text.skel | grep -qFx "$lang" \ +|| fail "Available languages: $(echo $(ls /usr/share/mlmmj/text.skel))" + +case "$webhost" in + *"/$domainpart") listurl="$webhost/$localpart/";; + *) listurl="$webhost/$domainpart/$localpart/";; +esac + +mkdir -p -m0700 "$spool/$domainpart/$localpart" +mkdir -p -m0750 "$lib/$domainpart/$localpart" + + +# The web server has read-only access to subscribers. +for dir in subscribers.d digesters.d nomailsubs.d; do + mkdir -m0750 "$lib/$domainpart/$localpart/$dir" + ln -s "$lib/$domainpart/$localpart/$dir" \ + "$spool/$domainpart/$localpart/$dir" +done + +# The web server can update the list configuration. +mkdir -m2770 "$lib/$domainpart/$localpart/control" +ln -s "$lib/$domainpart/$localpart/control" \ + "$spool/$domainpart/$localpart/control" + +# Internal directories. +for dir in incoming queue queue/discarded \ + subconf unsubconf bounce moderation requeue; do + mkdir -m0700 "$spool/$domainpart/$localpart/$dir" +done + +# Link to templates. +ln -s /usr/share/mlmmj/text.skel/$lang "$spool/$domainpart/$localpart/text" + +# Archives are private, but web archives are public. +mkdir -m0700 "$lib/$domainpart/$localpart/archive" +mkdir -m0750 "$lib/$domainpart/$localpart/webarchive" +ln -s "$lib/$domainpart/$localpart/archive" \ + "$spool/$domainpart/$localpart/archive" +ln -s "$lib/$domainpart/$localpart/webarchive" \ + "$spool/$domainpart/$localpart/webarchive" + +# Default configuration, non-writable from the web. +echo "$list" > "$lib/$domainpart/$localpart/control/listaddress" +echo "$owner" > "$lib/$domainpart/$localpart/control/owner" +# XXX: these tunables are ignored, see http://mlmmj.org/bugs/bug.php?id=51 +#echo 127.0.0.1 > "$lib/$domainpart/$localpart/control/relayhost" +#echo 16132 > "$lib/$domainpart/$localpart/control/smtpport" +echo month > "$lib/$domainpart/$localpart/control/archiverotate" + +# RFC 2369 +cat > "$lib/$domainpart/$localpart/control/customheaders" <<- EOF + Errors-to: $localpart+owner@$domainpart + Precedence: list + List-Id: <$localpart.$domainpart> + List-URL: <$listurl> + List-Post: <mailto:$list> + List-Help: <mailto:$localpart+help@$domainpart>, + <$listurl/> + List-Subscribe: <$localpart+subscribe@$domainpart>, + <$listurl/> + List-Unsubscribe: <mailto:$localpart+unsubscribe@$domainpart>, + <$listurl/> + List-Owner: <mailto:$localpart+owner@$domainpart> + List-Archives: <$listurl/archives/> + Reply-To: $list + X-MailingList: $list + X-Loop: $list +EOF +cat > "$lib/$domainpart/$localpart/control/delheaders" <<- EOF + Return-Receipt-To: + Disposition-Notification-To: + X-Confirm-Reading-To: + X-Pmrqc: +EOF + +# Some useful default, that the user is free to change via the web +# interface. +cat > "$lib/$domainpart/$localpart/control/footer" <<- EOF + _______________________________________________ + $localpart mailing list + $localpart@$domainpart + $listurl +EOF +echo "[$localpart]" > "$lib/$domainpart/$localpart/control/prefix" +touch "$lib/$domainpart/$localpart/control/subonlypost" \ + "$lib/$domainpart/$localpart/control/subonlyget" + +for control in customheaders footer prefix subonlypost subonlyget; do + chmod 0664 "$lib/$domainpart/$localpart/control/$control" +done + +# TODO: welcome mail diff --git a/roles/lists/files/var/lib/mlmmj/static/css/fripost.css b/roles/lists/files/var/lib/mlmmj/static/css/fripost.css new file mode 100644 index 0000000..197eee6 --- /dev/null +++ b/roles/lists/files/var/lib/mlmmj/static/css/fripost.css @@ -0,0 +1,63 @@ +html, body { + height: 100%; + max-width: 1024px; +} +#wrap { + min-height: 100%; + height: auto; + margin: -1.6em auto; + padding: 1.6em 0; + width: 100%; +} +#main { + margin: 0 15px; + padding: 0; + width: auto; +} +#header, #footer { + margin: 0 15px; + height: 1.5em; + overflow: hidden; + padding: 0; +} +#header { + border-bottom: 1px solid #c0c0c0; +} +#footer { + border-top: 1px solid #c0c0c0; +} +h1.msg.subject { + margin-bottom: 1.5ex; +} +.msg.header { + width: auto; + padding: 0; + margin-bottom: 2ex; +} +.msg.header table { + margin: 10px; +} +.msg.header table tr td:first-child { + font-weight: bold; + padding-right: 1ex; +} +.msg.body pre { + background: none; + border: none; + padding: 0 10px; +} +.dummy { + list-style: none; + font-style: italic; + color: gray; +} +.msg.footer { + margin-top: 1ex; + padding: 10px; +} +.msg.footer > ul { + margin-bottom: 0; +} +#main ul.index { + margin: 2ex 0; +} diff --git a/roles/lists/handlers/main.yml b/roles/lists/handlers/main.yml new file mode 100644 index 0000000..c27834e --- /dev/null +++ b/roles/lists/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: Restart Postfix + service: name=postfix state=restarted + +- name: Reload Postfix + service: name=postfix state=reloaded diff --git a/roles/lists/tasks/mail.yml b/roles/lists/tasks/mail.yml new file mode 100644 index 0000000..7bd471e --- /dev/null +++ b/roles/lists/tasks/mail.yml @@ -0,0 +1,35 @@ +- name: Install Postfix + apt: pkg={{ item }} + with_items: + - postfix + - postfix-ldap + +- name: Configure Postfix + template: src=etc/postfix/main.cf.j2 + dest=/etc/postfix-{{ postfix_instance[inst].name }}/main.cf + owner=root group=root + mode=0644 + register: r + notify: + - Restart Postfix + +- name: Create directory /etc/postfix-.../virtual + file: path=/etc/postfix-{{ postfix_instance[inst].name }}/virtual + state=directory + owner=root group=root + mode=0755 + +- name: Copy lookup tables + copy: src=etc/postfix/virtual/{{ item }} + dest=/etc/postfix-{{ postfix_instance[inst].name }}/virtual/{{ item }} + owner=root group=root + mode=0644 + with_items: + - mailbox_domains.cf + - transport_lists_maps.cf + +- name: Start Postfix + service: name=postfix state=started + when: not r.changed + +- meta: flush_handlers diff --git a/roles/lists/tasks/main.yml b/roles/lists/tasks/main.yml new file mode 100644 index 0000000..13d3451 --- /dev/null +++ b/roles/lists/tasks/main.yml @@ -0,0 +1,2 @@ +- include: mail.yml tags=postfix,mail +- include: mlmmj.yml tags=mlmmj,lists diff --git a/roles/lists/tasks/mlmmj.yml b/roles/lists/tasks/mlmmj.yml new file mode 100644 index 0000000..0b88d75 --- /dev/null +++ b/roles/lists/tasks/mlmmj.yml @@ -0,0 +1,78 @@ +- name: Install MLMMJ + apt: pkg={{ item }} + with_items: + - mlmmj + +# Weird the debian package doesn't do it by itself... +- name: Create a user 'mlmmj' + user: name=mlmmj system=yes + home=/var/spool/mlmmj + shell=/bin/false + password=! + state=present + +- name: Add 'www-data' to the group 'mlmmj' + user: name=www-data groups=mlmmj append=yes + +- name: Create a home directory for user 'mlmmj' + file: path=/var/spool/mlmmj + state=directory + owner=mlmmj group=mlmmj + mode=0700 + +- name: Create /var/lib/mlmmj + file: path=/var/lib/mlmmj + state=directory + owner=mlmmj group=mlmmj + mode=0750 + +- name: Auto-maintain mlmmj's spool directory + copy: src=etc/cron.d/mlmmj + dest=/etc/cron.d/mlmmj + owner=root group=root + mode=0644 + +- name: Copy mlmmj-newlist.sh and mhonarc-scan.sh + copy: src=usr/local/bin/{{ item }} + dest=/usr/local/bin/{{ item }} + owner=root group=root + mode=0755 + with_items: + - mlmmj-newlist.sh + - mhonarc-scan.sh + +- name: Copy /etc/mhonarc.rc + copy: src=etc/mhonarc.rc + dest=/etc/mhonarc.rc + owner=root group=root + mode=0644 + +- name: Create /var/lib/mlmmj/... + file: path=/var/lib/mlmmj/{{ item }} + state=directory + owner=root group=root + mode=0755 + with_items: + - static + - static/css + - static/fonts + +- name: Copy CSS files + copy: src=var/lib/mlmmj/static/css/{{ item }} + dest=/var/lib/mlmmj/static/css/{{ item }} + owner=root group=root + mode=0644 + with_items: + - bootstrap.min.css + - fripost.css + +- name: Copy Glyphicon Halflings + copy: src=var/lib/mlmmj/static/fonts/{{ item }} + dest=/var/lib/mlmmj/static/fonts/{{ item }} + owner=root group=root + mode=0644 + with_items: + - glyphicons-halflings-regular.eot + - glyphicons-halflings-regular.svg + - glyphicons-halflings-regular.ttf + - glyphicons-halflings-regular.woff diff --git a/roles/lists/templates/etc/postfix/main.cf.j2 b/roles/lists/templates/etc/postfix/main.cf.j2 new file mode 100644 index 0000000..dff9d0a --- /dev/null +++ b/roles/lists/templates/etc/postfix/main.cf.j2 @@ -0,0 +1,76 @@ +######################################################################## +# Lists configuration +# +# {{ ansible_managed }} +# Do NOT edit this file directly! + +smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU) +biff = no +readme_directory = no +mail_owner = postfix + +delay_warning_time = 4h +maximal_queue_lifetime = 5d + +myorigin = /etc/mailname +myhostname = lists{{ listsno | default('') }}.$mydomain +mydomain = {{ ansible_domain }} +append_dot_mydomain = no + +# Turn off all TCP/IP listener ports except that necessary for the list server. +# XXX: mlmmj is not compatible with the MX, see +# http://mlmmj.org/bugs/bug.php?id=51 +master_service_disable = !127.0.0.1:smtp.inet !2527.inet inet + +queue_directory = /var/spool/postfix-{{ postfix_instance[inst].name }} +data_directory = /var/lib/postfix-{{ postfix_instance[inst].name }} +multi_instance_group = {{ postfix_instance[inst].group | default('') }} +multi_instance_name = postfix-{{ postfix_instance[inst].name }} +multi_instance_enable = yes + +# This server is a Mail Delivery Agent +mynetworks_style = host +inet_interfaces = 172.16.0.1 +{% if 'MX' in group_names %} + 127.0.0.1 +{% endif %} +inet_protocols = ipv4 + +# No local delivery +mydestination = +local_transport = error:5.1.1 Mailbox unavailable +alias_maps = +alias_database = +local_recipient_maps = + +message_size_limit = 67108864 +recipient_delimiter = + + +# Forward everything to our internal mailhub +{% if 'MTA-out' in group_names %} +relayhost = [127.0.0.1]:{{ MTA_out.port }} +{% else %} +relayhost = [{{ MTA_out.host }}]:{{ MTA_out.port }} +{% endif %} +relay_domains = + +# Virtual transport (the alias resolution is already done by the MX:es) +transport_maps = ldap:$config_directory/virtual/transport_lists_maps.cf +mlmmj_destination_recipient_limit = 1 + +# Don't rewrite remote headers +local_header_rewrite_clients = +# Avoid splitting the envelope and scanning messages multiple times +smtp_destination_recipient_limit = 1000 +# Tolerate occasional high latency +smtp_data_done_timeout = 1200s +smtpd_timeout = 1200s + +# Tunnel everything through IPSec +smtp_tls_security_level = none +{% if 'MTA-out' in group_names %} +smtp_bind_address = 127.0.0.1 +{% else %} +smtp_bind_address = 172.16.0.1 +{% endif %} +smtpd_tls_security_level = none |