Initial commit with previous code

This commit is contained in:
Antonio J. Delgado 2022-10-11 10:19:06 +03:00
commit abff60dcfe
33 changed files with 1983 additions and 0 deletions

84
defaults/main.yml Normal file
View file

@ -0,0 +1,84 @@
---
mail_server_fqdn: mail.example.org
mail_db_user: mail_admin
mail_db_password: "{{ vault_mail_admin_password }}"
mail_db_name: 'mail'
admin_password: "{{ mail_admin_password }}"
mail_admin_username: 'mail_admin'
dns_resolver: 1.1.1.1
mail_domains:
- example.org
- example.net
mail_own_networks:
- 1.2.3.4
mail_own_networks_hosts:
- host1.example.org
- host1.example.com
update_spam_db_user: "{{ vault_update_spam_db_user }}"
mail_users:
- fullname: User number 1
email: user1@example.org
password: "{{ vault_user1_md5_password_hash }}"
quota: 104857600000
mail_transports:
- domain: example.com
transport: smtp
mail_forwardings:
- source: user_1@example.org
destination: user1@example.org
porpouse: Alias for user1. Mail sent to user_1 it's redirected to user1
postfix_master_extra: |
dbmail-lmtp unix - - n - - lmtp
-o disable_dns_lookups=yes
postfix_config:
alias_database: hash:/etc/aliases
alias_maps: hash:/etc/aliases
append_dot_mydomain: no
biff: no
body_checks: regexp:/etc/postfix/maps/body_checks.map
broken_sasl_auth_clients: yes
compatibility_level: 2
header_checks: regexp:/etc/postfix/maps/whitelist_senders.map regexp:/etc/postfix/maps/spam_filter_header_check
html_directory: /usr/share/doc/postfix/html
inet_interfaces: all
inet_protocols: all
mailbox_size_limit: 0
mydestination: "{{ mail_server_fqdn }}; localhost; localhost.localdomain"
myhostname: "{{ mail_server_fqdn }}"
mynetworks: "127.0.0.0/8 /etc/postfix/allowed_clients{% if mail_own_networks %}{% for ip in mail_own_networks %} {{ ip }}{% endfor %}{% endif %}"
myorigin: /etc/mailname
policy-spf_time_limit: 3600s
proxy_read_maps: $local_recipient_maps $mydestination $virtual_alias_maps $virtual_alias_domains $virtual_mailbox_maps $virtual_mailbox_domains $relay_recipient_maps $relay_domains $canonical_maps $sender_canonical_maps $recipient_canonical_maps $relocated_maps $transport_maps $mynetworks $virtual_mailbox_limit_maps
readme_directory: /usr/share/doc/postfix
recipient_delimiter: +
relay_recipient_maps: ''
smtpd_banner: $myhostname ESMTP $mail_name
# Block clients that speak too early.
smtpd_data_restrictions: reject_unauth_pipelining
# Don't talk to mail systems that don't know their own hostname.
smtpd_helo_restrictions: reject_unknown_helo_hostname
smtpd_recipient_restrictions: permit_mynetworks, permit_sasl_authenticated, permit_auth_destination, reject_unauth_destination, check_policy_service unix:private/policy-spf
smtpd_relay_restrictions: permit_mynetworks permit_sasl_authenticated defer_unauth_destination
smtpd_sasl_auth_enable: 'yes'
smtpd_sasl_authenticated_header: 'yes'
smtpd_tls_cert_file: "/etc/letsencrypt/live/{{ mail_server_fqdn }}/fullchain.pem"
smtpd_tls_dh1024_param_file: /etc/ssl/private/dhparams.pem
smtpd_tls_exclude_ciphers: aNULL, eNULL, EXPORT, DES, RC4, MD5, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CDC3-SHA, KRB5-DE5, CBC3-SHA
smtpd_tls_key_file: "/etc/letsencrypt/live/{{ mail_server_fqdn }}/privkey.pem"
smtpd_tls_session_cache_database: btree:/var/lib/postfix/smtpd_scache
smtpd_use_tls: yes
# If this is a backupmx or satellite then smtp_sasl_auth_enable: 'yes'
smtp_sasl_auth_enable: 'no'
smtp_sasl_security_options: noanonymous
smtp_sasl_type: cyrus
smtp_tls_session_cache_database: btree:/var/lib/postfix/smtp_scache
smtp_use_tls: 'yes'
transport_maps: proxy:mysql:/etc/postfix/mysql-virtual_transports.cf
virtual_alias_domains: ''
virtual_alias_maps: proxy:mysql:/etc/postfix/mysql-virtual_forwardings.cf, mysql:/etc/postfix/mysql-virtual_email2email.cf
virtual_gid_maps: static:5000
virtual_mailbox_base: /home/vmail
virtual_mailbox_domains: proxy:mysql:/etc/postfix/mysql-virtual_domains.cf
virtual_mailbox_limit_maps: proxy:mysql:/etc/postfix/mysql-virtual_mailbox_limit_maps.cf
virtual_mailbox_maps: proxy:mysql:/etc/postfix/mysql-virtual_mailboxes.cf
virtual_uid_maps: static:5000

View file

@ -0,0 +1,7 @@
#!/bin/bash
FILTER_PATH='/var/spool/filter'
while read -r FOLDER
do
tar cjf "${FOLDER}.tar.bz2" "${FOLDER}" &> /dev/null && rm -rf "${FOLDER}"
done <<< "$(find "${FILTER_PATH}/" -maxdepth 1 -type d -regex "^${FILTER_PATH}/2[0-9]*$")"

331
files/disclaimer.sh Normal file
View file

@ -0,0 +1,331 @@
#!/bin/bash
# http://www.postfix.org/FILTER_README.html
# Clean up when done or when aborting.
finished=false
SENDMAIL=$(which sendmail)
if [ -z ${SENDMAIL} ]; then
if [ -x "/usr/sbin/sendmail" ]; then
SENDMAIL="/usr/sbin/sendmail"
else
nessage "Sendmail command not found"
fi
fi
function finish {
${finished} || logger -t disclaimer "Disclaimer script not finished cleanly."
message "Sendmail command: ${SENDMAIL} -v -v ${ARGUMENTS} <in.$$"
# shellcheck disable=SC2086
result=$("${SENDMAIL}" -v -v ${ARGUMENTS} <"in.$$")
return_code="${?}"
if [[ "${return_code}" != "0" ]]; then
message "Error ${return_code} sending file to sendmail"
message "result: ${result}"
echo "Error ${return_code} sending file to sendmail with disclaimer.sh. result: ${result}" | mail -s "Error in disclaimer.sh filter" ad@susurrando.com
exit ${return_code}
fi
}
#trap "$SENDMAIL '$@' <in.$$" 0 1 2 3 15
trap finish exit
LOGFILE=/var/log/disclaimer.log
if [ ! -w "${LOGFILE}" ]; then
LOGFILE="${HOME}/log/disclaimer.log"
if [ ! -w "${LOGFILE}" ]; then
LOGFILE="./disclaimer.log"
fi
fi
function message() {
HIDE="${2}"
CDATE=$(date '+%Y-%m-%d %H:%M:%S')
APP=$(basename "${0}")
MSG="[$$] ${1}"
if [[ "${HIDE}" != "true" ]]; then
echo "${MSG}"
fi
/usr/bin/logger -t "${APP}" "${MSG}"
echo "${CDATE} ${MSG}" >> "${LOGFILE}"
}
ARGUMENTS=${*}
inspect_dir=/var/spool/filter
export HOME="${inspect_dir}"
out_stat_folder="${inspect_dir}/out.stats"
mkdir -p "${out_stat_folder}"
in_stat_folder="${inspect_dir}/in.stats"
mkdir -p "${in_stat_folder}"
DISCLAIMER_ADDRESSES=/etc/postfix/disclaimer_addresses
TEXT_DISCLAIMER=/etc/postfix/text_disclaimer.txt
HTML_DISCLAIMER_FILE=/etc/postfix/html_disclaimer.htm
# Exit codes from <sysexits.h>
EX_TEMPFAIL=75
EX_UNAVAILABLE=69
DATE=$(date +%s)
#GetRecordID $FROM mail_addresses mail_address mail
function GetRecordID() {
TEXT=$(echo "$1" | sed "s/'/\'/g" -)
ERR="${?}"
if [[ "$ERR" != "0" ]]
then
message "Error $ERR escaping single quotes from TEXT in GetRecordID"
fi
if [[ "$TEXT" == "" ]]; then
echo NULL
else
TABLE=$(echo "$2" | sed "s/'/\'/g" -)
ERR="${?}"
if [[ "$ERR" != "0" ]]
then
message "Error $ERR escaping single quotes from TABLE in GetRecordID"
fi
FIELD=$(echo "$3" | sed "s/'/\'/g" -)
ERR="${?}"
if [[ "$ERR" != "0" ]]
then
message "Error $ERR escaping single quotes from FIELD in GetRecordID"
fi
DATABASE=$(echo "$4" | sed "s/'/\'/g" -)
ERR="${?}"
if [[ "$ERR" != "0" ]]
then
message "Error $ERR escaping single quotes from DATABASE in GetRecordID"
fi
if [[ "$DATABASE" == "" ]]; then
message "No database indicated." true
else
ACTION=$(echo "$5" | sed "s/'/\'/g" -)
ERR="${?}"
if [[ "$ERR" != "0" ]]
then
message "Error $ERR escaping single quotes from $ACTION in GetRecordID"
fi
QUERY="SELECT id FROM $TABLE WHERE $FIELD='$TEXT';"
result=$(echo "$QUERY" | /usr/bin/env mysql "$DATABASE")
resultID=$(echo "$result" | grep -v id)
return_code="${?}"
if [[ "$return_code" != "0" ]]; then
message "Error getting $FIELD id for $TEXT from database with query '$QUERY'. result: $result" true
else
if [[ "$resultID" == "" ]]; then
message "The field '$TABLE.$FIELD' in database '$DATABASE' doesn't contain a record '$TEXT', adding it."
QUERY="INSERT INTO $TABLE (id, $FIELD) VALUES (NULL, '$TEXT');"
temp_file=$(mktemp /tmp/tmp.XXXXX)
echo "$QUERY" | /usr/bin/env mysql -v -v -v "$DATABASE" &> "${temp_file}"
return_code="${?}"
result=$(cat "${temp_file}")
if [[ "$return_code" != "0" ]]; then
message "Error $return_code adding $FIELD to database '$DATABASE' with query '$QUERY'. result: $result" true
else
QUERY="SELECT id FROM $TABLE WHERE $FIELD='$FROM';"
result=$(echo "$QUERY" | /usr/bin/env mysql "${DATABASE}")
resultID=$(echo "$result" | grep -v id)
return_code="${?}"
if [[ "$return_code" != "0" ]]; then
message "Error $return_code obtaining $FIELD id with query '$QUERY'. result: $result" true
exit 1
fi
fi
fi
fi
echo "${resultID}"
fi
fi
}
function FromID() {
FROM="$1"
SENDER_ID=$(GetRecordID "$FROM" senders sender mail "Getting Id for From $1")
echo "${SENDER_ID}"
}
function RecipientID() {
RECIPIENT="$1"
RECIPIENT_ID=$(GetRecordID "$FROM" recipients recipient mail "Getting Id for To $1")
echo "${RECIPIENT_ID}"
}
# Start processing.
message "Processing message 'in.$$' as user '$(whoami)'..."
cd "${inspect_dir}" || exit
return_code="${?}"
if [[ "$return_code" != "0" ]]; then
message "Error $return_code changing to directory '$inspect_dir'"
exit $EX_TEMPFAIL;
fi
cat >in.$$
return_code="${?}"
if [[ "$return_code" != "0" ]]; then
message "Cannot save mail to file"
exit $EX_TEMPFAIL
fi
message "Adding labels..."
/usr/bin/env python3 /etc/postfix/labeler.py in.$$ > in.$$.labeler.log
cp in.$$.labeler.log /var/spool/filter/last_message.eml.labeler.log -rfp
#Save last message
message "Saving as last_message.eml..."
cp in.$$ /var/spool/filter/last_message.eml -rfp
#Save copy of the messages. DANGEROUS!! Will consumpt disk space
message "Saving copy of message (this might fill the disk)..."
mkdir -p "/var/spool/filter/$(date +%Y%m%d)/"
cp in.$$ "/var/spool/filter/$(date +%Y%m%d)/$(date +%Y%m%d%H%M%S%N).eml" -rfp
# obtain headers
message "Obtaining headers..."
HEADERS=$(sed ':a;N;$!ba;s/\n\n.*//g' in.$$)
messageID=$(echo "$HEADERS" | sed ':a;N;$!ba;s/\n\s/ /g' | grep -m 1 -i "^message-id: " | sed 's/^message-id: //g' | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
message "message ID: '$messageID'"
FROMLONG=$(echo "$HEADERS" | sed ':a;N;$!ba;s/\n\s/ /g' | grep -m 1 "^From:" | sed "s/From: //" -)
SENDER_ID=$(FromID "$FROM")
message "From(long): '${FROMLONG}' (Id: $SENDER_ID)"
FROM=$(echo "${FROMLONG}" | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
message "From(short): '${FROM}'"
SUBJECT=$(echo "$HEADERS" | sed ':a;N;$!ba;s/\n\s/ /g' | grep -m 1 "^Subject:" | sed "s/Subject: //" - | sed "s/'/\'/g" - )
message "Subject: '$SUBJECT'"
RECIPIENT=$(echo "$HEADERS" | sed ':a;N;$!ba;s/\n\s/ /g' | grep -m 1 "^To:" | sed "s/To: //g" - | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
RECIPIENT_ID=$(RecipientID "$RECIPIENT")
message "TO: '$RECIPIENT'"
CCRECIPIENT=$(echo "$HEADERS" | sed ':a;N;$!ba;s/\n\s/ /g' | grep -m 1 "^Cc:" | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
message "CC: '$CCRECIPIENT'"
BCRECIPIENT=$(echo "$HEADERS" | sed ':a;N;$!ba;s/\n\s/ /g' | grep -m 1 "^Bcc:" | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
message "CCO: '$BCRECIPIENT'"
DELIVEREDTO=$(echo "$HEADERS" | sed ':a;N;$!ba;s/\n\s/ /g' | grep -m 1 "^Delivered-To:" | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
message "Delivered to: '$DELIVEREDTO'"
RETURNPATH=$(echo "$HEADERS" | sed ':a;N;$!ba;s/\n\s/ /g' | grep -m 1 "^Return-Path:" | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
message "Return path: '$RETURNPATH'"
REPLYTO=$(echo "$HEADERS" | sed ':a;N;$!ba;s/\n\s/ /g' | grep -m 1 "^Reply-To:" | sed "s/Sender: //g" - | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
message "Reply to: '$REPLYTO'"
XORIGINALSENDER=$(echo "$HEADERS" | sed ':a;N;$!ba;s/\n\s/ /g' |grep -m 1 "^X-Original-Sender:" | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
message "Reply to (original sender): '$XORIGINALSENDER'"
SENDER=$(echo "$HEADERS" | sed ':a;N;$!ba;s/\n\s/ /g' | grep -m 1 "^Sender:" | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
message "Reply to (sender): '$SENDER'"
XFORWARDEDTO=$(echo "$HEADERS" | sed ':a;N;$!ba;s/\n\s/ /g' | grep -m 1 "^X-Forwarded-To:" | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
message "Reply to (forwarded to): '$XFORWARDEDTO'"
XFORWARDEDFOR=$(echo "$HEADERS" | sed ':a;N;$!ba;s/\n\s/ /g' |grep -m 1 "^X-Forwarded-For:" | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
message "Reply to (forwarded for): '$XFORWARDEDFOR'"
XBEENTHERE=$(echo "$HEADERS" | sed ':a;N;$!ba;s/\n\s/ /g' | grep -m 1 "^X-BeenThere:" | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
message "Reply to (been there): '$XBEENTHERE'"
if grep -qi "^${FROM}$" "${DISCLAIMER_ADDRESSES}"; then
message "This is a mail from a local user '$FROM' with a disclaimer text, adding it to the message..."
DIRECTION=0
CURHOUR=$(date +%Y%m%d%H)
if [[ -e $in_stat_folder/$CURHOUR.stats ]]; then
CURHOURSTAT=$(cat "$in_stat_folder/$CURHOUR.stats")
else
CURHOURSTAT=0
fi
CURHOURSTAT=$((CURHOURSTAT + 1))
echo "$CURHOURSTAT" > "$in_stat_folder/$CURHOUR.stats"
QUERY="INSERT INTO messages (id, sender_id, subject, date, messageid) VALUES (NULL, '$SENDER_ID', '$SUBJECT', '$DATE', '$messageID');"
temp_file=$(mktemp /tmp/tmp.XXXX)
echo "$QUERY" | /usr/bin/env mysql -v -v -v mailview &> "${temp_file}"
return_code="${?}"
result=$(cat "${temp_file}")
if [[ "$return_code" != "0" ]]; then
message "Error $return_code while adding message to database 'mailview' with query '$QUERY'. result: '${result}'"
fi
QUERY="SELECT id FROM messages WHERE sender_id='$SENDER_ID' AND subject='$SUBJECT' AND date='$DATE';"
# MSG_ID=$(echo "$QUERY" | /usr/bin/env mysql mailview | grep -v id)
return_code="${?}"
if [[ "$return_code" != "0" ]]; then
message "Error $return_code while obtaining message id with query '$QUERY' from database 'mailview'"
fi
TMP_HTML_DISCLAIMER_FILE=$(/bin/mktemp /tmp/disclaimer.XXXXX)
if [[ -r /etc/postfix/$FROM.disclaimer.html ]]; then
HTML_DISCLAIMER="/etc/postfix/$FROM.disclaimer.html"
echo "${HTML_DISCLAIMER}" > /dev/null
fi
sed "s/id=MSGID/id=$DB_MSG_ID/g" $HTML_DISCLAIMER_FILE > "$TMP_HTML_DISCLAIMER_FILE"
TMP_HTML_DISCLAIMER_FILE_BIS=$(/bin/mktemp /tmp/disclaimer.XXXXX)
sed "s/SENDERID/$SENDER_ID/g" "$TMP_HTML_DISCLAIMER_FILE" > "$TMP_HTML_DISCLAIMER_FILE_BIS"
sed "s/RECIPIENTID/$RECIPIENT_ID/g" "$TMP_HTML_DISCLAIMER_FILE_BIS" > "$TMP_HTML_DISCLAIMER_FILE"
message "Disclaimer HTML at '$TMP_HTML_DISCLAIMER_FILE'"
if [[ -r /etc/postfix/$FROM.disclaimer.txt ]]; then
TEXT_DISCLAIMER=/etc/postfix/$FROM.disclaimer.txt
fi
message "Running altermime with: /usr/bin/altermime --input=in.$$ --log-syslog --log-stdout --disclaimer=$TEXT_DISCLAIMER --disclaimer-html=$TMP_HTML_DISCLAIMER_FILE --xheader=\"X-MTA: $(cat /etc/mailname)\""
ALTERMIMEresult=$(/usr/bin/altermime --input=in.$$ --log-syslog --log-stdout --disclaimer="$TEXT_DISCLAIMER" --disclaimer-html="$TMP_HTML_DISCLAIMER_FILE" --xheader="X-MTA: $(cat /etc/mailname)")
message "altermime result: $ALTERMIMEresult"
return_code="${?}"
if [[ "$return_code" != "0" ]]; then
message "Error $return_code message content rejected."
exit $EX_UNAVAILABLE;
fi
else
DIRECTION=1
message "Sender '$FROMLONG' ($FROM) is NOT in disclaimer addresses file"
CURHOUR=$(date +%Y%m%d%H)
if [[ -e $out_stat_folder/$CURHOUR.stats ]]; then
CURHOURSTAT=$(cat "$out_stat_folder/$CURHOUR.stats")
else
CURHOURSTAT=0
fi
CURHOURSTAT=$((CURHOURSTAT + 1))
echo $CURHOURSTAT > "$out_stat_folder/$CURHOUR.stats"
fi
#Stats
FROMID=$(GetRecordID "$FROM" mail_addresses mail_address mail "Get ID of From $FROM")
message "From $FROM with ID $FROMID"
if [[ "$RECIPIENT" != "" ]]; then
TOID=$(GetRecordID "$RECIPIENT" mail_addresses mail_address mail "Get ID of To $TO")
else
TOID="0"
fi
message "To $RECIPIENT with ID $TOID"
if [[ -z "$CCRECIPIENT" ]]; then
CCID=$(GetRecordID "$CCRECIPIENT" mail_addresses mail_address mail "Get ID of CC")
else
CCID="0"
fi
if [[ -z "$BCRECIPIENT" ]]; then
CCOID=$(GetRecordID "$BCRECIPIENT" mail_addresses mail_address mail "Get ID of BCC")
else
CCOID="0"
fi
if [[ -z "$DELIVEREDTO" ]]; then
DTID=$(GetRecordID "$DELIVEREDTO" mail_addresses mail_address mail "Get ID of DeliveredTo")
else
DTID="0"
fi
if [[ -z "$RETURNPATH" ]]; then
RPID=$(GetRecordID "$RETURNPATH" mail_addresses mail_address mail)
else
RPID="0"
fi
if [[ -z "$XORIGINALSENDER" ]]; then
XOSID=$(GetRecordID "$XORIGINALSENDER" mail_addresses mail_address mail)
else
XOSID="0"
fi
if [[ -z "$SENDER" ]]; then
SID=$(GetRecordID "$SENDER" mail_addresses mail_address mail)
else
SID="0"
fi
if [[ -z "$XFORWARDEDTO" ]]; then
XFTID=$(GetRecordID "$XFORWARDEDTO" mail_addresses mail_address mail)
else
XFTID="0"
fi
if [[ -z "$XFORWARDEDFOR" ]]; then
XFFID=$(GetRecordID "$XFORWARDEDFOR" mail_addresses mail_address mail)
else
XFFID="0"
fi
QUERY="INSERT INTO stats (msgid, from_id, to_id,cc_id, cco_id, subject, direction, delivered_to_id, return_path_id, s_original_sender_id, sender_id, x_forwarded_to, x_forwarded_for) VALUES ('$messageID',$FROMID, $TOID, $CCID, $CCOID, '$SUBJECT', $DIRECTION, $DTID, $RPID, $XOSID, $SID, $XFTID, $XFFID);"
temp_file=$(mktemp /tmp/tmp.XXXX)
echo "${QUERY}" | /usr/bin/env mysql -v -v -v mail &> "${temp_file}"
return_code="${?}"
result=$(cat "${temp_file}")
if [[ "${return_code}" != "0" ]]; then
message "Error ${return_code} saving stats with query '${QUERY}'. result: ${result}"
fi
finished=true

9
files/mail.local Normal file
View file

@ -0,0 +1,9 @@
[courier-auth]
enabled = true
[sasl]
enabled = true
port = smtp
filter = postfix-sasl
logpath = /var/log/mail.log
maxretry = 5

291
files/mysql_mail_db.sql Normal file
View file

@ -0,0 +1,291 @@
-- MySQL dump 10.19 Distrib 10.3.34-MariaDB, for debian-linux-gnu (x86_64)
--
-- Host: localhost Database: mail
-- ------------------------------------------------------
-- Server version 10.3.34-MariaDB-0ubuntu0.20.04.1
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `bannedcontent`
--
DROP TABLE IF EXISTS `bannedcontent`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `bannedcontent` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`creation_date` timestamp NOT NULL DEFAULT current_timestamp(),
`regexp` varchar(255) NOT NULL,
`action` varchar(20) NOT NULL DEFAULT 'DROP',
PRIMARY KEY (`id`),
UNIQUE KEY `regexp` (`regexp`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `bannedsenders`
--
DROP TABLE IF EXISTS `bannedsenders`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `bannedsenders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`sender` varchar(255) NOT NULL,
`creation_date` timestamp NOT NULL DEFAULT current_timestamp(),
`frommsgid` varchar(255) NOT NULL,
`banned` tinyint(1) NOT NULL DEFAULT 1,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=26705 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `bannedservers`
--
DROP TABLE IF EXISTS `bannedservers`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `bannedservers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`server` varchar(255) NOT NULL,
`creation_date` timestamp NOT NULL DEFAULT current_timestamp(),
`frommsgid` varchar(255) NOT NULL,
`banned` tinyint(1) NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `server` (`server`)
) ENGINE=InnoDB AUTO_INCREMENT=18537 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `bannedsubjects`
--
DROP TABLE IF EXISTS `bannedsubjects`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `bannedsubjects` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`subject` varchar(255) NOT NULL,
`count` int(11) NOT NULL DEFAULT 0,
`creation_date` timestamp NOT NULL DEFAULT current_timestamp(),
`frommsgid` varchar(255) NOT NULL,
`banned` tinyint(1) NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `subject` (`subject`)
) ENGINE=InnoDB AUTO_INCREMENT=14505 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `domains`
--
DROP TABLE IF EXISTS `domains`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `domains` (
`domain` varchar(50) NOT NULL,
`creationdate` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`domain`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `forwardings`
--
DROP TABLE IF EXISTS `forwardings`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `forwardings` (
`creation_date` timestamp NOT NULL DEFAULT current_timestamp(),
`source` varchar(80) NOT NULL,
`destination` text NOT NULL,
`porpouse` varchar(255) NOT NULL,
PRIMARY KEY (`source`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `logins`
--
DROP TABLE IF EXISTS `logins`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `logins` (
`creation_date` timestamp NOT NULL DEFAULT current_timestamp(),
`ip` varchar(16) NOT NULL,
`user` varchar(255) NOT NULL,
`ipxmlinfo` longtext NOT NULL,
`city` varchar(255) NOT NULL,
`region` varchar(255) NOT NULL,
`country` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `mail_addresses`
--
DROP TABLE IF EXISTS `mail_addresses`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `mail_addresses` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`mail_address` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `mail_address` (`mail_address`)
) ENGINE=InnoDB AUTO_INCREMENT=115336 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `notifiedmtas`
--
DROP TABLE IF EXISTS `notifiedmtas`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `notifiedmtas` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`mta_mail` varchar(255) NOT NULL,
`creation_date` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`,`mta_mail`)
) ENGINE=InnoDB AUTO_INCREMENT=18395 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `recipients`
--
DROP TABLE IF EXISTS `recipients`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `recipients` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`creation_date` timestamp NOT NULL DEFAULT current_timestamp(),
`recipient` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `recipient` (`recipient`)
) ENGINE=InnoDB AUTO_INCREMENT=1889 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `senders`
--
DROP TABLE IF EXISTS `senders`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `senders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`creation_date` timestamp NOT NULL DEFAULT current_timestamp(),
`sender` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `sender` (`sender`)
) ENGINE=InnoDB AUTO_INCREMENT=1885 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `stats`
--
DROP TABLE IF EXISTS `stats`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `stats` (
`msgid` varchar(255) NOT NULL,
`from_id` int(11) NOT NULL,
`to_id` int(11) NOT NULL,
`cc_id` int(11) NOT NULL,
`cco_id` int(11) NOT NULL,
`subject` varchar(255) NOT NULL,
`direction` tinyint(1) NOT NULL COMMENT '0 inbound, 1 outbound',
`delivered_to_id` int(11) NOT NULL,
`return_path_id` int(11) NOT NULL,
`s_original_sender_id` int(11) NOT NULL,
`sender_id` int(11) NOT NULL,
`x_forwarded_to` int(11) NOT NULL,
`x_forwarded_for` int(11) NOT NULL,
UNIQUE KEY `msgid` (`msgid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `transactions`
--
DROP TABLE IF EXISTS `transactions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `transactions` (
`start_timestamp` timestamp NOT NULL DEFAULT current_timestamp(),
`pid` int(11) NOT NULL,
`from_IP` varchar(15) NOT NULL,
`from_host` varchar(255) NOT NULL,
`transaction_id` varchar(12) NOT NULL DEFAULT 'unknown',
`message_id` varchar(255) NOT NULL DEFAULT 'unknown',
`from` varchar(255) NOT NULL DEFAULT 'unknown',
`size` int(11) NOT NULL DEFAULT 0,
`nrcpt` int(11) NOT NULL DEFAULT 0,
`to` varchar(255) NOT NULL DEFAULT 'unknown',
`relay` varchar(255) NOT NULL DEFAULT 'unknown',
`delay` int(11) NOT NULL DEFAULT 0,
`delays` varchar(22) NOT NULL DEFAULT 'unknown',
`dsn` varchar(7) NOT NULL DEFAULT 'unknown',
`status` varchar(255) NOT NULL DEFAULT 'unknown',
`orig_to` varchar(255) NOT NULL DEFAULT 'unknown',
`end_timestamp` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `transport`
--
DROP TABLE IF EXISTS `transport`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `transport` (
`domain` varchar(128) NOT NULL DEFAULT '',
`transport` varchar(128) NOT NULL DEFAULT '',
UNIQUE KEY `domain` (`domain`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `users`
--
DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `users` (
`email` varchar(80) NOT NULL,
`password` varchar(256) DEFAULT NULL,
`quota` bigint(20) DEFAULT 104857600000,
`fullname` varchar(255) NOT NULL,
PRIMARY KEY (`email`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2022-10-04 20:45:17

111
files/new_mail_alias Normal file
View file

@ -0,0 +1,111 @@
#!/bin/bash
# shellcheck disable=SC1091
[ -r /var/lib/from_repos/scripts/shared_functions.sh ] && . /var/lib/from_repos/scripts/shared_functions.sh
function usage() {
echo "Usage:"
echo "$(basename "${0}") -p|--porpouse <porpouse> [-o|--domain <DOMAIN>] [-l|--length <ALIAS_LENGTH>] [-e|--destination <DESTINATION>]"
echo ""
echo " -d|--debug Show more debug information."
echo " -p|--porpouse <PORPOUSE> Porpouse of this forwarding that will help you remember why you created it."
echo " -o|--domain <DOMAIN> Domain name for the alias. From the list:"
while read -r DOMAIN
do
echo " # ${DOMAIN}"
done <<< "$(echo 'select domain FROM mail.domains' | mysql mail|grep -v '^domain$')"
echo " -l|--length <ALIAS_LENGTH> Length of pseudo-random character of the alias."
echo " -e|--destination <DESTINATION> Destination of the forwarding."
echo " -a|--alias <ALIAS> Use a defined alias."
}
domain="susurrando.com"
alias_length=6
destination="ad@susurrando.com"
alias=""
while [ $# -gt 0 ]
do
case "${1}" in
"-h"|"-?"|"--help")
shift
usage
exit 0
;;
"-d"|"--debug")
shift
export DEBUG=true
;;
"-o"|"--domain")
shift
domain="${1}"
existing=$(echo "select domain FROM mail.domains WHERE domain = '${domain}';" | mysql mail | grep -v '^domain$')
if [ -z "${existing}" ]; then
message "The domain '${domain}' doesn't exist in the database. Add it first." p
echo "Current list of domains:"
while read -r DOMAIN
do
echo " # ${DOMAIN}"
done <<< "$(echo 'select domain FROM mail.domains' | mysql mail|grep -v '^domain$')"
exit 4
fi
shift
;;
"-l"|"--length")
shift
alias_length="${1}"
shift
;;
"-e"|"--destination")
shift
destination="${1}"
shift
;;
"-p"|"--porpouse")
shift
porpouse="${1}"
shift
;;
"-a"|"--alias")
shift
alias="${1}"
shift
;;
*)
message "Unknown parameter '$1'" p
shift
;;
esac
done
if [ -z "${porpouse}" ]; then
message "You have to provide a porpouse." p
usage
exit 1
fi
existing=$(echo "SELECT * FROM mail.forwardings where porpouse = '${porpouse}';" | mysql mail)
if [ -n "${existing}" ]; then
message "There is already an alias for that porpouse:" p
message "${existing}" p
exit 2
fi
if [ -n "${alias}" ]; then
existing=$(echo "SELECT source FROM mail.forwardings where source = '${alias}';" |mysql mail | grep -v '^source$')
if [ -n "${existing}" ]; then
message "Alias already exist in the database:" p
message "'${existing}'" p
exit 3
fi
fi
while [ "${alias}" == "" ]
do
random_string=$(LC_ALL=C tr -dc A-Za-z0-9 </dev/urandom | head -c "${alias_length}")
existing=$(echo "SELECT source FROM mail.forwardings where source = '${random_string}@${domain}';" |mysql mail |grep -v '^source$')
if [ -z "${existing}" ]; then
alias="${random_string}@${domain}"
message "Generated alias: '${alias}'"
else
message "Generated alias '${random_string}@${domain}' exists in the database."
message "'${existing}'"
fi
done
echo "INSERT INTO mail.forwardings (source, destination, porpouse) VALUES ('${alias}','${destination}','${porpouse}');"| mysql mail
message "Inserted forwarding from '${alias}' to '${destination}' for the porpouse of '${porpouse}'." p

75
files/new_mail_user.sh Executable file
View file

@ -0,0 +1,75 @@
#!/bin/bash
# shellcheck disable=SC1091
[ -r /var/lib/from_repos/scripts/shared_functions.sh ] && . /var/lib/from_repos/scripts/shared_functions.sh
fullname=''
quota=104857600000
db_name='mail'
DEFAULT_DOMAIN='susurrando.com'
function usage() {
message "$(basename "${0}") --user|-u <USEREMAIL> [--password|-p <PASSWORD>] [--fullname|-n <FULL USER NAME>] [--quota|-q <QUOTA>] [--debug|-d]" p
}
while [ "${#}" -gt 0 ]
do
case "${1}" in
'--user'|'-u')
shift
username="${1}"
shift
;;
'--password'|'-p')
shift
password="${1}"
shift
;;
'--full-name'|'-n')
shift
fullname="${1}"
shift
;;
'--quota'|'-q')
shift
quota=$(echo "${1}" | grep -o '[0-9]*' | head -n 1)
shift
;;
'--debug'|'-d')
shift
export DEBUG=true
;;
*)
parameters="${parameters} ${1}"
message "Ignoring parameter '${1}'." p
shift
;;
esac
done
if [[ "${username}" == "" ]]; then
usage
exit 65
fi
if [[ "${password}" == "" ]]; then
password=$(/usr/bin/pwgen -c -n -B 9 1)
message "User password is ${password}" p
fi
if ! [[ "${username}" =~ .*@[a-zA-Z0-9]*\.[a-zA-Z0-9]{2,} ]]
then
username="${username}@${DEFAULT_DOMAIN}"
message "Username: '${username}'" p
fi
query="insert into users (email, password, fullname, quota) VALUES ('${username}', ENCRYPT('${password}'), '${fullname}', ${quota});"
message "Calling query: '${query}'"
echo "${query}" | mysql "${db_name}"
error_code="${?}"
if [[ "${error_code}" != "0" ]]; then
message "Error ${error_code} while adding user to the mail database" p
exit "${error_code}"
else
message "Added sucessfully to mail database" p
echo "Welcome to Susurrando.com mail service" | mail -s "Welcome to Susurrando mail service" "${username}"
error_code="${?}"
if [[ "${error_code}" != "0" ]]; then
message "Error ${error_code} sending welcome message" p
exit "${error_code}"
fi
fi

6
files/postfix-sasl.conf Normal file
View file

@ -0,0 +1,6 @@
# Fail2Ban filter for postfix authentication failures
[INCLUDES]
before = common.conf
[Definition]
_daemon = postfix/smtpd
failregex = ^%(__prefix_line)swarning: [-._\w]+\[<HOST>\]: SASL (?:LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed(: [ A-Za-z0-9+/]*={0,2})?\s*$

11
files/remove_queued_messages.sh Executable file
View file

@ -0,0 +1,11 @@
#!/bin/bash
if [[ "$1" == "" ]]
then
echo "Indicate a string to search in the postqueue."
exit 54
fi
postqueue -p | grep -v '^-' | sed ':a;N;$!ba;s/\n\n/ENTER/g' | sed ':a;N;$!ba;s/\n/ /g' | sed 's/ENTER/\n/g' | grep "$1" |awk '{print($1)}' | while read -r KMSGID
do
echo "Removing message with ID '${KMSGID}'..."
postsuper -d "$KMSGID"
done

66
files/saslauthd Normal file
View file

@ -0,0 +1,66 @@
#
# Settings for saslauthd daemon
# Please read /usr/share/doc/sasl2-bin/README.Debian for details.
#
# Should saslauthd run automatically on startup? (default: no)
START=yes
# Description of this saslauthd instance. Recommended.
# (suggestion: SASL Authentication Daemon)
DESC="SASL Authentication Daemon"
# Short name of this saslauthd instance. Strongly recommended.
# (suggestion: saslauthd)
NAME="saslauthd"
# Which authentication mechanisms should saslauthd use? (default: pam)
#
# Available options in this Debian package:
# getpwent -- use the getpwent() library function
# kerberos5 -- use Kerberos 5
# pam -- use PAM
# rimap -- use a remote IMAP server
# shadow -- use the local shadow password file
# sasldb -- use the local sasldb database file
# ldap -- use LDAP (configuration is in /etc/saslauthd.conf)
#
# Only one option may be used at a time. See the saslauthd man page
# for more information.
#
# Example: MECHANISMS="pam"
MECHANISMS="pam"
#MECHANISMS="rimap"
# Additional options for this mechanism. (default: none)
# See the saslauthd man page for information about mech-specific options.
MECH_OPTIONS=""
# How many saslauthd processes should we run? (default: 5)
# A value of 0 will fork a new process for each connection.
THREADS=5
# Other options (default: -c -m /var/run/saslauthd)
# Note: You MUST specify the -m option or saslauthd won't run!
#
# WARNING: DO NOT SPECIFY THE -d OPTION.
# The -d option will cause saslauthd to run in the foreground instead of as
# a daemon. This will PREVENT YOUR SYSTEM FROM BOOTING PROPERLY. If you wish
# to run saslauthd in debug mode, please run it by hand to be safe.
#
# See /usr/share/doc/sasl2-bin/README.Debian for Debian-specific information.
# See the saslauthd man page and the output of 'saslauthd -h' for general
# information about these options.
#
# Example for chroot Postfix users: "-c -m /var/spool/postfix/var/run/saslauthd"
# Example for non-chroot Postfix users: "-c -m /var/run/saslauthd"
#
# To know if your Postfix is running chroot, check /etc/postfix/master.cf.
# If it has the line "smtp inet n - y - - smtpd" or "smtp inet n - - - - smtpd"
# then your Postfix is running in a chroot.
# If it has the line "smtp inet n - n - - smtpd" then your Postfix is NOT
# running in a chroot.
#OPTIONS="-c -m /var/run/saslauthd"
#OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd -r"
#OPTIONS="-c -m /var/spool/postfix/var/run/courier/authdaemon -r -d"
OPTIONS="-r -c -m /var/spool/postfix/var/run/saslauthd"

14
files/update_clients.sh Normal file
View file

@ -0,0 +1,14 @@
#!/bin/bash
declare clients=( hiljainen.susurrando.com www.susurrando.com oc.koti.site)
rm /etc/postfix/allowed_clients
touch /etc/postfix/allowed_clients
chown postfix.postfix /etc/postfix/allowed_clients
chmod o-rwx /etc/postfix/allowed_clients
for client in "${clients[@]}"
do
client_ip=$(dig "${client}" | grep -v '^;' |grep "\sA\s" | awk '{print($5)}')
echo "${client_ip}" >> /etc/postfix/allowed_clients
done

8
handlers/main.yml Normal file
View file

@ -0,0 +1,8 @@
---
- name: Refresh aliases
shell: newaliases
- name: Restart postfix
service:
name: postfix
state: restarted

207
tasks/configure.yml Normal file
View file

@ -0,0 +1,207 @@
---
- name: Create mailname file
copy:
dest: /etc/mailname
content: "{{ inventory_hostname }}"
backup: true
mode: '0644'
notify: Restart postfix
- name: Template file authmysqlrc
template:
src: templates/authmysqlrc.j2
dest: /etc/courier/authmysqlrc
owner: root
group: postfix
mode: '0550'
backup: true
notify: Restart postfix
- name: Template file mysql-body_checks.cf
template:
src: templates/mysql-body_checks.cf.j2
dest: /etc/postfix/mysql-body_checks.cf
owner: root
group: postfix
mode: '0550'
backup: true
notify: Restart postfix
- name: Template file mysql-virtual_domains.cf
template:
src: templates/mysql-virtual_domains.cf.j2
dest: /etc/postfix/mysql-virtual_domains.cf
owner: root
group: postfix
mode: '0550'
backup: true
notify: Restart postfix
- name: Template file mysql-virtual_email2email.cf
template:
src: templates/mysql-virtual_email2email.cf.j2
dest: /etc/postfix/mysql-virtual_email2email.cf
owner: root
group: postfix
mode: '0550'
backup: true
notify: Restart postfix
- name: Template file mysql-virtual_forwardings.cf
template:
src: templates/mysql-virtual_forwardings.cf.j2
dest: /etc/postfix/mysql-virtual_forwardings.cf
owner: root
group: postfix
mode: '0550'
backup: true
notify: Restart postfix
- name: Template file mysql-virtual_mailbox_limit_maps.cf
template:
src: templates/mysql-virtual_mailbox_limit_maps.cf.j2
dest: /etc/postfix/mysql-virtual_mailbox_limit_maps.cf
owner: root
group: postfix
mode: '0550'
backup: true
notify: Restart postfix
- name: Template file mysql-virtual_mailboxes.cf
template:
src: templates/mysql-virtual_mailboxes.cf.j2
dest: /etc/postfix/mysql-virtual_mailboxes.cf
owner: root
group: postfix
mode: '0550'
backup: true
notify: Restart postfix
- name: Template file mysql-virtual_transports.cf
template:
src: templates/mysql-virtual_transports.cf.j2
dest: /etc/postfix/mysql-virtual_transports.cf
owner: root
group: postfix
mode: '0550'
backup: true
notify: Restart postfix
- name: Template file smtp
template:
src: templates/smtp.j2
dest: /etc/pam.d/smtp
owner: root
group: postfix
mode: '0550'
backup: true
notify: Restart postfix
- name: Template file smtpd.conf
template:
src: templates/smtpd.conf.j2
dest: /etc/postfix/sasl/smtpd.conf
owner: root
group: postfix
mode: '0550'
backup: true
notify: Restart postfix
- name: Generate DH param certificate
shell:
cmd: /usr/bin/openssl dhparam -out /etc/ssl/private/dhparams.pem 2048 && /bin/chmod 600 /etc/ssl/private/dhparams.pem
creates: /etc/ssl/private/dhparams.pem
notify: Restart postfix
- name: Ensure SASL Authdaemond folder exists
file:
path: /var/spool/postfix/var/run/saslauthd
state: directory
owner: root
group: sasl
mode: '0770'
- name: Ensure Postfix spool folders exists
file:
path: /var/spool/postfix/var
state: directory
owner: root
group: root
mode: '0755'
- name: Ensure Postfix spool folders exists
file:
path: /var/spool/postfix/var/run/courier
state: directory
owner: root
group: root
mode: '0755'
- name: Ensure SASL authentication daemon starts
lineinfile:
path: /etc/default/saslauthd
regexp: '^START='
line: 'START=yes'
backup: true
create: true
- name: Ensure SASL authentication daemon spool directory match postfix
lineinfile:
path: /etc/default/saslauthd
regexp: '^OPTIONS='
line: 'OPTIONS="-r -c -m /var/spool/postfix/var/run/saslauthd"'
backup: true
create: true
- name: Ensure CRON job to update clients exists
cron:
name: "Update SMTPD trusted clients"
job: '/etc/postfix/scripts/update_clients.sh'
user: root
minute: '20'
- name: Ensure cron to check if authdaemond is stuck exists
cron:
name: check authdaemond stuck
job: /etc/postfix/scripts/authdaemond_check_stuck.sh
minute: '*/5'
state: absent
- name: Resolve host names
set_fact:
mail_own_networks: "{{ mail_own_networks + [ lookup('community.general.dig', item + '.', '@' + dns_resolver) ] }}"
loop: "{{ mail_own_networks_hosts }}"
- name: Remove duplicates
set_fact:
mail_own_networks: "{{ mail_own_networks | unique | select | list }}"
###################
# #
# This at the end #
# #
###################
- name: Configure postfix main.cf
template:
src: templates/main.cf.j2
dest: /etc/postfix/main.cf
owner: root
group: root
mode: '0644'
backup: true
notify: Restart postfix
- name: Configure postfix master.cf
template:
src: templates/master.cf.j2
dest: /etc/postfix/master.cf
owner: root
group: root
mode: '0644'
backup: true
notify: Restart postfix
- name: Ensure postifx service is started and enabled
service:
name: postfix
enabled: true
state: started

View file

@ -0,0 +1,78 @@
---
- name: Initialize fact create_database
set_fact:
create_database: true
- name: Check for existing database
shell: mysql -Bqe 'show databases'
register: databases
- name: Check for existing tables in database
shell: "mysql {{ mail_db_name }} -Bqe 'show tables'"
register: tables
when: "mail_db_name in databases.stdout"
- name: Update fact create_database
set_fact:
create_database: false
when:
- "mail_db_name in databases.stdout"
- "'transport' in tables.stdout"
- name: Copy database dump file
copy:
src: files/mysql_mail_db.sql
dest: /tmp
when: create_database
- name: Create a new database with name 'mail' from structure file
mysql_db:
name: "{{ mail_db_name }}"
state: import
target: /tmp/mysql_mail_db.sql
login_unix_socket: /var/run/mysqld/mysqld.sock
when: create_database
- name: Create database user with name '{{ mail_db_user}}' with mail database privileges
mysql_user:
name: "{{ mail_db_user}}"
password: "{{ mail_db_password }}"
priv: "{{ mail_db_name }}.*:ALL"
state: present
login_unix_socket: /var/run/mysqld/mysqld.sock
- name: Insert forwarding in database
community.mysql.mysql_query:
login_db: "{{ mail_db_name }}"
query: "INSERT INTO forwardings (source, destination, porpouse) VALUES ('{{ item['source'] }}', '{{ item['destination'] }}', '{{ item['porpouse'] }}')"
single_transaction: yes
login_unix_socket: /var/run/mysqld/mysqld.sock
ignore_errors: true
loop: "{{ mail_forwardings }}"
- name: Insert domains in database
community.mysql.mysql_query:
login_db: "{{ mail_db_name }}"
query: "INSERT INTO domains (domain) VALUES ('{{ item }}')"
single_transaction: yes
login_unix_socket: /var/run/mysqld/mysqld.sock
ignore_errors: true
loop: "{{ mail_domains }}"
- name: Insert users in database
community.mysql.mysql_query:
login_db: "{{ mail_db_name }}"
query: "INSERT INTO users (email, password, fullname, quota) VALUES ('{{ item['email'] }}', '{{ item['password'] }}', '{{ item['fullname'] }}', '{{ item['quota'] }}')"
single_transaction: yes
login_unix_socket: /var/run/mysqld/mysqld.sock
ignore_errors: true
loop: "{{ mail_users }}"
- name: Insert transports in database
community.mysql.mysql_query:
login_db: "{{ mail_db_name }}"
query: "INSERT INTO transport (domain, transport) VALUES ('{{ item['domain'] }}', '{{ item['transport'] }}')"
single_transaction: yes
login_unix_socket: /var/run/mysqld/mysqld.sock
ignore_errors: true
loop: "{{ mail_transports }}"

View file

@ -0,0 +1,55 @@
---
- name: Deploy script disclaimer.sh
copy:
src: files/disclaimer.sh
dest: /etc/postfix/scripts/disclaimer.sh
owner: postfix
group: root
mode: '0775'
backup: true
- name: Ensure filter group exists
group:
name: filter
- name: Ensure filter user exists
user:
name: filter
group: filter
create_home: false
home: /var/spool/filter
- name: Ensure filter home exists with the right permissions
file:
path: /var/spool/filter
state: directory
owner: filter
group: root
mode: 0770
- name: Ensure filter user can write disclaimer log
file:
path: /var/log/disclaimer.log
owner: filter
group: postfix
mode: 0660
state: touch
- name: Compress stored incoming messages labeler logs
shell: find /var/spool/filter/ -maxdepth 1 -type f -iname in.\*.log -exec bzip2 -z9 {} \;
- name: Compress stored incoming messages
shell: find /var/spool/filter/ -maxdepth 1 -type f -regex '^/var/spool/filter/in\.[0-9]*' -exec bzip2 -z9 {} \;
- name: Deploy script to compress stored messages
copy:
src: files/compress_stored_messages.sh
dest: /usr/local/bin/compress_stored_messages.sh
mode: 0755
backup: yes
- name: Ensure cron to compress stored messages exists
cron:
name: Compress Stored messages
job: /usr/local/bin/compress_stored_messages.sh
hour: '2'

13
tasks/configure_ufw.yml Normal file
View file

@ -0,0 +1,13 @@
---
- name: Allow traffic to Postfix
ufw:
rule: allow
name: Postfix
- name: Allow traffic to Postfix SMTPS
ufw:
rule: allow
name: Postfix SMTPS
- name: Allow traffic to Postfix Submission
ufw:
rule: allow
name: Postfix Submission

70
tasks/deploy_scripts.yml Normal file
View file

@ -0,0 +1,70 @@
---
- name: Ensure Postfix scripts folder exists
file:
path: /etc/postfix/scripts
state: directory
owner: root
group: postfix
mode: '0775'
- name: Ensure Courier scripts folder exists
file:
path: /etc/courier/scripts
state: directory
owner: root
group: root
mode: '0750'
- name: Template file letsencrypt_update.sh
template:
src: templates/letsencrypt_update.sh.j2
dest: /etc/courier/scripts/letsencrypt_update.sh
owner: root
group: root
mode: '0750'
backup: true
# - name: Deploy script authdaemond_check_stuck.sh
# copy:
# src: files/authdaemond_check_stuck.sh
# dest: /etc/postfix/scripts/authdaemond_check_stuck.sh
# owner: root
# group: root
# mode: '0750'
# backup: true
- name: Deploy script update_clients.sh
copy:
src: files/update_clients.sh
dest: /etc/postfix/scripts/update_clients.sh
owner: root
group: root
mode: '0750'
backup: true
- name: Deploy script remove_queues_messages.sh
copy:
src: files/remove_queued_messages.sh
dest: /etc/postfix/scripts/remove_queued_messages.sh
owner: root
group: root
mode: '0750'
backup: true
- name: Deploy new_mail_alias script
copy:
src: files/new_mail_alias
dest: /usr/local/bin/new_mail_alias
owner: root
group: root
mode: 0755
backup: true
- name: Deploy new_mail_user.sh script
copy:
src: files/new_mail_user.sh
dest: /usr/local/bin/new_mail_user.sh
owner: root
group: root
mode: 0755
backup: true

22
tasks/install.yml Normal file
View file

@ -0,0 +1,22 @@
---
- name: Ensure mail server software is present
apt:
name:
- postfix-mysql
- postfix-doc
- certbot
- libsasl2-2
- libsasl2-modules
- libsasl2-modules-sql
- sasl2-bin
- libpam-mysql
- openssl
- postfix-policyd-spf-python
- altermime
- gamin
- amavisd-new
- spamassassin
- opendkim
- opendkim-tools
state: latest
update_cache: true

27
tasks/main.yml Normal file
View file

@ -0,0 +1,27 @@
---
- name: Ensure installation of Postfix
include_tasks: install.yml
when: postfix_only_populate_db == False
- name: Ensure scripts are deployed
include_tasks: deploy_scripts.yml
when: postfix_only_populate_db == False
- name: Ensure configuration of Postfix
include_tasks: configure.yml
when: postfix_only_populate_db == False
- name: Ensure configuration of UFW
include_tasks: configure_ufw.yml
when:
- configure_ufw
- postfix_only_populate_db == False
- name: Ensure configuration of Postfix disclaimer
include_tasks: configure_disclaimer.yml
when:
- postfix_only_populate_db == False
- name: Ensure configuration of Postfix database
include_tasks: configure_database.yml
when: inventory_hostname == ansible_play_hosts[0]

17
templates/authmysqlrc.j2 Normal file
View file

@ -0,0 +1,17 @@
MYSQL_SERVER localhost
MYSQL_USERNAME {{ mail_db_user}}
MYSQL_PASSWORD {{ mail_db_password }}
MYSQL_PORT 0
MYSQL_DATABASE {{ mail_db_name }}
MYSQL_USER_TABLE users
MYSQL_CRYPT_PWFIELD password
#MYSQL_CLEAR_PWFIELD password
MYSQL_UID_FIELD 5000
MYSQL_GID_FIELD 5000
MYSQL_LOGIN_FIELD email
MYSQL_HOME_FIELD "/home/vmail"
MYSQL_MAILDIR_FIELD CONCAT(SUBSTRING_INDEX(email,'@',-1),'/',SUBSTRING_INDEX(email,'@',1),'/')
#MYSQL_NAME_FIELD
MYSQL_QUOTA_FIELD quota
MYSQL_OPT 0
##NAME: MARKER:0 # # Do not remove this section from this configuration file. This section # must be present at the end of this file.

269
templates/disclaimer.sh.j2 Normal file
View file

@ -0,0 +1,269 @@
#!/bin/bash
# Clean up when done or when aborting.
trap "rm -f in.$$" 0 1 2 3 15
INSPECT_DIR=/var/spool/filter
SENDMAIL=/usr/sbin/sendmail
LOGFILE=/var/log/disclaimer.log
OUTSTATFOLDER=/var/spool/filter/out.stats
mkdir -p $OUTSTATFOLDER
INSTATFOLDER=/var/spool/filter/in.stats
mkdir -p $INSTATFOLDER
DISCLAIMER_ADDRESSES=/etc/postfix/disclaimer_addressess
TEXT_DISCLAIMER=/etc/postfix/text_disclaimer.txt
HTML_DISCLAIMER_FILE=/etc/postfix/html_disclaimer.htm
# Exit codes from <sysexits.h>
EX_TEMPFAIL=75
EX_UNAVAILABLE=69
DATE=$(date +%s)
APP=$(basename $0 .sh)
function Message() {
HIDE=$2
CDATE=$(date +%s)
MSG="[$$] $CDATE $1"
if [[ "$HIDE" != "true" ]]; then
echo "$MSG"
fi
/usr/bin/env logger -t "$APP" "$MSG"
echo "$MSG" >> $LOGFILE
#logger -t disclaimer.sh "$MSG"
}
#GetRecordID $FROM mail_addresses mail_address mail
function GetRecordID() {
TEXT="$1"
if [[ "$TEXT" == "" ]]; then
echo NULL
else
TABLE="$2"
FIELD="$3"
DATABASE="$4"
if [[ "$DATABASE" == "" ]]; then
Message "No database indicated." true
else
ACTION="$5"
QUERY="SELECT id FROM $TABLE WHERE $FIELD='$TEXT';"
RESULT=$(echo "$QUERY" | /usr/bin/env mysql --defaults-file=/var/spool/filter/.my.cnf $DATABASE)
RESULTID=$(echo "$RESULT" | grep -v id)
error_code=$?
if [[ "$error_code" != "0" ]]; then
Message "Error getting $FIELD id for $TEXT from database with query '$QUERY'. Result: $RESULT" true
else
if [[ "$RESULTID" == "" ]]; then
QUERY="INSERT INTO $TABLE (id, $FIELD) VALUES (NULL, '$TEXT');"
RESULT=$(echo "$QUERY" | /usr/bin/env mysql --defaults-file=/var/spool/filter/.my.cnf $DATABASE)
error_code=$?
if [[ "$error_code" != "0" ]]; then
Message "Error $error_code adding $FIELD to database with query '$QUERY'. Result: $RESULT" true
else
QUERY="SELECT id FROM $TABLE WHERE $FIELD='$FROM';"
RESULT=$(echo "$QUERY" | /usr/bin/env mysql --defaults-file=/var/spool/filter/.my.cnf $DATABASE)
RESULTID=$(echo "$RESULT" | grep -v id)
error_code=$?
if [[ "$error_code" != "0" ]]; then
Message "Error $error_code obtaining $FIELD id with query '$QUERY'. Result: $RESULT" true
fi
fi
fi
fi
echo $RESULTID
fi
fi
}
function FromID() {
FROM=$1
SENDER_ID=$(GetRecordID $FROM senders sender mailview "Getting Id for From $1")
echo $SENDER_ID
}
function RecipientID() {
RECIPIENT=$1
RECIPIENT_ID=$(GetRecordID $FROM recipients recipient mailview "Getting Id for To $1")
echo $RECIPIENT_ID
}
# Start processing.
Message "Processing message 'in.$$'"
cd $INSPECT_DIR
error_code=$?
if [[ "$error_code" != "0" ]]; then
Message "Error $error_code changing to directory '$INSPECT_DIR'"
exit $EX_TEMPFAIL;
fi
cat >in.$$
error_code=$?
if [[ "$error_code" != "0" ]]; then
Message "Cannot save mail to file"
exit $EX_TEMPFAIL
fi
#Save last message
cp in.$$ /var/spool/filter/last_message.eml -rfp
#Save copy of the messages. DANGEROUS!! Will consumpt disk space
mkdir -p /var/spool/filter/)date +%Y%m%d)/
cp in.$$ /var/spool/filter/)date +%Y%m%d)/)date +%Y%m%d%H%M%S%N).eml -rfp
# obtain header
FROMLONG=$(grep -m 1 "From:" in.$$ | sed "s/From://" -)
FROM=$(echo $FROMLONG | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
SENDER_ID=$(FromID $FROM)
Message "From: $FROMLONG (Id: $SENDER_ID)"
MESSAGEID=$(grep -m 1 -i "^Message-id:" in.$$ | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
Message "Message ID: $MESSAGEID"
SUBJECT=$(grep -m 1 "Subject:" in.$$ | sed "s/Subject://" - | sed "s/'/\'/g" - )
Message "Subject: $SUBJECT"
RECIPIENT=$(grep -m 1 "^To:" in.$$ | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
RECIPIENT_ID=$(RecipientID $RECIPIENT)
Message "TO: $RECIPIENT"
CCRECIPIENT=$(grep -m 1 "Cc:" in.$$ | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
Message "CC: $CCRECIPIENT"
BCRECIPIENT=$(grep -m 1 "Bcc:" in.$$ | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
Message "CCO: $BCRECIPIENT"
DELIVEREDTO=$(grep -m 1 "Delivered-To:" in.$$ | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
Message "Delivered to: $DELIVEREDTO"
RETURNPATH=$(grep -m 1 "Return-Path:" in.$$ | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
Message "Return path: $RETURNPATH"
REPLYTO=$(grep -m 1 "Reply-To:" in.$$ | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
Message "Reply to: $REPLYTO"
XORIGINALSENDER=$(grep -m 1 "X-Original-Sender:" in.$$ | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
Message "Reply to: $XORIGINALSENDER"
SENDER=$(grep -m 1 "Sender:" in.$$ | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
Message "Reply to: $SENDER"
XFORWARDEDTO=$(grep -m 1 "X-Forwarded-To:" in.$$ | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
Message "Reply to: $XFORWARDEDTO"
XFORWARDEDFOR=$(grep -m 1 "X-Forwarded-For:" in.$$ | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
Message "Reply to: $XFORWARDEDFOR"
XBEENTHERE=$(grep -m 1 "X-BeenThere:" in.$$ | cut -d "<" -f 2 | cut -d ">" -f 1 | sed "s/'/\'/g" - )
Message "Reply to: $XBEENTHERE"
if [[ $(grep -wi ^${FROM}$ ${DISCLAIMER_ADDRESSES}) ]]; then
DIRECTION=0
CURHOUR=$(date +%Y%m%d%H)
if [[ -e $INSTATFOLDER/$CURHOUR.stats ]]; then
CURHOURSTAT=$(cat $INSTATFOLDER/$CURHOUR.stats)
else
CURHOURSTAT=0
fi
CURHOURSTAT=$(expr $CURHOURSTAT + 1)
echo $CURHOURSTAT > $INSTATFOLDER/$CURHOUR.stats
Message "Sender '$FROMLONG' ($FROM) is in disclaimer addresses file"
QUERY="INSERT INTO messages (id, sender_id, subject, date, messageid) VALUES (NULL, '$SENDER_ID', '$SUBJECT', '$DATE', '$MESSAGEID');"
echo "$QUERY" | /usr/bin/env mysql --defaults-file=/var/spool/filter/.my.cnf mailview
error_code=$?
if [[ "$error_code" != "0" ]]; then
Message "Error $error_code while adding message to database with query '$QUERY'"
fi
QUERY="SELECT id FROM messages WHERE sender_id='$SENDER_ID' AND subject='$SUBJECT' AND date='$DATE';"
MSG_ID=$(echo "$QUERY" | /usr/bin/env mysql --defaults-file=/var/spool/filter/.my.cnf mailview | grep -v id)
error_code=$?
if [[ "$error_code" != "0" ]]; then
Message "Error $error_code while obtaining message id with query '$QUERY'"
fi
TMP_HTML_DISCLAIMER_FILE=$(/bin/mktemp /tmp/disclaimer.XXXXX)
if [[ -r /etc/postfix/$FROM.disclaimer.html ]]; then
HTML_DISCLAIMER=/etc/postfix/$FROM.disclaimer.html
fi
sed "s/id=MSGID/id=$DB_MSG_ID/g" $HTML_DISCLAIMER_FILE > $TMP_HTML_DISCLAIMER_FILE
TMP_HTML_DISCLAIMER_FILE_BIS=$(/bin/mktemp /tmp/disclaimer.XXXXX)
sed "s/SENDERID/$SENDER_ID/g" $TMP_HTML_DISCLAIMER_FILE > $TMP_HTML_DISCLAIMER_FILE_BIS
sed "s/RECIPIENTID/$RECIPIENT_ID/g" $TMP_HTML_DISCLAIMER_FILE_BIS > $TMP_HTML_DISCLAIMER_FILE
Message "Disclaimer HTML at '$TMP_HTML_DISCLAIMER_FILE'"
if [[ -r /etc/postfix/$FROM.disclaimer.txt ]]; then
TEXT_DISCLAIMER=/etc/postfix/$FROM.disclaimer.txt
fi
ALTERMIMERESULT=$(/usr/bin/altermime --input=in.$$
--log-syslog
--log-stdout
--disclaimer=$TEXT_DISCLAIMER
--disclaimer-html=$TMP_HTML_DISCLAIMER_FILE
--xheader="X-MTA: mudito.susurrando.com")
Message "altermime result: $ALTERMIMERESULT"
error_code=$?
if [[ "$error_code" != "0" ]]; then
Message "Error $error_code Message content rejected."
exit $EX_UNAVAILABLE;
fi
else
DIRECTION=1
Message "Sender '$FROMLONG' ($FROM) is NOT in disclaimer addresses file"
CURHOUR=$(date +%Y%m%d%H)
if [[ -e $OUTSTATFOLDER/$CURHOUR.stats ]]; then
CURHOURSTAT=$(cat $OUTSTATFOLDER/$CURHOUR.stats)
else
CURHOURSTAT=0
fi
CURHOURSTAT=$(expr $CURHOURSTAT + 1)
echo $CURHOURSTAT > $OUTSTATFOLDER/$CURHOUR.stats
fi
#Stats
FROMID=$(GetRecordID $FROM mail_addresses mail_address mail "Get ID of From $FROM")
Message "From $FROM with ID $FROMID"
if [[ "$RECIPIENT" != "" ]]; then
TOID=$(GetRecordID $RECIPIENT mail_addresses mail_address mail "Get ID of To $TO")
else
TOID="0"
fi
Message "To $RECIPIENT with ID $TOID"
if [[ "$CCRECIPIENT" != "" ]]; then
CCID=$(GetRecordID $CCRECIPIENT mail_addresses mail_address mail "Get ID of CC")
else
CCID="0"
fi
if [[ "$BCRECIPIENT" != "" ]]; then
CCOID=$(GetRecordID $BCRECIPIENT mail_addresses mail_address mail "Get ID of BCC")
else
CCOID="0"
fi
if [[ "$DELIVEREDTO" != "" ]]; then
DTID=$(GetRecordID $DELIVEREDTO mail_addresses mail_address mail "Get ID of DeliveredTo")
else
DTID="0"
fi
if [[ "$RETURNPATH" != "" ]]; then
RPID=$(GetRecordID $RETURNPATH mail_addresses mail_address mail)
else
RPID="0"
fi
if [[ "$XORIGINALSENDER" != "" ]]; then
XOSID=$(GetRecordID $XORIGINALSENDER mail_addresses mail_address mail)
else
XOSID="0"
fi
if [[ "$SENDER" != "" ]]; then
SID=$(GetRecordID $SENDER mail_addresses mail_address mail)
else
SID="0"
fi
if [[ "$XFORWARDEDTO" != "" ]]; then
XFTID=$(GetRecordID $XFORWARDEDTO mail_addresses mail_address mail)
else
XFTID="0"
fi
if [[ "$XFORWARDEDFOR" != "" ]]; then
XFFID=$(GetRecordID $XFORWARDEDFOR mail_addresses mail_address mail)
else
XFFID="0"
fi
QUERY="INSERT INTO stats (msgid, from_id, to_id,cc_id, cco_id, subject, direction, delivered_to_id, return_path_id, s_original_sender_id, sender_id, x_forwarded_to, x_forwarded_for) VALUES ('$MESSAGEID',$FROMID, $TOID, $CCID, $CCOID, '$SUBJECT', $DIRECTION, $DTID, $RPID, $XOSID, $SID, $XFTID, $XFFID);"
RESULT=$(echo "$QUERY" | /usr/bin/env mysql --defaults-file=/var/spool/filter/.my.cnf mail)
error_code=$?
if [[ "$error_code" != "0" ]]; then
Message "Error $error_code saving stats with query '$QUERY'. Result: $RESULT"
fi
Message "Arguments: $*"
$SENDMAIL "$@" <in.$$
error_code=$?
if [[ "$error_code" != "0" ]]; then
Message "Error $error_code sending file to sendmail"
exit $error_code
fi
exit 0

View file

@ -0,0 +1,11 @@
#/bin/bash
FQDN='{{ mail_server_fqdn }}'
cat "/etc/letsencrypt/live/${FQDN}/privkey.pem" "/etc/letsencrypt/live/${FQDN}/fullchain.pem" > /etc/courier/${FQDN}.imapd.pem
chmod 600 "/etc/courier/${FQDN}.imapd.pem"
chown courier.courier "/etc/courier/${FQDN}.imapd.pem"
cp "/etc/letsencrypt/live/${FQDN}/privkey.pem" /etc/postfix/smtpd.key -rfpL
chmod 0600 /etc/postfix/smtpd.key
chown root.courier /etc/postfix/smtpd.key
cp "/etc/letsencrypt/live/${FQDN}/fullchain.pem" /etc/postfix/smtpd.crt -rfpL
chmod 0600 /etc/postfix/smtpd.crt
chown root.courier /etc/postfix/smtpd.crt

11
templates/main.cf.j2 Normal file
View file

@ -0,0 +1,11 @@
{% for key, value in postfix_config.items() %}
{% if value == True %}
{{ key }} = yes
{% else %}
{% if value == False %}
{{ key }} = no
{% else %}
{{ key }} = {{ value }}
{% endif %}
{% endif %}
{% endfor %}

142
templates/master.cf.j2 Normal file
View file

@ -0,0 +1,142 @@
#
# Postfix master process configuration file.
# ATTENTION! Managed by Ansible
# For details on the format
# of the file, see the master(5) manual page (command: "man 5 master").
#
# Do not forget to execute "postfix reload" after editing this file.
#
# ==========================================================================
# service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (no) (never) (100)
# ==========================================================================
# SMTP: Port 25
smtp inet n - y - - smtpd
# -o content_filter=filter:
# Submission: Port 587
submission inet n - y - - smtpd
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
-o content_filter=filter:
# SMTPS: Port 465
smtps inet n - y - - smtpd
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
-o content_filter=filter:
#628 inet n - y - - qmqpd
pickup unix n - y 60 1 pickup
cleanup unix n - y - 0 cleanup
qmgr unix n - n 300 1 qmgr
#qmgr unix n - n 300 1 oqmgr
tlsmgr unix - - y 1000? 1 tlsmgr
rewrite unix - - y - - trivial-rewrite
bounce unix - - y - 0 bounce
defer unix - - y - 0 bounce
trace unix - - y - 0 bounce
verify unix - - y - 1 verify
flush unix n - y 1000? 0 flush
proxymap unix - - n - - proxymap
proxywrite unix - - n - 1 proxymap
smtp unix - - y - - smtp
# When relaying mail as backup MX, disable fallback_relay to avoid MX loops
relay unix - - y - - smtp
-o smtp_fallback_relay=
# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
showq unix n - y - - showq
error unix - - y - - error
retry unix - - y - - error
discard unix - - y - - discard
local unix - n n - - local
virtual unix - n n - - virtual
lmtp unix - - y - - lmtp
anvil unix - - y - 1 anvil
scache unix - - y - 1 scache
#
# ====================================================================
# Interfaces to non-Postfix software. Be sure to examine the manual
# pages of the non-Postfix software to find out what options it wants.
#
# Many of the following services use the Postfix pipe(8) delivery
# agent. See the pipe(8) man page for information about ${recipient}
# and other message envelope options.
# ====================================================================
#
# maildrop. See the Postfix MAILDROP_README file for details.
# Also specify in main.cf: maildrop_destination_recipient_limit=1
#
maildrop unix - n n - - pipe
flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
#
# ====================================================================
#
# Recent Cyrus versions can use the existing "lmtp" master.cf entry.
#
# Specify in cyrus.conf:
# lmtp cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4
#
# Specify in main.cf one or more of the following:
# mailbox_transport = lmtp:inet:localhost
# virtual_transport = lmtp:inet:localhost
#
# ====================================================================
#
# Cyrus 2.1.5 (Amos Gouaux)
# Also specify in main.cf: cyrus_destination_recipient_limit=1
#
#cyrus unix - n n - - pipe
# user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
#
# ====================================================================
# Old example of delivery via Cyrus.
#
#old-cyrus unix - n n - - pipe
# flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user}
#
# ====================================================================
#
# See the Postfix UUCP_README file for configuration details.
#
uucp unix - n n - - pipe
flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
#
# Other external delivery methods.
#
ifmail unix - n n - - pipe
flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
bsmtp unix - n n - - pipe
flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
scalemail-backend unix - n n - 2 pipe
flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
mailman unix - n n - - pipe
flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
${nexthop} ${user}
amavis unix y y y - 2 smtp
-o smtp_data_done_timeout=1200
-o smtp_send_xforward_command=yes
127.0.0.1:10025 inet n y y - - smtpd
-o content_filter=
-o local_recipient_maps=
-o relay_recipient_maps=
-o smtpd_restriction_classes=
-o smtpd_client_restrictions=
-o smtpd_helo_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o mynetworks=127.0.0.0/8
-o strict_rfc821_envelopes=yes
-o receive_override_options=no_unknown_recipient_checks,no_header_body_checks
filter unix - n n - - pipe
flags=Rq user=filter argv=/etc/postfix/scripts/disclaimer.sh -f ${sender} -- ${recipient}
policy-spf unix - n n - - spawn
user=nobody argv=/usr/bin/policyd-spf
greypolicy unix - n n - - spawn
user=nobody argv=/usr/bin/perl
/usr/local/libexec/postfix/greylist.pl
{{ postfix_master_extra }}

View file

@ -0,0 +1,5 @@
user = {{ mail_db_user}}
password = {{ mail_db_password }}
dbname = {{ mail_db_name }}
query = SELECT action FROM bannedcontent WHERE regexp='%s'
hosts = 127.0.0.1

View file

@ -0,0 +1,5 @@
user = {{ mail_db_user}}
password = {{ mail_db_password }}
dbname = {{ mail_db_name }}
query = SELECT domain AS virtuald FROM domains WHERE domain='%s'
hosts = 127.0.0.1

View file

@ -0,0 +1,5 @@
user = {{ mail_db_user}}
password = {{ mail_db_password }}
dbname = {{ mail_db_name }}
query = SELECT email FROM users WHERE email='%s'
hosts = 127.0.0.1

View file

@ -0,0 +1,5 @@
user = {{ mail_db_user}}
password = {{ mail_db_password }}
dbname = {{ mail_db_name }}
query = SELECT destination FROM forwardings WHERE source='%s'
hosts = 127.0.0.1

View file

@ -0,0 +1,5 @@
user = {{ mail_db_user}}
password = {{ mail_db_password }}
dbname = {{ mail_db_name }}
query = SELECT quota FROM users WHERE email='%s'
hosts = 127.0.0.1

View file

@ -0,0 +1,5 @@
user = {{ mail_db_user}}
password = {{ mail_db_password }}
dbname = {{ mail_db_name }}
query = SELECT CONCAT(SUBSTRING_INDEX(email,'@',-1),'/',SUBSTRING_INDEX(email,'@',1),'/') FROM users WHERE email='%s'
hosts = 127.0.0.1

View file

@ -0,0 +1,5 @@
user = {{ mail_db_user}}
password = {{ mail_db_password }}
dbname = {{ mail_db_name }}
query = SELECT transport FROM transport WHERE domain='%s'
hosts = 127.0.0.1

2
templates/smtp.j2 Normal file
View file

@ -0,0 +1,2 @@
auth required pam_mysql.so user={{ mail_db_user}} passwd={{ mail_db_password }} host=127.0.0.1 db={{ mail_db_name }} table=users usercolumn=email passwdcolumn=password crypt=1
account sufficient pam_mysql.so user={{ mail_db_user}} passwd={{ mail_db_password }} host=127.0.0.1 db={{ mail_db_name }} table=users usercolumn=email passwdcolumn=password crypt=1

11
templates/smtpd.conf.j2 Normal file
View file

@ -0,0 +1,11 @@
pwcheck_method: saslauthd
mech_list: plain login
allow_plaintext: true
auxprop_plugin: sql
sql_engine: mysql
sql_hostnames: 127.0.0.1
sql_user: {{ mail_db_user}}
sql_passwd: {{ mail_db_password }}
sql_database: {{ mail_db_name }}
sql_select: select password from users where email = '%u@%r'
log_level: 9