From 43fd2da518d2b8804b3add253c6aa2b4b090927b Mon Sep 17 00:00:00 2001 From: iburadempa Date: Sat, 12 Sep 2020 13:58:52 +0200 Subject: [PATCH] Add sender rewriting scheme (SRS). --- mail_system/tasks/database.yml | 13 ++++ mail_system/templates/postfix/main.cf | 4 +- .../postfix/recipient_canonical_maps.cf | 39 +++++++++- .../postfix/sender_canonical_maps.cf | 75 ++++++++++++++++--- 4 files changed, 116 insertions(+), 15 deletions(-) diff --git a/mail_system/tasks/database.yml b/mail_system/tasks/database.yml index bdc516d..528cbc1 100644 --- a/mail_system/tasks/database.yml +++ b/mail_system/tasks/database.yml @@ -146,6 +146,7 @@ - t timestamp default now() - original varchar(250) not null - rewritten varchar(250) not null + - srs_id bigint - name: database index mail_from__rewritten postgresql_idx: @@ -158,3 +159,15 @@ table: mail_from columns: rewritten idxname: mail_from__rewritten + +- name: database index mail_from__srs_id + postgresql_idx: + login_host: "{{ mailserver.postgresql.host }}" + port: "{{ mailserver.postgresql.port }}" + login_user: "{{ mailserver.postgresql.username }}" + login_password: "{{ mailserver.postgresql.password }}" + db: "{{ mailserver.postgresql.dbname }}" + ssl_mode: disable + table: mail_from + columns: srs_id + idxname: mail_from__srs_id diff --git a/mail_system/templates/postfix/main.cf b/mail_system/templates/postfix/main.cf index e5efdc3..a512a7f 100644 --- a/mail_system/templates/postfix/main.cf +++ b/mail_system/templates/postfix/main.cf @@ -99,10 +99,10 @@ smtpd_relay_restrictions = # VERP marking # Envelope sender addresses matching mydomains are marked. # The marker is removed from envelope recipient addresses. -canonical_classes = envelope_sender, envelope_recipient +canonical_classes = envelope_sender, envelope_recipient, header_recipient sender_canonical_classes = envelope_sender sender_canonical_maps = pgsql:/etc/postfix/sender_canonical_maps.cf -recipient_canonical_classes = envelope_recipient +recipient_canonical_classes = envelope_recipient, header_recipient recipient_canonical_maps = pgsql:/etc/postfix/recipient_canonical_maps.cf diff --git a/mail_system/templates/postfix/recipient_canonical_maps.cf b/mail_system/templates/postfix/recipient_canonical_maps.cf index 13cf5a6..b5a0ca7 100644 --- a/mail_system/templates/postfix/recipient_canonical_maps.cf +++ b/mail_system/templates/postfix/recipient_canonical_maps.cf @@ -1,5 +1,11 @@ # THIS FILE IS CONTROLLED BY ANSIBLE - DO NOT CHANGE IN DEPLOYMENT! +# Rewrite (envelope and header) recipient addresses: +# - if the recipient address has a local domain and does +# not begin with SRS0 or SRS1, then strip our verp_marker +# - if the recipient address has a local domain and does +# begin with SRS0 or SRS1, try to extract our srs_id +# and lookup the corresponding original address # man pgsql_table @@ -7,4 +13,35 @@ user = {{ mailserver.postgresql.username }} password = {{ mailserver.postgresql.password }} dbname = {{ mailserver.postgresql.dbname }} hosts = {{ mailserver.postgresql.host }} -query = select regexp_replace('%s', '\+(.*){{ mailserver.postfix.verp_marker }}-\d+@', '+\1@') +query = + with + mydomains as (select regexp_replace('%s', '.*@([^@]+)$', '\1') in (select name from domains) v), + srs0 as (select lower(substr('%s', 1, 4)) = 'srs0' v), + srs1 as (select lower(substr('%s', 1, 4)) = 'srs1' v) + select + case + when mydomains.v and not srs0.v and not srs1.v + then regexp_replace('%s', '^(.*)\+{{ mailserver.postfix.verp_marker }}-\d+@', '\1@') + else + case + when mydomains.v + then + case + when srs0.v + then + case + when '%s' ~* '^SRS0=\d+@[^@]+$' + then (select original from mail_from + where srs_id = regexp_replace('%s', '^.....(\d+)@[^@]+$', '\1')::bigint + and original<>rewritten) + else '%s' + end + else (with t2 as (select regexp_replace('%s', '^(.*)@[^@]+$', '\1') v) + select original from mail_from, t2 + where lower(substr(rewritten, 1, length(t2.v))) = lower(t2.v) + and lower(substr(original, 1, 4)) = 'srs0') + end + else '%s' + end + end + from mydomains, srs0, srs1 diff --git a/mail_system/templates/postfix/sender_canonical_maps.cf b/mail_system/templates/postfix/sender_canonical_maps.cf index ed7e88d..51758d2 100644 --- a/mail_system/templates/postfix/sender_canonical_maps.cf +++ b/mail_system/templates/postfix/sender_canonical_maps.cf @@ -1,5 +1,21 @@ # THIS FILE IS CONTROLLED BY ANSIBLE - DO NOT CHANGE IN DEPLOYMENT! +# Rewrite envelope sender addresses: +# - if the sender address has a local domain, then add our verp_marker +# - else implement sender rewriting scheme (cf. +# https://en.wikipedia.org/wiki/Sender_Rewriting_Scheme, +# http://www.libsrs2.org/srs/srs.pdf +# ): +# * if the address does not begin with SRS0 or SRS1: +# rewrite address to srs0={random_bigint}@main_local_domain +# * if the address begins with SRS0: +# replace SRS0 with srs1={random_bigint}= and +# replace the domain with our main local domain +# * if the address begins with SRS0: +# replace the domain with our main local domain +# The main local domain is the first domain with a name +# that is not equal to 'role_specific'. +# The random_bigint has at not 15 decimal digits. # man pgsql_table @@ -7,15 +23,50 @@ user = {{ mailserver.postgresql.username }} password = {{ mailserver.postgresql.password }} dbname = {{ mailserver.postgresql.dbname }} hosts = {{ mailserver.postgresql.host }} -query = insert into mail_from (id, original, rewritten) - values (nextval('mail_from_id_seq'), '%s', - case - when regexp_replace('%s', '.*@([^@]+)$', '\1') in (select name from domains) - then case - when '%s'~*'{{ mailserver.postfix.verp_marker }}-\d+@' - then '%s' - else regexp_replace('%s', '^(.*)@[^@]+$', '\1') || case when '%s'~'\+' then '{{ mailserver.postfix.verp_marker }}-' else '+{{ mailserver.postfix.verp_marker }}-' end || lastval()::text || '@' || regexp_replace('%s', '.*@([^@]+)$', '\1') - end - else '%s' - end - ) returning rewritten +query = + with + rnd as (select (random() * 1000000000000000)::bigint v), + mydomains as (select regexp_replace('%s', '.*@([^@]+)$', '\1') in (select name from domains) v), + srs0 as (select lower(substr('%s', 1, 4)) = 'srs0' v), + srs1 as (select lower(substr('%s', 1, 4)) = 'srs1' v), + new_row as ( + select + nextval('mail_from_id_seq') new_id, + '%s' original, + case + when mydomains.v and not srs0.v and not srs1.v + then + case + when '%s'~*'\+{{ mailserver.postfix.verp_marker }}-\d+@' + then '%s' + else regexp_replace('%s', '^(.*)@[^@]+$', '\1') || '+{{ mailserver.postfix.verp_marker }}-' + || lastval()::text || '@' || regexp_replace('%s', '.*@([^@]+)$', '\1') + end + else + case + when not mydomains.v and not srs0.v and not srs1.v + then 'SRS0=' || LPAD(rnd.v::text, 15 , '0') || '@' || (select name from domains where name<>'role_specific' order by id limit 1) + else + case + when srs0.v + then + case + when mydomains.v + then '%s' + else 'SRS1=' || LPAD(rnd.v::text, 15 , '0') || '=' + || regexp_replace(substr('%s', 5), '^(.*)@[^@]+$', '\1@' + || (select name from domains where name<>'role_specific' order by id limit 1)) + end + else regexp_replace('%s', '^(.*)@[^@]+$', '\1@' || (select name from domains where name<>'role_specific' order by id limit 1)) + end + end + end rewritten, + rnd.v srs_id + from rnd, mydomains, srs0, srs1 + where '%s' <> '""' + ), + ins_row as (insert into mail_from (id, original, rewritten, srs_id) + select new_row.new_id, new_row.original, new_row.rewritten, case when mydomains.v and not srs0.v and not srs1.v then null else new_row.srs_id end + from new_row, mydomains, srs0, srs1 where new_row.original<>new_row.rewritten), + orig(o) as (values('%s')) + select rewritten from new_row