Update to 2021-12-01 19:13

This commit is contained in:
Daniel Berteaud
2021-12-01 19:13:34 +01:00
commit 4c4556c660
2153 changed files with 60999 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
---
pmg_api_ports: [8006]
pmg_api_src_ip: "{{ trusted_ip | default(['127.0.0.1']) }}"
pmg_smtp_ext_ports:
- 25
pmg_smtp_ext_src_ip:
- 0.0.0.0/0
pmg_smtp_int_ports:
- 26
pmg_smtp_int_src_ip: "{{ trusted_ip | default(['127.0.0.1']) }}"
pmg_dkim_sign: []
# pmg_dkim_sign:
# - domain: '*'
# selector: default
# priority: 999
# opendkim will resolv names itself,
# bypassing /etc/resolv.conf by default. Set here a list of DNS server
# to use if you want to use specific name servers
pmg_dkim_nameservers: []
# You can give an imap account, and PMG will fetch email and learn as spam/ham
# pmg_bayes_imap_server: imap.domain.net
# Security used. Can be none, starttls or tls
pmg_bayes_imap_security: starttls
# pmg_bayes_imap_user: pmg-sa-learn@domain.tld
# pmg_bayes_imap_pass: XXXX
pmg_bayes_imap_spam: Spam
pmg_bayes_imap_ham: Ham
# If spam/ham are just plain email, set it to false. But if they are attached
# as message/rfc822 (it's the case for example with Zimbra spam / non spam buttons)
# then set it to true. If true, the script will extract the first rfc822 attachment
# and feed it to sa-learn instead of feeding the outer email
pmg_bays_imap_attachment: False
# Should we use the openfish feeds. They should only be used for personal usage
# unless allowed by openphish. See https://openphish.com/feed.txt
pmg_use_openphish: False

View File

@@ -0,0 +1,148 @@
#!/usr/bin/perl
use strict;
use warnings;
use Mail::IMAPClient;
use MIME::Parser;
use Getopt::Long;
use Data::Dumper;
# Hide args from process list
$0 = 'imap-sa-learn';
my ($all,$list) = undef;
my $server = $ENV{IMAP_SA_LEARN_SERVER};
my $port = $ENV{IMAP_SA_LEARN_PORT};
my $user = $ENV{IMAP_SA_LEARN_USER};
my $password = $ENV{IMAP_SA_LEARN_PASSWORD};
my $spamdir = $ENV{IMAP_SA_LEARN_SPAMDIR} || 'Spam';
my $hamdir = $ENV{IMAP_SA_LEARN_HAMDIR} || 'Ham';
my $security = $ENV{IMAP_SA_LEARN_SECURITY} || 'tls';
my $attachment = $ENV{IMAP_SA_LEARN_ATTACHMENT} || 0;
my $debug = $ENV{IMAP_SA_LEARN_DEBUG} || 0;
my $need_sync = 0;
GetOptions(
'server=s' => \$server,
'security=s' => \$security,
'port=i' => \$port,
'user=s' => \$user,
'password=s' => \$password,
'spamdir=s' => \$spamdir,
'hamdir=s' => \$hamdir,
'attachment' => \$attachment,
'all' => \$all,
'list' => \$list,
'debug+' => \$debug
);
# Set ssl and starttls based on security value
my $ssl = (lc $security eq 'tls' or lc $security eq 'ssl') ? 1 : 0;
my $starttls = (lc $security eq 'starttls' or lc $security eq 'start_tls') ? 1 : 0;
# Unless port is explicitely given, set the default based on SSL being enabled
$port ||= ($ssl) ? '993' : '143';
# Check required args are given
if (not defined $server or not defined $user or not defined $password){
die "You must provide server, user and password";
}
# Check for mutual exclusive options
if ($ssl and $starttls){
die "ssl and starttls are mutually exclusive";
}
# Create the imap client
my $imap = Mail::IMAPClient->new(
Server => $server,
User => $user,
Password => $password,
Port => $port,
Ssl => $ssl,
Starttls => $starttls,
);
if ($@){
die "Can't connect to imap server: $@\n";
}
# If --list is given, just print the list of folders available and exit
# This can be used to check how some special characters are encoded so
# you can just copy/past it later
if ($list){
print $_ . "\n" foreach (@{$imap->folders});
exit 0;
}
if (defined $spamdir){
crawl_imap_dir($spamdir,'spam');
}
if (defined $hamdir){
crawl_imap_dir($hamdir,'ham');
}
if ($need_sync){
qx(sa-learn --sync);
}
$imap->close;
exit 0;
sub crawl_imap_dir {
my $dir = shift;
my $type = shift;
$imap->select($dir);
debug("Crawling in folder $dir\n");
# Default is to only process unseen emails
my @list = $imap->unseen;
# Unless --all is given, in which case we process also seen ones
push @list, $imap->seen if ($all);
foreach my $id (@list){
debug("Found mail id $id", 2);
my $raw_mail = $imap->message_string($id);
# When --attachment is passed, we expect spam/ham to be attached as
# message/rfc822. Extract the first matching attachment and feed it to sa-learn
# Note that there's no support for nested attachment, but it shouldn't be needed
if ($attachment) {
my $parser = MIME::Parser->new;
my $entity = $parser->parse_data($raw_mail);
my $inner_found = 0;
foreach my $part ($entity->parts) {
if ($part->effective_type eq 'message/rfc822'){
# Remove the first 3 lines which presents the raw mail, but as an attachment
my @lines = split /\n/, $part->stringify;
splice @lines,0,3;
sa_learn(join("\n", @lines), $type);
$inner_found = 1;
}
}
if (not $inner_found){
debug("Couldn't find a message/rfc822 attachment. Are you sure --attachment is needed ?");
}
} else {
sa_learn($raw_mail, $type);
}
}
}
sub sa_learn {
my $data = shift;
my $type = shift;
open SALEARN, "| sa-learn --no-sync --$type";
print SALEARN $data;
close SALEARN;
$need_sync = 1;
}
sub debug {
my $msg = shift;
my $level = shift;
$level ||= 1;
if ($debug ge $level){
print "$msg\n";
}
}

View File

@@ -0,0 +1,11 @@
--- /usr/share/perl5/PMG/API2/Subscription.pm.orig 2018-10-19 09:00:37.950836918 +0200
+++ /usr/share/perl5/PMG/API2/Subscription.pm 2018-10-19 09:00:45.782516677 +0200
@@ -79,7 +79,7 @@
my $info = PVE::INotify::read_file('subscription');
if (!$info) {
return {
- status => "NotFound",
+ status => "Active",
message => "There is no subscription key",
serverid => $server_id,
url => $url,

View File

@@ -0,0 +1,24 @@
---
- name: restart saslauthd
service: name=saslauthd state=restarted
- name: restart postfix
service: name=postfix state=restarted
- name: restart dovecot
service: name=dovecot state=restarted
- name: pmgconfig sync
command: pmgconfig sync --restart 1
- name: restart opendkim-signer
service: name=opendkim-signer state=restarted
- name: restart opendkim-verifier
service: name=opendkim-verifier state=restarted
- name: restart pmgproxy
service: name=pmgproxy state=restarted
- name: reload pmg-smtp-filter
service: name=pmg-smtp-filter state=reloaded

4
roles/pmg/meta/main.yml Normal file
View File

@@ -0,0 +1,4 @@
---
dependencies:
- role: mkdir

View File

@@ -0,0 +1,24 @@
---
- name: Remove tmp and obsolete files
file: path={{ item }} state=absent
loop:
- /etc/opendkim/verifier.conf
- /etc/systemd/system/opendmarc.service
- /etc/systemd/system/opendkim-verifier.service
- /etc/opendmarc.conf
- /etc/opendmarc.dbpass
- /usr/local/bin/dmarc_reports
- /etc/cron.d/opendmarc
- /etc/pmg/templates/main.cf.in
- /etc/mail/spamassassin/scores.cf
tags: pmg
- name: Disable unused services
service: name={{ item }} state=stopped disabled=True
loop:
- opendmarc
- opendkim-verifier
- saslauthd
failed_when: False
tags: pmg

View File

@@ -0,0 +1,5 @@
---
- name: Deploy filebeat configuration
template: src=filebeat.yml.j2 dest=/etc/filebeat/ansible_inputs.d/pmg.yml
tags: pmg,log

283
roles/pmg/tasks/main.yml Normal file
View File

@@ -0,0 +1,283 @@
---
- set_fact: pmg_smtpd_milters_int={{ [] }}
tags: pmg
- name: Enable dkim signing for outgoing messages
set_fact: pmg_smtpd_milters_int={{ pmg_smtpd_milters_int + ['unix:/var/run/opendkim/signer.sock'] }}
when: pmg_dkim_sign | length > 0
tags: pmg
- name: Add PMG repository APT key
apt_key:
url: http://download.proxmox.com/debian/proxmox-ve-release-6.x.gpg
state: present
environment:
- http_proxy: "{{ system_proxy | default('') }}"
tags: pmg
- name: Remove the paid repository
apt_repository:
repo: deb https://enterprise.proxmox.com/debian/pmg stretch pmg-enterprise
state: absent
filename: pmg-enterprise
tags: pmg
- name: Configure the no-subscription PMG repository
apt_repository:
repo: deb http://download.proxmox.com/debian/pmg {{ ansible_distribution_release }} pmg-no-subscription
state: present
filename: pmg-enterprise
tags: pmg
- name: Install PMG
apt:
name:
- proxmox-mailgateway
- opendkim
- opendkim-tools
- patch # Needed to remove the subscription nag screen
- libmail-imapclient-perl # For the sa-learn script
update_cache: True
tags: pmg
- name: Remove registration nag
patch: src=remove_nag.patch dest=/usr/share/perl5/PMG/API2/Subscription.pm
notify: restart pmgproxy
tags: pmg
# The tracking center uses mails.{info,err,log}
- name: Ensure syslog daemon is enabled and running
service: name=rsyslog state=started enabled=True
tags: pmg
- name: Add postfix to the opendkim group
user:
name: postfix
groups: opendkim
append: True
notify: restart postfix
tags: pmg
- name: Create custom templates directory
file: path=/etc/pmg/templates state=directory
tags: pmg
- name: Copy custom templates
template: src={{ item }}.j2 dest=/etc/pmg/templates/{{ item }}
with_items:
- master.cf.in
notify: pmgconfig sync
tags: pmg
- name: Check if pmg backup dir exists
stat: path=/var/lib/pmg/backup
register: pmg_backup
tags: pmg
- name: Move backup dir
command: mv /var/lib/pmg/backup /home/lbkp/pmg
when: pmg_backup.stat.isdir is defined and pmg_backup.stat.isdir
tags: pmg
- name: Link backup dir
file: src=/home/lbkp/pmg dest=/var/lib/pmg/backup state=link
tags: pmg
- name: Deploy pre/post backup scripts
template: src=pmg_{{ item }}_backup.sh.j2 dest=/etc/backup/{{ item }}.d/pmg.sh mode=755
with_items:
- pre
- post
tags: pmg
- name: Deploy dehydrated hook
template: src=dehydrated_deploy_hook.j2 dest=/etc/dehydrated/hooks_deploy_cert.d/pmg mode=755
tags: pmg
- name: Create directories for opendkim
file: path={{ item.dir }} owner={{ item.owner | default(omit) }} mode={{ item.mode | default(omit) }} state=directory
with_items:
- dir: /etc/opendkim
- dir: /etc/opendkim/keys
owner: opendkim
mode: 700
tags: pmg
- name: Deploy opendkim signing
template: src={{ item }}.j2 dest=/etc/opendkim/{{ item }}
with_items:
- keytable
- signingtable
notify: restart opendkim-signer
tags: pmg
- name: Deploy opendkim daemon config
template: src=opendkim.conf.j2 dest=/etc/opendkim/{{ item }}.conf
with_items:
- signer
notify:
- restart opendkim-signer
tags: pmg
- name: Create DKIM dir
file: path=/etc/opendkim/keys/{{ (item.domain == '*') | ternary('default',item.domain) }} state=directory owner=opendkim group=opendkim mode=700
with_items: "{{ pmg_dkim_sign }}"
tags: pmg
- name: Create DKIM keys
command: opendkim-genkey -D /etc/opendkim/keys/{{ (item.domain == '*') | ternary('default',item.domain) }}/ -s {{ item.selector | default('default') }}
args:
creates: /etc/opendkim/keys/{{ (item.domain == '*') | ternary('default',item.domain) }}/{{ item.selector | default('default') }}.private
become_user: opendkim
with_items: "{{ pmg_dkim_sign }}"
tags: pmg
- name: Deploy opendkim service unit
template: src=opendkim.service.j2 dest=/etc/systemd/system/opendkim-{{ item }}.service
with_items:
- signer
register: pmg_opendkim_unit
tags: pmg
- name: Reload systemd
systemd: daemon_reload=True
when: pmg_opendkim_unit.results | selectattr('changed','equalto',True) | list | length > 0
tags: pmg
- name: Check if /etc/pmg/mynetworks exists
stat: path=/etc/pmg/mynetworks
register: pmg_mynetworks
tags: pmg
- name: Handle opendkim services
service: name={{ item.service }} state={{ item.enabled | ternary('started','stopped') }} enabled={{ item.enabled }}
with_items:
- service: opendkim
enabled: False
- service: opendkim-signer
enabled: "{{ pmg_dkim_sign | length > 0 }}"
tags: pmg
- name: Install imap-sa-learn script
copy: src=imap-sa-learn dest=/usr/local/bin/imap-sa-learn mode=755
tags: pmg
- name: Deploy imap-sa-learn config
template: src=imap-sa-learn.j2 dest=/etc/default/imap-sa-learn mode=600
tags: pmg
- name: Deploy imap-sa-learn systemd units
template: src=imap-sa-learn.{{ item }}.j2 dest=/etc/systemd/system/imap-sa-learn.{{ item }}
loop:
- service
- timer
register: pmg_imap_sa_learn_unit
tags: pmg
- name: Reload systemd
systemd: daemon_reload=True
when: pmg_imap_sa_learn_unit.results | selectattr('changed','equalto',True) | list | length > 0
tags: pmg
- name: Handle imap-sa-learn timer
systemd:
name: imap-sa-learn.timer
state: "{{ (pmg_bayes_imap_server is defined and pmg_bayes_imap_user is defined and pmg_bayes_imap_pass is defined) | ternary('started','stopped') }}"
enabled: "{{ (pmg_bayes_imap_server is defined and pmg_bayes_imap_user is defined and pmg_bayes_imap_pass is defined) | ternary(True,False) }}"
tags: pmg
- name: Remove imap-sa-learn wrapper
file: path={{ item }} state=absent
loop:
- /usr/local/bin/imap-sa-learn.sh
- /etc/cron.d/imap_sa_learn
tags: pmg
- name: Configure log retention
template: src=logrotate.d/{{ item }}.j2 dest=/etc/logrotate.d/{{ item }}
with_items:
- rsyslog
tags: pmg
- name: Configure additional spamassassin plugins
template: src=spamassassin/{{ item }}.j2 dest=/etc/mail/spamassassin/{{ item }}
with_items:
- hashbl.pre
- hashbl.cf
- fromnamespoof.pre
- fromnamespoof.cf
- phishing.pre
- phishing.cf
- bayes_auto_learn.cf
notify: reload pmg-smtp-filter
tags: pmg
- name: Create spamassassin data dir
file: path=/var/lib/spamassassin state=directory
tags: pmg
- name: Download fishtank feed
get_url:
url: https://data.phishtank.com/data/online-valid.csv
dest: /var/lib/spamassassin/phishtank.txt
notify: reload pmg-smtp-filter
environment:
- https_proxy: "{{ system_proxy | default('') }}"
tags: pmg
- name: Download openphish feed
get_url:
url: https://openphish.com/feed.txt
dest: /var/lib/spamassassin/openphish.txt
notify: reload pmg-smtp-filter
environment:
- https_proxy: "{{ system_proxy | default('') }}"
when: pmg_use_openphish
tags: pmg
- name: Deploy a script to update phishing feeds
template: src=update-phishing-feeds.j2 dest=/usr/local/bin/update-phishing-feeds mode=755
tags: pmg
- name: Add a cron job to update phishing feeds
cron:
name: phishing_feeds
cron_file: phishing_feeds
user: root
job: systemd-cat /usr/local/bin/update-phishing-feeds
minute: 1
hour: '*/3'
state: present
tags: pmg
- name: Handle ports in the firewall
iptables_raw:
name: "{{ item.name }}"
state: "{{ (item.src | length > 0) | ternary('present','absent') }}"
rules: "{% if 'tcp' in item.proto | default(['tcp']) or item.proto | default('tcp') == 'tcp' %}-A INPUT -m state --state NEW -p tcp -m multiport --dports {{ item.ports | join(',') }} -s {{ item.src | join(',') }} -j ACCEPT\n{% endif %}
{% if 'udp' in item.proto | default(['tcp']) or item.proto | default('tcp') == 'udp' %}-A INPUT -m state --state NEW -p udp -m multiport --dports {{ item.ports | join(',') }} -s {{ item.src | join(',') }} -j ACCEPT{% endif %}"
when: iptables_manage | default(True)
with_items:
- ports: "{{ pmg_api_ports }}"
name: pmg_api_ports
src: "{{ pmg_api_src_ip }}"
- ports: "{{ pmg_smtp_ext_ports }}"
name: pmg_smtp_ext_ports
src: "{{ pmg_smtp_ext_src_ip }}"
- ports: "{{ pmg_smtp_int_ports }}"
name: pmg_smtp_int_ports
src: "{{ pmg_smtp_int_src_ip }}"
tags: pmg,firewall
- name: Remove obsolete firewall rules
iptables_raw:
name: "{{ item }}"
state: absent
loop:
- pmg_imap_ports
- pmg_pop_ports
tags: pmg,firewall
- include: filebeat.yml
- include: cleanup.yml

View File

@@ -0,0 +1,11 @@
#!/bin/bash -e
{% if pmg_letsencrypt_cert is defined %}
if [ $1 == "{{ pmg_letsencrypt_cert }}" ]; then
cat /var/lib/dehydrated/certificates/certs/{{ pmg_letsencrypt_cert }}/privkey.pem > /etc/pmg/pmg-tls.pem
cat /var/lib/dehydrated/certificates/certs/{{ pmg_letsencrypt_cert }}/fullchain.pem >> /etc/pmg/pmg-tls.pem
chown root:root /etc/pmg/pmg-tls.pem
chmod 600 /etc/pmg/pmg-tls.pem
/bin/systemctl reload postfix
fi
{% endif %}

View File

@@ -0,0 +1,8 @@
- type: log
enabled: True
paths:
- /var/log/postgresql/postgresql-11-main.log
- /var/log/pmgproxy/*.log
exclude_files:
- '\.[gx]z$'
- '\d+$'

View File

@@ -0,0 +1,15 @@
{% if pmg_bayes_imap_server is defined %}
IMAP_SA_LEARN_SERVER={{ pmg_bayes_imap_server }}
{% endif %}
{% if pmg_bayes_imap_user is defined %}
IMAP_SA_LEARN_USER={{ pmg_bayes_imap_user }}
{% endif %}
{% if pmg_bayes_imap_pass is defined %}
IMAP_SA_LEARN_PASSWORD={{ pmg_bayes_imap_pass | quote }}
{% endif %}
IMAP_SA_LEARN_SPAMDIR={{ pmg_bayes_imap_spam }}
IMAP_SA_LEARN_HAMDIR={{ pmg_bayes_imap_ham }}
IMAP_SA_LEARN_SECURITY={{ pmg_bayes_imap_security }}
{% if pmg_bays_imap_attachment %}
IMAP_SA_LEARN_ATTACHMENT=1
{% endif %}

View File

@@ -0,0 +1,8 @@
[Unit]
Description=Train spamassassin beayes from an IMAP account
[Service]
Type=oneshot
EnvironmentFile=/etc/default/imap-sa-learn
ExecStart=/usr/local/bin/imap-sa-learn
TimeoutSec=7200

View File

@@ -0,0 +1,8 @@
[Unit]
Description=Train spamassassin beayes from an IMAP account
[Timer]
OnCalendar=daily
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,3 @@
{% for domain in pmg_dkim_sign %}
{{ (domain.domain == '*') | ternary('default',domain.domain) }} {{ (domain.domain == '*') | ternary('%',domain.domain) }}:{{ domain.selector | default('default') }}:/etc/opendkim/keys/{{ (domain.domain == '*') | ternary('default',domain.domain) }}/{{ domain.selector | default('default') }}.private
{% endfor %}

View File

@@ -0,0 +1,37 @@
/var/log/syslog
{
rotate 24
monthly
missingok
notifempty
delaycompress
compress
postrotate
/usr/lib/rsyslog/rsyslog-rotate
endscript
}
/var/log/mail.info
/var/log/mail.warn
/var/log/mail.err
/var/log/mail.log
/var/log/daemon.log
/var/log/kern.log
/var/log/auth.log
/var/log/user.log
/var/log/lpr.log
/var/log/cron.log
/var/log/debug
/var/log/messages
{
rotate 52
weekly
missingok
notifempty
compress
delaycompress
sharedscripts
postrotate
/usr/lib/rsyslog/rsyslog-rotate
endscript
}

View File

@@ -0,0 +1,154 @@
#
# Postfix master process configuration file. Each logical line
# describes how a Postfix daemon program should be run.
#
# A logical line starts with non-whitespace, non-comment text.
# Empty lines and whitespace-only lines are ignored, as are comment
# lines whose first non-whitespace character is a `#'.
# A line that starts with whitespace continues a logical line.
#
# The fields that make up each line are described below. A "-" field
# value requests that a default value be used for that field.
#
# Service: any name that is valid for the specified transport type
# (the next field). With INET transports, a service is specified as
# host:port. The host part (and colon) may be omitted. Either host
# or port may be given in symbolic form or in numeric form. Examples
# for the SMTP server: localhost:smtp receives mail via the loopback
# interface only; 10025 receives mail on port 10025.
#
# Transport type: "inet" for Internet sockets, "unix" for UNIX-domain
# sockets, "fifo" for named pipes.
#
# Private: whether or not access is restricted to the mail system.
# Default is private service. Internet (inet) sockets can't be private.
#
# Unprivileged: whether the service runs with root privileges or as
# the owner of the Postfix system (the owner name is controlled by the
# mail_owner configuration variable in the main.cf file). Only the
# pipe, virtual and local delivery daemons require privileges.
#
# Chroot: whether or not the service runs chrooted to the mail queue
# directory (pathname is controlled by the queue_directory configuration
# variable in the main.cf file). Presently, all Postfix daemons can run
# chrooted, except for the pipe, virtual and local delivery daemons.
# The proxymap server can run chrooted, but doing so defeats most of
# the purpose of having that service in the first place.
# The files in the examples/chroot-setup subdirectory describe how
# to set up a Postfix chroot environment for your type of machine.
#
# Wakeup time: automatically wake up the named service after the
# specified number of seconds. A ? at the end of the wakeup time
# field requests that wake up events be sent only to services that
# are actually being used. Specify 0 for no wakeup. Presently, only
# the pickup, queue manager and flush daemons need a wakeup timer.
#
# Max procs: the maximum number of processes that may execute this
# service simultaneously. Default is to use a globally configurable
# limit (the default_process_limit configuration parameter in main.cf).
# Specify 0 for no process count limit.
#
# Command + args: the command to be executed. The command name is
# relative to the Postfix program directory (pathname is controlled by
# the daemon_directory configuration variable). Adding one or more
# -v options turns on verbose logging for that service; adding a -D
# option enables symbolic debugging (see the debugger_command variable
# in the main.cf configuration file). See individual command man pages
# for specific command-line options, if any.
#
# In order to use the "uucp" message transport below, set up entries
# in the transport table.
#
# In order to use the "cyrus" message transport below, configure it
# in main.cf as the mailbox_transport.
#
# SPECIFY ONLY PROGRAMS THAT ARE WRITTEN TO RUN AS POSTFIX DAEMONS.
# ALL DAEMONS SPECIFIED HERE MUST SPEAK A POSTFIX-INTERNAL PROTOCOL.
#
# DO NOT SHARE THE POSTFIX QUEUE BETWEEN MULTIPLE POSTFIX INSTANCES.
#
# ==========================================================================
# service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (yes) (never) (100)
# ==========================================================================
[% IF ! pmg.mail.before_queue_filtering -%]
scan unix - - n - [% pmg.mail.max_filters %] lmtp
-o lmtp_send_xforward_command=yes
-o lmtp_connection_cache_on_demand=no
-o disable_dns_lookups=yes
[% END -%]
[% pmg.mail.int_port %] inet n - - - [% pmg.mail.max_smtpd_out %] smtpd
[% IF pmg.mail.before_queue_filtering -%]
-o smtpd_proxy_filter=127.0.0.1:10023
-o smtpd_proxy_options=speed_adjust
-o smtpd_client_connection_count_limit=[% pmg.mail.conn_count_limit div 5 %]
[%- ELSE -%]
-o content_filter=scan:127.0.0.1:10023
[%- END %]
-o smtpd_recipient_restrictions=permit_mynetworks,reject_unauth_destination
-o smtpd_helo_restrictions=
-o smtpd_client_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_milters={{ pmg_smtpd_milters_int | join(',') }}
[% pmg.mail.ext_port %] inet n - - - 1 postscreen
smtpd pass - - - - [% pmg.mail.max_smtpd_in %] smtpd
[% IF pmg.mail.before_queue_filtering -%]
-o smtpd_proxy_filter=127.0.0.1:10024
-o smtpd_proxy_options=speed_adjust
-o smtpd_client_connection_count_limit=[% pmg.mail.conn_count_limit div 5 %]
[%- ELSE -%]
-o content_filter=scan:127.0.0.1:10024
[%- END %]
-o receive_override_options=no_address_mappings
-o smtpd_discard_ehlo_keywords=silent-discard,dsn
-o mynetworks=127.0.0.0/8,[% postfix.int_ip %]
127.0.0.1:10025 inet n - n - - smtpd
-o content_filter=
-o local_recipient_maps=
-o relay_recipient_maps=
-o receive_override_options=no_unknown_recipient_checks,no_header_body_checks
-o smtpd_helo_restrictions=
-o smtpd_client_restrictions=
-o smtpd_restriction_classes=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o mynetworks=127.0.0.0/8
-o smtpd_error_sleep_time=0
-o smtpd_client_connection_count_limit=0
-o smtpd_client_connection_rate_limit=0
-o smtpd_tls_security_level=none
-o smtpd_authorized_xforward_hosts=127.0.0.0/8
-o message_size_limit=[% (pmg.mail.maxsize*2) %]
pickup fifo n - - 60 1 pickup
cleanup unix n - - - 0 cleanup
-o message_size_limit=[% (pmg.mail.maxsize*2) %]
qmgr fifo n - - 300 1 qmgr
rewrite unix - - - - - trivial-rewrite
bounce unix - - - - 0 bounce
defer unix - - - - 0 bounce
flush unix n - - 1000? 0 flush
proxymap unix - - n - - proxymap
smtp unix - - - - - smtp
relay unix - - - - - smtp
showq unix n - - - - showq
error unix - - - - - error
local unix - n n - - local
virtual unix - n n - - virtual
lmtp unix - - n - - lmtp
verify unix - - - - 1 verify
trace unix - - n - 0 bounce
tlsmgr unix - - - 1000? 1 tlsmgr
anvil unix - - - - 1 anvil
scache unix - - - - 1 scache
discard unix - - - - - discard
retry unix - - - - - error
dnsblog unix - - - - 0 dnsblog
tlsproxy unix - - - - 0 tlsproxy

View File

@@ -0,0 +1,23 @@
Syslog yes
LogResults yes
LogWhy yes
SyslogSuccess yes
UMask 007
{% if item == 'signer' %}
KeyTable /etc/opendkim/keytable
SigningTable /etc/opendkim/signingtable
Mode s
InternalHosts 0.0.0.0/0
{% else %}
Mode v
AllowSHA1Only yes
AlwaysAddARHeader yes
{% endif %}
Socket local:/var/run/opendkim/{{ item }}.sock
PidFile /var/run/opendkim/{{ item }}.pid
TrustAnchorFile /usr/share/dns/root.key
UserID opendkim
Background no
{% if pmg_dkim_nameservers is defined and pmg_dkim_nameservers | length > 0 %}
Nameservers {{ pmg_dkim_nameservers | join(',') }}
{% endif %}

View File

@@ -0,0 +1,23 @@
[Unit]
Description=OpenDKIM DomainKeys Identified Mail (DKIM) Milter - {{ item }}
Documentation=man:opendkim(8) man:opendkim.conf(5) man:opendkim-genkey(8) man:opendkim-genzone(8) man:opendkim-testadsp(8) man:opendkim-testkey http://www.opendkim.org/docs.html
After=network.target nss-lookup.target
[Service]
Type=simple
UMask=0007
ExecStart=/usr/sbin/opendkim -x /etc/opendkim/{{ item }}.conf
User=opendkim
Group=opendkim
MemoryLimit=50M
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=full
ProtectHome=yes
NoNewPrivileges=yes
Restart=on-failure
ExecReload=/bin/kill -USR1 $MAINPID
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,3 @@
#!/bin/bash -e
find /home/lbkp/pmg/ -type f -mtime +7 -exec rm -f "{}" \;

View File

@@ -0,0 +1,6 @@
#!/bin/sh
set -eo pipefail
/usr/bin/systemd-cat /usr/bin/pmgbackup backup
cp -a /etc/opendkim/keys /home/lbkp/pmg/

View File

@@ -0,0 +1,10 @@
ldap_servers: {{ pmg_ldap_servers | join(' ') }}
ldap_start_tls: {{ pmg_ldap_starttls | ternary('yes','no') }}
ldap_tls_check_peer: yes
ldap_tls_cacert_file: /etc/ssl/certs/ca-certificates.crt
ldap_search_base: {{ pmg_ldap_search_base }}
ldap_filter: {{ pmg_ldap_filter }}
{% if pmg_ldap_bind_dn is defined and pmg_ldap_bind_pass is defined %}
ldap_bind_dn: {{ pmg_ldap_bind_dn }}
ldap_password: {{ pmg_ldap_bind_pass }}
{% endif %}

View File

@@ -0,0 +1,6 @@
START=yes
DESC="SASL Authentication Daemon"
NAME="saslauthd"
MECHANISMS="ldap"
MECH_OPTIONS=""
OPTIONS="-c -m /var/run/saslauthd"

View File

@@ -0,0 +1,3 @@
{% for domain in pmg_dkim_sign | sort(attribute='priority') %}
{{ domain.domain }} {{ (domain.domain == '*') | ternary('default',domain.domain) }}
{% endfor %}

View File

@@ -0,0 +1,2 @@
pwcheck_method: saslauthd
mech_list: plain login

View File

@@ -0,0 +1,2 @@
# Bayes autolearn never gave any interesting results
bayes_auto_learn 0

View File

@@ -0,0 +1,36 @@
# skip messages with one or more of these headers
fns_ignore_headers List-Id List-Post Mailing-List X-Forwarded-For
# Does the From:name look like an email address
header __PLUGIN_FROMNAME_EMAIL eval:check_fromname_contains_email()
# Is the From:name differen to the from header
header __PLUGIN_FROMNAME_DIFFERENT eval:check_fromname_different()
# From:name owners differ
header __PLUGIN_FROMNAME_OWNERS_DIFFER eval:check_fromname_owners_differ()
# From:name domain differs to from header
header __PLUGIN_FROMNAME_DOMAIN_DIFFER eval:check_fromname_domain_differ()
# From:name and From:address don't match and owners differ
header __PLUGIN_FROMNAME_SPOOF eval:check_fromname_spoof()
# From:name address matches To:address
header __PLUGIN_FROMNAME_EQUALS_TO eval:check_fromname_equals_to()
meta FROMNAME_SPOOF (__PLUGIN_FROMNAME_SPOOF)
describe FROMNAME_SPOOF From:name doesn't match From:address
score FROMNAME_SPOOF 1
meta FROMNAME_SPOOF_EQUALS_TO (FROMNAME_SPOOF && __PLUGIN_FROMNAME_EQUALS_TO)
describe FROMNAME_SPOOF_EQUALS_TO From:name is spoof to look like To: address
score FROMNAME_SPOOF_EQUALS_TO 1
meta FROMNAME_EQUALS_TO (!FROMNAME_SPOOF && __PLUGIN_FROMNAME_EQUALS_TO)
describe FROMNAME_EQUALS_TO From:name matches To: address
score FROMNAME_EQUALS_TO 0.01
meta FROMNAME_SPOOF_FREEMAIL (FREEMAIL_FROM && FROMNAME_SPOOF)
describe FROMNAME_SPOOF_FREEMAIL From:name spoof and Freemail From:address
score FROMNAME_SPOOF_FREEMAIL 2.0

View File

@@ -0,0 +1 @@
loadplugin Mail::SpamAssassin::Plugin::FromNameSpoof

View File

@@ -0,0 +1,3 @@
header HASHBL_EMAIL eval:check_hashbl_emails('ebl.msbl.org')
describe HASHBL_EMAIL Message contains email address found on EBL
score HASHBL_EMAIL 2.5

View File

@@ -0,0 +1 @@
loadplugin Mail::SpamAssassin::Plugin::HashBL

View File

@@ -0,0 +1,7 @@
{% if pmg_use_openphish %}
phishing_openphish_feed /etc/mail/spamassassin/openphish.txt
{% endif %}
phishing_phishtank_feed /var/lib/spamassassin/phishtank.txt
body URI_PHISHING eval:check_phishing()
describe URI_PHISHING Url match phishing in feed
score URI_PHISHING 3

View File

@@ -0,0 +1 @@
loadplugin Mail::SpamAssassin::Plugin::Phishing

View File

@@ -0,0 +1,34 @@
#!/bin/bash -e
NEED_RELOAD=0
TEMP=$(mktemp -u)
wget https://data.phishtank.com/data/online-valid.csv -O $TEMP
OLD_SHA1=""
if [ -e /var/lib/spamassassin/phishtank-feed.csv ]; then
OLD_SHA1=$(sha1sum /var/lib/spamassassin/phishtank.txt)
fi
NEW_SHA1=$(sha1sum $TEMP)
if [ "$OLD_SHA1" != "$NEW_SHA1" ]; then
mv -f $TEMP /var/lib/spamassassin/phishtank.txt
NEED_RELOAD=1
fi
{% if pmg_use_openphish %}
wget https://openphish.com/feed.txt -O $TEMP
OLD_SHA1=""
if [ -e /var/lib/spamassassin/openphish.txt ]; then
OLD_SHA1=$(sha1sum /var/lib/spamassassin/openphish.txt)
fi
NEW_SHA1=$(sha1sum $TEMP)
if [ "$OLD_SHA1" != "$NEW_SHA1" ]; then
mv -f $TEMP /var/lib/spamassassin/openphish.txt
NEED_RELOAD=1
fi
{% endif %}
if [ "$NEED_RELOAD" == "1" ]; then
systemctl reload pmg-smtp-filter
fi

5
roles/pmg/vars/main.yml Normal file
View File

@@ -0,0 +1,5 @@
---
# The tracking center needs /var/log/syslog
system_disable_syslog: False
# postfix conf is handled by PMG templating engine
system_postfix: False