commit abff60dcfe2a92e5d3aa99b40d857003b7c5f048 Author: Antonio J. Delgado Date: Tue Oct 11 10:19:06 2022 +0300 Initial commit with previous code diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..8dcd19a --- /dev/null +++ b/defaults/main.yml @@ -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 \ No newline at end of file diff --git a/files/compress_stored_messages.sh b/files/compress_stored_messages.sh new file mode 100644 index 0000000..57ead89 --- /dev/null +++ b/files/compress_stored_messages.sh @@ -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]*$")" \ No newline at end of file diff --git a/files/disclaimer.sh b/files/disclaimer.sh new file mode 100644 index 0000000..0236df3 --- /dev/null +++ b/files/disclaimer.sh @@ -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} > "${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 +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 diff --git a/files/mail.local b/files/mail.local new file mode 100644 index 0000000..8477f44 --- /dev/null +++ b/files/mail.local @@ -0,0 +1,9 @@ +[courier-auth] +enabled = true + +[sasl] +enabled = true +port = smtp +filter = postfix-sasl +logpath = /var/log/mail.log +maxretry = 5 \ No newline at end of file diff --git a/files/mysql_mail_db.sql b/files/mysql_mail_db.sql new file mode 100644 index 0000000..7b64c87 --- /dev/null +++ b/files/mysql_mail_db.sql @@ -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 diff --git a/files/new_mail_alias b/files/new_mail_alias new file mode 100644 index 0000000..aadb444 --- /dev/null +++ b/files/new_mail_alias @@ -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 [-o|--domain ] [-l|--length ] [-e|--destination ]" + echo "" + echo " -d|--debug Show more debug information." + echo " -p|--porpouse Porpouse of this forwarding that will help you remember why you created it." + echo " -o|--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 Length of pseudo-random character of the alias." + echo " -e|--destination Destination of the forwarding." + echo " -a|--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 [--password|-p ] [--fullname|-n ] [--quota|-q ] [--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 diff --git a/files/postfix-sasl.conf b/files/postfix-sasl.conf new file mode 100644 index 0000000..884e16f --- /dev/null +++ b/files/postfix-sasl.conf @@ -0,0 +1,6 @@ +# Fail2Ban filter for postfix authentication failures +[INCLUDES] +before = common.conf +[Definition] +_daemon = postfix/smtpd +failregex = ^%(__prefix_line)swarning: [-._\w]+\[\]: SASL (?:LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed(: [ A-Za-z0-9+/]*={0,2})?\s*$ \ No newline at end of file diff --git a/files/remove_queued_messages.sh b/files/remove_queued_messages.sh new file mode 100755 index 0000000..fb7f895 --- /dev/null +++ b/files/remove_queued_messages.sh @@ -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 \ No newline at end of file diff --git a/files/saslauthd b/files/saslauthd new file mode 100644 index 0000000..c4446a8 --- /dev/null +++ b/files/saslauthd @@ -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" diff --git a/files/update_clients.sh b/files/update_clients.sh new file mode 100644 index 0000000..6364483 --- /dev/null +++ b/files/update_clients.sh @@ -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 \ No newline at end of file diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..965d8f7 --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,8 @@ +--- +- name: Refresh aliases + shell: newaliases + +- name: Restart postfix + service: + name: postfix + state: restarted \ No newline at end of file diff --git a/tasks/configure.yml b/tasks/configure.yml new file mode 100644 index 0000000..7e157be --- /dev/null +++ b/tasks/configure.yml @@ -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 \ No newline at end of file diff --git a/tasks/configure_database.yml b/tasks/configure_database.yml new file mode 100644 index 0000000..f7aec6e --- /dev/null +++ b/tasks/configure_database.yml @@ -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 }}" diff --git a/tasks/configure_disclaimer.yml b/tasks/configure_disclaimer.yml new file mode 100644 index 0000000..6537cc7 --- /dev/null +++ b/tasks/configure_disclaimer.yml @@ -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' diff --git a/tasks/configure_ufw.yml b/tasks/configure_ufw.yml new file mode 100644 index 0000000..fcf412c --- /dev/null +++ b/tasks/configure_ufw.yml @@ -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 diff --git a/tasks/deploy_scripts.yml b/tasks/deploy_scripts.yml new file mode 100644 index 0000000..c1b6fe4 --- /dev/null +++ b/tasks/deploy_scripts.yml @@ -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 \ No newline at end of file diff --git a/tasks/install.yml b/tasks/install.yml new file mode 100644 index 0000000..3bdf408 --- /dev/null +++ b/tasks/install.yml @@ -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 \ No newline at end of file diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..8292c00 --- /dev/null +++ b/tasks/main.yml @@ -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] \ No newline at end of file diff --git a/templates/authmysqlrc.j2 b/templates/authmysqlrc.j2 new file mode 100644 index 0000000..2876102 --- /dev/null +++ b/templates/authmysqlrc.j2 @@ -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. diff --git a/templates/disclaimer.sh.j2 b/templates/disclaimer.sh.j2 new file mode 100644 index 0000000..1434cea --- /dev/null +++ b/templates/disclaimer.sh.j2 @@ -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 +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 "$@" /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 diff --git a/templates/main.cf.j2 b/templates/main.cf.j2 new file mode 100644 index 0000000..4ded3f9 --- /dev/null +++ b/templates/main.cf.j2 @@ -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 %} \ No newline at end of file diff --git a/templates/master.cf.j2 b/templates/master.cf.j2 new file mode 100644 index 0000000..3d4ac13 --- /dev/null +++ b/templates/master.cf.j2 @@ -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 }} \ No newline at end of file diff --git a/templates/mysql-body_checks.cf.j2 b/templates/mysql-body_checks.cf.j2 new file mode 100644 index 0000000..2e22d82 --- /dev/null +++ b/templates/mysql-body_checks.cf.j2 @@ -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 diff --git a/templates/mysql-virtual_domains.cf.j2 b/templates/mysql-virtual_domains.cf.j2 new file mode 100644 index 0000000..3b2f4c4 --- /dev/null +++ b/templates/mysql-virtual_domains.cf.j2 @@ -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 diff --git a/templates/mysql-virtual_email2email.cf.j2 b/templates/mysql-virtual_email2email.cf.j2 new file mode 100644 index 0000000..08efe82 --- /dev/null +++ b/templates/mysql-virtual_email2email.cf.j2 @@ -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 diff --git a/templates/mysql-virtual_forwardings.cf.j2 b/templates/mysql-virtual_forwardings.cf.j2 new file mode 100644 index 0000000..db61e71 --- /dev/null +++ b/templates/mysql-virtual_forwardings.cf.j2 @@ -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 diff --git a/templates/mysql-virtual_mailbox_limit_maps.cf.j2 b/templates/mysql-virtual_mailbox_limit_maps.cf.j2 new file mode 100644 index 0000000..ea6a6af --- /dev/null +++ b/templates/mysql-virtual_mailbox_limit_maps.cf.j2 @@ -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 diff --git a/templates/mysql-virtual_mailboxes.cf.j2 b/templates/mysql-virtual_mailboxes.cf.j2 new file mode 100644 index 0000000..db327a2 --- /dev/null +++ b/templates/mysql-virtual_mailboxes.cf.j2 @@ -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 diff --git a/templates/mysql-virtual_transports.cf.j2 b/templates/mysql-virtual_transports.cf.j2 new file mode 100644 index 0000000..8a6d00e --- /dev/null +++ b/templates/mysql-virtual_transports.cf.j2 @@ -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 diff --git a/templates/smtp.j2 b/templates/smtp.j2 new file mode 100644 index 0000000..9f3cd6b --- /dev/null +++ b/templates/smtp.j2 @@ -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 diff --git a/templates/smtpd.conf.j2 b/templates/smtpd.conf.j2 new file mode 100644 index 0000000..477a240 --- /dev/null +++ b/templates/smtpd.conf.j2 @@ -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