Optimize
Signed-off-by: Patrick Niebeling <patrick.niebeling@adacor.com>
This commit is contained in:
@ -1,57 +0,0 @@
|
|||||||
FROM debian:stable-slim
|
|
||||||
LABEL maintainer="gnilebein - <docker@gnilebein.nl>"
|
|
||||||
|
|
||||||
# Set apt non-interactive
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
|
||||||
|
|
||||||
# Install Rspamd
|
|
||||||
RUN set -x \
|
|
||||||
&& apt update \
|
|
||||||
&& apt --no-install-recommends install -y lsb-release wget gnupg openssl ca-certificates \
|
|
||||||
&& DEBIAN_CODE_NAME=`lsb_release -c -s` \
|
|
||||||
&& wget -O - https://rspamd.com/apt-stable/gpg.key | apt-key add - \
|
|
||||||
&& echo "deb http://rspamd.com/apt-stable/ $DEBIAN_CODE_NAME main" > /etc/apt/sources.list.d/rspamd.list \
|
|
||||||
&& apt purge -y lsb-release wget gnupg \
|
|
||||||
&& apt update \
|
|
||||||
&& apt --no-install-recommends install -y rspamd \
|
|
||||||
&& apt autoremove --purge -y \
|
|
||||||
&& apt clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Override default settings
|
|
||||||
COPY rspamd.conf.local.override /etc/rspamd/
|
|
||||||
COPY worker-controller.inc /etc/rspamd/override.d/
|
|
||||||
COPY worker-proxy.inc /etc/rspamd/override.d/
|
|
||||||
|
|
||||||
# Keep database and configuration persistent
|
|
||||||
VOLUME /etc/rspamd/local.d
|
|
||||||
VOLUME /var/lib/rspamd
|
|
||||||
|
|
||||||
# Port 11334 is for web frontend
|
|
||||||
# Port 11332 is for milter
|
|
||||||
# Port 11333 is for worker
|
|
||||||
EXPOSE 11332 11334
|
|
||||||
|
|
||||||
# Healtcheck if Rspamd is returning stats
|
|
||||||
HEALTHCHECK --interval=1m --timeout=5s --start-period=10s \
|
|
||||||
CMD /usr/bin/rspamadm control stat || exit 1
|
|
||||||
|
|
||||||
# Run Rspamd
|
|
||||||
ENTRYPOINT ["/usr/bin/rspamd","-f","-u","_rspamd","-g","_rspamd"]
|
|
||||||
|
|
||||||
# Setup Labels
|
|
||||||
ARG VERSION
|
|
||||||
ARG COMMIT
|
|
||||||
ARG BRANCH
|
|
||||||
ARG DATE
|
|
||||||
|
|
||||||
LABEL org.label-schema.name="Rspamd" \
|
|
||||||
org.label-schema.description="Rspamd Spam Filter - STABLE" \
|
|
||||||
org.label-schema.usage="https://hub.docker.com/r/gnilebein/rspamd/" \
|
|
||||||
org.label-schema.url="https://rspamd.com" \
|
|
||||||
org.label-schema.vendor="gnilebein" \
|
|
||||||
org.label-schema.schema-version="1.0" \
|
|
||||||
org.label-schema.version=$VERSION \
|
|
||||||
org.label-schema.vcs-url="https://github.com/rspamd/rspamd/" \
|
|
||||||
org.label-schema.vcs-ref=$COMMIT \
|
|
||||||
org.label-schema.build-date=$DATE \
|
|
@ -9,6 +9,8 @@ VERSION=$(git ls-remote --tags -q https://github.com/rspamd/rspamd | sed -n "s/^
|
|||||||
|
|
||||||
IMAGE_NAME=docker-rspamd
|
IMAGE_NAME=docker-rspamd
|
||||||
|
|
||||||
|
zip -r config
|
||||||
|
|
||||||
docker build \
|
docker build \
|
||||||
--build-arg VERSION=${VERSION} \
|
--build-arg VERSION=${VERSION} \
|
||||||
--build-arg COMMIT=$(git ls-remote --tags -q https://github.com/rspamd/rspamd | sed -n "s/^\([[:xdigit:]]\{40\}\)[[:blank:]]refs\/tags\/${VERSION}^{}$/\1/p" | xargs git rev-parse --short) \
|
--build-arg COMMIT=$(git ls-remote --tags -q https://github.com/rspamd/rspamd | sed -n "s/^\([[:xdigit:]]\{40\}\)[[:blank:]]refs\/tags\/${VERSION}^{}$/\1/p" | xargs git rev-parse --short) \
|
||||||
|
@ -2,12 +2,12 @@ FROM debian:stable-slim
|
|||||||
LABEL maintainer="gnilebein - <docker@gnilebein.nl>"
|
LABEL maintainer="gnilebein - <docker@gnilebein.nl>"
|
||||||
|
|
||||||
# Set apt non-interactive
|
# Set apt non-interactive
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
|
||||||
# Install Rspamd
|
# Install Rspamd
|
||||||
RUN set -x \
|
RUN set -x \
|
||||||
&& apt update \
|
&& apt update \
|
||||||
&& apt --no-install-recommends install -y lsb-release wget gnupg openssl ca-certificates less nano grep\
|
&& apt --no-install-recommends install -y lsb-release wget gnupg openssl ca-certificates nano less \
|
||||||
&& DEBIAN_CODE_NAME=`lsb_release -c -s` \
|
&& DEBIAN_CODE_NAME=`lsb_release -c -s` \
|
||||||
&& wget -O - https://rspamd.com/apt-stable/gpg.key | apt-key add - \
|
&& wget -O - https://rspamd.com/apt-stable/gpg.key | apt-key add - \
|
||||||
&& echo "deb http://rspamd.com/apt-stable/ $DEBIAN_CODE_NAME main" > /etc/apt/sources.list.d/rspamd.list \
|
&& echo "deb http://rspamd.com/apt-stable/ $DEBIAN_CODE_NAME main" > /etc/apt/sources.list.d/rspamd.list \
|
||||||
@ -17,23 +17,19 @@ RUN set -x \
|
|||||||
&& apt autoremove --purge -y \
|
&& apt autoremove --purge -y \
|
||||||
&& apt clean \
|
&& apt clean \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& echo 'alias ll="ls -la --color"' >> ~/.bashrc
|
&& echo 'alias ll="ls -la --color"' >> ~/.bashrc
|
||||||
|
|
||||||
# Override default settings
|
# Override default settings
|
||||||
COPY conf/* /etc/rspamd/
|
|
||||||
COPY rspamd.conf.local.override /etc/rspamd/
|
COPY rspamd.conf.local.override /etc/rspamd/
|
||||||
COPY worker-controller.inc /etc/rspamd/override.d/
|
COPY worker-controller.inc /etc/rspamd/override.d/
|
||||||
COPY worker-proxy.inc /etc/rspamd/override.d/
|
COPY worker-proxy.inc /etc/rspamd/override.d/
|
||||||
|
COPY set_worker_password.sh /set_worker_password.sh
|
||||||
|
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
|
|
||||||
# Keep database and configuration persistent
|
# Keep database and configuration persistent
|
||||||
VOLUME /hooks
|
|
||||||
VOLUME /etc/rspamd/custom
|
|
||||||
VOLUME /etc/rspamd/override.d
|
|
||||||
VOLUME /etc/rspamd/local.d
|
VOLUME /etc/rspamd/local.d
|
||||||
VOLUME /etc/rspamd/plugins.d
|
VOLUME /etc/rspamd/override.d
|
||||||
VOLUME /etc/rspamd/lua/
|
VOLUME /etc/rspamd/custom
|
||||||
VOLUME /etc/rspamd/rspamd.conf.local
|
|
||||||
VOLUME /etc/rspamd/rspamd.conf.override
|
|
||||||
VOLUME /var/lib/rspamd
|
VOLUME /var/lib/rspamd
|
||||||
|
|
||||||
# Port 11334 is for web frontend
|
# Port 11334 is for web frontend
|
||||||
@ -49,8 +45,6 @@ HEALTHCHECK --interval=1m --timeout=5s --start-period=10s \
|
|||||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||||
CMD ["/usr/bin/rspamd", "-f", "-u", "_rspamd", "-g", "_rspamd"]
|
CMD ["/usr/bin/rspamd", "-f", "-u", "_rspamd", "-g", "_rspamd"]
|
||||||
|
|
||||||
STOPSIGNAL SIGTERM
|
|
||||||
|
|
||||||
# Setup Labels
|
# Setup Labels
|
||||||
ARG VERSION
|
ARG VERSION
|
||||||
ARG COMMIT
|
ARG COMMIT
|
||||||
@ -66,4 +60,4 @@ LABEL org.label-schema.name="Rspamd" \
|
|||||||
org.label-schema.version=$VERSION \
|
org.label-schema.version=$VERSION \
|
||||||
org.label-schema.vcs-url="https://github.com/rspamd/rspamd/" \
|
org.label-schema.vcs-url="https://github.com/rspamd/rspamd/" \
|
||||||
org.label-schema.vcs-ref=$COMMIT \
|
org.label-schema.vcs-ref=$COMMIT \
|
||||||
org.label-schema.build-date=$DATE \
|
org.label-schema.build-date=$DATE \
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
# High spam networks, disabled by default
|
|
||||||
# ASN SCORE DESC
|
|
||||||
# Remove comment to enable score
|
|
||||||
#12874 5 #Fastweb SpA, Italy
|
|
||||||
#12876 2 #ONLINE S.A.S, France
|
|
||||||
#13335 5 #Cloudflare Inc., United States
|
|
||||||
#14061 4 #DigitalOcean LLC, United States
|
|
||||||
#16276 2 #OVH SAS, France
|
|
||||||
#21100 2 #ITL LLC, Ukraine
|
|
||||||
#28753 5 #Leaseweb Deutschland GmbH, Germany
|
|
||||||
#29119 5 #ServiHosting Networks S.L., Spain
|
|
||||||
#29422 5 #Telia Inmics-Nebula Oy, Finland
|
|
||||||
#30823 3 #combahton GmbH, Germany
|
|
||||||
#31034 5 #Aruba S.p.A, Italy
|
|
||||||
#39364 4 #Hormoz IT & Network Waves Connection Co. (PJS), Iran
|
|
||||||
#42831 5 #UK Dedicated Servers Limited, United Kingdom
|
|
||||||
#43146 2 #Domain names registrar REG.RU Ltd, Russia
|
|
||||||
#44493 2 #Chelyabinsk-Signal LLC, Russia
|
|
||||||
#46606 2 #Unified Layer, United States
|
|
||||||
#49100 4 #Pishgaman Toseeh Ertebatat Company (Private Joint Stock), Iran
|
|
||||||
#49505 2 #OOO Network of data-centers Selectel, Russia
|
|
||||||
#53755 5 #Input Output Flood LLC, United States
|
|
||||||
#55293 4 #A2 Hosting Inc., United States
|
|
||||||
#61272 5 #Informacines sistemos ir technologijos - UAB, Lithuania
|
|
||||||
#62255 4 #Asmunda New Media Ltd., Seychelles
|
|
||||||
#63018 4 #Dedicated.com, United States
|
|
||||||
#197518 2 #Rackmarkt SL, Spain
|
|
||||||
#197695 2 #Domain names registrar REG.RU Ltd, Russia
|
|
||||||
#198068 2 #P.A.G.M. OU, Estonia
|
|
||||||
#201942 5 #Soltia Consulting SL, Spain
|
|
||||||
#213373 4 #IP Connect Inc
|
|
@ -1,2 +0,0 @@
|
|||||||
/Thread-Topic:\s[a-zA-Z]{3}\s[a-zA-Z]{2}[\s\r\n]{0,1}[^a-zA-Z0-9][\r\n]/i
|
|
||||||
/Thread-Topic:\s[a-zA-Z]{3}\s[a-zA-Z]{2}\s[a-zA-Z]{1}\s[a-zA-Z]{5}[\s\r\n]{0,1}[^a-zA-Z0-9][\r\n]/i
|
|
@ -1 +0,0 @@
|
|||||||
# Regex! /de/ will also match /de_at/ etc.
|
|
@ -1,29 +0,0 @@
|
|||||||
/\serotic\s/i
|
|
||||||
/\serection\s/i
|
|
||||||
/\ssexy\s/i
|
|
||||||
/\sass\s/i
|
|
||||||
/\sviagra\s/i
|
|
||||||
/\stits\s/i
|
|
||||||
/\stitty\s/i
|
|
||||||
/\stitties\s/i
|
|
||||||
/\scum\s/i
|
|
||||||
/\ssperm\s/i
|
|
||||||
/\sslut\s/i
|
|
||||||
/\sporn\s/i
|
|
||||||
/\scock\s/i
|
|
||||||
/\spharma\s/i
|
|
||||||
/\spharmacy\s/i
|
|
||||||
/\sseo\s/i
|
|
||||||
/\sjackpot\s/i
|
|
||||||
/\slottery\s/i
|
|
||||||
/bitcoin/i
|
|
||||||
/trojaner/i
|
|
||||||
/malware/i
|
|
||||||
/\sscooter\s/i
|
|
||||||
/testost/i
|
|
||||||
/web\sdevelopment/i
|
|
||||||
/\slottery\s/i
|
|
||||||
/\ssex\s/i
|
|
||||||
/\svagina\s/i
|
|
||||||
/\spenis\s/i
|
|
||||||
/\smarketing\s/i
|
|
@ -1,17 +0,0 @@
|
|||||||
/\slotto\s/i
|
|
||||||
/pillenversand/i
|
|
||||||
/\skredithilfe\s/i
|
|
||||||
/\skapital\s/i
|
|
||||||
/\skrankenversicherung\s/i
|
|
||||||
/pädophil/i
|
|
||||||
/paedophil/i
|
|
||||||
/freiberufler/i
|
|
||||||
/unternehmer/i
|
|
||||||
/masturbieren/i
|
|
||||||
/\sescooter\s/i
|
|
||||||
/\se-scooter\s/i
|
|
||||||
/testost/i
|
|
||||||
/\spotenz\s/i
|
|
||||||
/potenzmittel/i
|
|
||||||
/rezeptfrei/i
|
|
||||||
/apotheke/i
|
|
@ -1,19 +0,0 @@
|
|||||||
/X-EMV-Platform; .*/i
|
|
||||||
/.*nur-1-click.*/i
|
|
||||||
/.*episerver.*/i
|
|
||||||
/.*supergewinne.*/i
|
|
||||||
/List-Unsubscribe.*nbps\.eu/i
|
|
||||||
/.*regiofinder.*/i
|
|
||||||
/.*EmailSocket.*/i
|
|
||||||
/List-Unsubscribe:.*respread.*/i
|
|
||||||
/.*greenflamingo.*/i
|
|
||||||
/.*senderemailglobal.*/i
|
|
||||||
/.*promio\.net.*/i
|
|
||||||
/.*promio\.de.*/i
|
|
||||||
/.*mailer-service\.com.*/i
|
|
||||||
/.*mailer-service\.de.*/i
|
|
||||||
/.*dynamic-lht.*/i
|
|
||||||
/.*light-house-traffic.*/i
|
|
||||||
/.*newsletterplus.*/i
|
|
||||||
/.*X-Chpo.*/i
|
|
||||||
/.*List-Unsubscribe:.*@nl\..*/i
|
|
@ -1,65 +0,0 @@
|
|||||||
/.+\.accountant$/i
|
|
||||||
/.+\.art$/i
|
|
||||||
/.+\.asia$/i
|
|
||||||
/.+\.bid$/i
|
|
||||||
/.+\.biz$/i
|
|
||||||
/.+\.care$/i
|
|
||||||
/.+\.cf$/i
|
|
||||||
/.+\.click$/i
|
|
||||||
/.+\.cloud$/i
|
|
||||||
/.+\.co$/i
|
|
||||||
/.+\.construction$/i
|
|
||||||
/.+\.country$/i
|
|
||||||
/.+\.cricket$/i
|
|
||||||
/.+\.date$/i
|
|
||||||
/.+\.desi$/i
|
|
||||||
/.+\.download$/i
|
|
||||||
/.+\.estate$/i
|
|
||||||
/.+\.faith$/i
|
|
||||||
/.+\.fit$/i
|
|
||||||
/.+\.flights$/i
|
|
||||||
/.+\.ga$/i
|
|
||||||
/.+\.gdn$/i
|
|
||||||
/.+\.gq$/i
|
|
||||||
/.+\.guru$/i
|
|
||||||
/.+\.icu$/i
|
|
||||||
/.+\.id$/i
|
|
||||||
/.+\.info$/i
|
|
||||||
/.+\.in.net$/i
|
|
||||||
/.+\.ir$/i
|
|
||||||
/.+\.jetzt$/i
|
|
||||||
/.+\.kim$/i
|
|
||||||
/.+\.life$/i
|
|
||||||
/.+\.link$/i
|
|
||||||
/.+\.loan$/i
|
|
||||||
/.+\.mk$/i
|
|
||||||
/.+\.ml$/i
|
|
||||||
/.+\.ninja$/i
|
|
||||||
/.+\.online$/i
|
|
||||||
/.+\.ooo$/i
|
|
||||||
/.+\.party$/i
|
|
||||||
/.+\.pro$/i
|
|
||||||
/.+\.ps$/i
|
|
||||||
/.+\.pw$/i
|
|
||||||
/.+\.racing$/i
|
|
||||||
/.+\.review$/i
|
|
||||||
/.+\.rocks$/i
|
|
||||||
/.+\.ryukyu$/i
|
|
||||||
/.+\.science$/i
|
|
||||||
/.+\.site$/i
|
|
||||||
/.+\.space$/i
|
|
||||||
/.+\.stream$/i
|
|
||||||
/.+\.sucks$/i
|
|
||||||
/.+\.tk$/i
|
|
||||||
/.+\.top$/i
|
|
||||||
/.+\.topica\.com$/i
|
|
||||||
/.+\.town$/i
|
|
||||||
/.+\.trade$/i
|
|
||||||
/.+\.uno$/i
|
|
||||||
/.+\.vip$/i
|
|
||||||
/.+\.webcam$/i
|
|
||||||
/.+\.website$/i
|
|
||||||
/.+\.win$/i
|
|
||||||
/.+\.work$/i
|
|
||||||
/.+\.world$/i
|
|
||||||
/.+\.xyz$/i
|
|
@ -1 +0,0 @@
|
|||||||
# /.+example\.com/i
|
|
@ -1 +0,0 @@
|
|||||||
# /.+example\.com/i
|
|
@ -1 +0,0 @@
|
|||||||
# /.+example\.com/i
|
|
@ -1 +0,0 @@
|
|||||||
# /.+example\.com/i
|
|
@ -1 +0,0 @@
|
|||||||
# /.+example\.com/i
|
|
@ -1 +0,0 @@
|
|||||||
# /.+example\.com/i
|
|
@ -1,4 +0,0 @@
|
|||||||
# IP whitelist
|
|
||||||
# 127.0.0.1
|
|
||||||
# 1.2.3.4
|
|
||||||
# ...
|
|
@ -1,7 +0,0 @@
|
|||||||
# Skip logging for these addresses
|
|
||||||
/monitoring-system@everycloudtech\.us/i
|
|
||||||
/monitor@tools\.mailflowmonitoring\.com/i
|
|
||||||
/watchdog@localhost/i
|
|
||||||
/supertool@mxtoolbox\.com/i
|
|
||||||
/test@mxtoolboxsmtpdiag\.com/i
|
|
||||||
/open-relay-check@mailcow\.email/i
|
|
@ -1,174 +0,0 @@
|
|||||||
<?php
|
|
||||||
// File size is limited by Nginx site to 10M
|
|
||||||
// To speed things up, we do not include prerequisites
|
|
||||||
header('Content-Type: text/plain');
|
|
||||||
require_once "vars.inc.php";
|
|
||||||
// Do not show errors, we log to using error_log
|
|
||||||
ini_set('error_reporting', 0);
|
|
||||||
// Init database
|
|
||||||
//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
|
|
||||||
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
|
|
||||||
$opt = [
|
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
||||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
||||||
PDO::ATTR_EMULATE_PREPARES => false,
|
|
||||||
];
|
|
||||||
try {
|
|
||||||
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
|
||||||
}
|
|
||||||
catch (PDOException $e) {
|
|
||||||
error_log("ALIASEXP: " . $e . PHP_EOL);
|
|
||||||
http_response_code(501);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init Redis
|
|
||||||
$redis = new Redis();
|
|
||||||
$redis->connect('redis-mailcow', 6379);
|
|
||||||
|
|
||||||
function parse_email($email) {
|
|
||||||
if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
|
|
||||||
$a = strrpos($email, '@');
|
|
||||||
return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1));
|
|
||||||
}
|
|
||||||
if (!function_exists('getallheaders')) {
|
|
||||||
function getallheaders() {
|
|
||||||
if (!is_array($_SERVER)) {
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
$headers = array();
|
|
||||||
foreach ($_SERVER as $name => $value) {
|
|
||||||
if (substr($name, 0, 5) == 'HTTP_') {
|
|
||||||
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $headers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read headers
|
|
||||||
$headers = getallheaders();
|
|
||||||
// Get rcpt
|
|
||||||
$rcpt = $headers['Rcpt'];
|
|
||||||
// Remove tag
|
|
||||||
$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt);
|
|
||||||
// Parse email address
|
|
||||||
$parsed_rcpt = parse_email($rcpt);
|
|
||||||
// Create array of final mailboxes
|
|
||||||
$rcpt_final_mailboxes = array();
|
|
||||||
|
|
||||||
// Skip if not a mailcow handled domain
|
|
||||||
try {
|
|
||||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) {
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (RedisException $e) {
|
|
||||||
error_log("ALIASEXP: " . $e . PHP_EOL);
|
|
||||||
http_response_code(504);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always assume rcpt is not a final mailbox but an alias for a mailbox or further aliases
|
|
||||||
//
|
|
||||||
// rcpt
|
|
||||||
// |
|
|
||||||
// mailbox <-- goto ---> alias1, alias2, mailbox2
|
|
||||||
// | |
|
|
||||||
// mailbox3 |
|
|
||||||
// |
|
|
||||||
// alias3 ---> mailbox4
|
|
||||||
//
|
|
||||||
try {
|
|
||||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':rcpt' => $rcpt
|
|
||||||
));
|
|
||||||
$gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
|
||||||
if (empty($gotos)) {
|
|
||||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':rcpt' => '@' . $parsed_rcpt['domain']
|
|
||||||
));
|
|
||||||
$gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
|
||||||
}
|
|
||||||
if (empty($gotos)) {
|
|
||||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :rcpt AND `active` = '1'");
|
|
||||||
$stmt->execute(array(':rcpt' => $parsed_rcpt['domain']));
|
|
||||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
|
||||||
if ($goto_branch) {
|
|
||||||
$gotos = $parsed_rcpt['local'] . '@' . $goto_branch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$gotos_array = explode(',', $gotos);
|
|
||||||
|
|
||||||
$loop_c = 0;
|
|
||||||
|
|
||||||
while (count($gotos_array) != 0 && $loop_c <= 20) {
|
|
||||||
|
|
||||||
// Loop through all found gotos
|
|
||||||
foreach ($gotos_array as $index => &$goto) {
|
|
||||||
error_log("ALIAS EXPANDER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL);
|
|
||||||
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');");
|
|
||||||
$stmt->execute(array(':goto' => $goto));
|
|
||||||
$username = $stmt->fetch(PDO::FETCH_ASSOC)['username'];
|
|
||||||
if (!empty($username)) {
|
|
||||||
error_log("ALIAS EXPANDER: http pipe: mailbox found: " . $username . PHP_EOL);
|
|
||||||
// Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate
|
|
||||||
if (!in_array($username, $rcpt_final_mailboxes)) {
|
|
||||||
$rcpt_final_mailboxes[] = $username;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$parsed_goto = parse_email($goto);
|
|
||||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
|
||||||
error_log("ALIAS EXPANDER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'");
|
|
||||||
$stmt->execute(array(':goto' => $goto));
|
|
||||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
|
||||||
if ($goto_branch) {
|
|
||||||
error_log("ALIAS EXPANDER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
|
|
||||||
$goto_branch_array = explode(',', $goto_branch);
|
|
||||||
} else {
|
|
||||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'");
|
|
||||||
$stmt->execute(array(':domain' => $parsed_goto['domain']));
|
|
||||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
|
||||||
if ($goto_branch) {
|
|
||||||
error_log("ALIAS EXPANDER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL);
|
|
||||||
$goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// goto item was processed, unset
|
|
||||||
unset($gotos_array[$index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge goto branch array derived from previous loop (if any), filter duplicates and unset goto branch array
|
|
||||||
if (!empty($goto_branch_array)) {
|
|
||||||
$gotos_array = array_unique(array_merge($gotos_array, $goto_branch_array));
|
|
||||||
unset($goto_branch_array);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reindex array
|
|
||||||
$gotos_array = array_values($gotos_array);
|
|
||||||
|
|
||||||
// Force exit if loop cannot be solved
|
|
||||||
// Postfix does not allow for alias loops, so this should never happen.
|
|
||||||
$loop_c++;
|
|
||||||
error_log("ALIAS EXPANDER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (PDOException $e) {
|
|
||||||
error_log("ALIAS EXPANDER: " . $e->getMessage() . PHP_EOL);
|
|
||||||
http_response_code(502);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Does also return the mailbox name if question == answer (query == mailbox)
|
|
||||||
if (count($rcpt_final_mailboxes) == 1) {
|
|
||||||
error_log("ALIASEXP: direct alias " . $rcpt . " expanded to " . $rcpt_final_mailboxes[0] . PHP_EOL);
|
|
||||||
echo trim($rcpt_final_mailboxes[0]);
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
<?php
|
|
||||||
// File size is limited by Nginx site to 10M
|
|
||||||
// To speed things up, we do not include prerequisites
|
|
||||||
header('Content-Type: text/plain');
|
|
||||||
require_once "vars.inc.php";
|
|
||||||
// Do not show errors, we log to using error_log
|
|
||||||
ini_set('error_reporting', 0);
|
|
||||||
// Init database
|
|
||||||
//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
|
|
||||||
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
|
|
||||||
$opt = [
|
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
||||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
||||||
PDO::ATTR_EMULATE_PREPARES => false,
|
|
||||||
];
|
|
||||||
try {
|
|
||||||
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
|
||||||
}
|
|
||||||
catch (PDOException $e) {
|
|
||||||
error_log("BCC MAP SQL ERROR: " . $e . PHP_EOL);
|
|
||||||
http_response_code(501);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parse_email($email) {
|
|
||||||
if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
|
|
||||||
$a = strrpos($email, '@');
|
|
||||||
return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1));
|
|
||||||
}
|
|
||||||
if (!function_exists('getallheaders')) {
|
|
||||||
function getallheaders() {
|
|
||||||
if (!is_array($_SERVER)) {
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
$headers = array();
|
|
||||||
foreach ($_SERVER as $name => $value) {
|
|
||||||
if (substr($name, 0, 5) == 'HTTP_') {
|
|
||||||
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $headers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read headers
|
|
||||||
$headers = getallheaders();
|
|
||||||
// Get rcpt
|
|
||||||
$rcpt = $headers['Rcpt'];
|
|
||||||
// Get from
|
|
||||||
$from = $headers['From'];
|
|
||||||
// Remove tags
|
|
||||||
$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt);
|
|
||||||
$from = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $from);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!empty($rcpt)) {
|
|
||||||
$stmt = $pdo->prepare("SELECT `bcc_dest` FROM `bcc_maps` WHERE `type` = 'rcpt' AND `local_dest` = :local_dest AND `active` = '1'");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':local_dest' => $rcpt
|
|
||||||
));
|
|
||||||
$bcc_dest = $stmt->fetch(PDO::FETCH_ASSOC)['bcc_dest'];
|
|
||||||
if (!empty($bcc_dest) && filter_var($bcc_dest, FILTER_VALIDATE_EMAIL)) {
|
|
||||||
error_log("BCC MAP: returning ". $bcc_dest . " for " . $rcpt . PHP_EOL);
|
|
||||||
http_response_code(201);
|
|
||||||
echo trim($bcc_dest);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!empty($from)) {
|
|
||||||
$stmt = $pdo->prepare("SELECT `bcc_dest` FROM `bcc_maps` WHERE `type` = 'sender' AND `local_dest` = :local_dest AND `active` = '1'");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':local_dest' => $from
|
|
||||||
));
|
|
||||||
$bcc_dest = $stmt->fetch(PDO::FETCH_ASSOC)['bcc_dest'];
|
|
||||||
if (!empty($bcc_dest) && filter_var($bcc_dest, FILTER_VALIDATE_EMAIL)) {
|
|
||||||
error_log("BCC MAP: returning ". $bcc_dest . " for " . $from . PHP_EOL);
|
|
||||||
http_response_code(201);
|
|
||||||
echo trim($bcc_dest);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (PDOException $e) {
|
|
||||||
error_log("BCC MAP SQL ERROR: " . $e->getMessage() . PHP_EOL);
|
|
||||||
http_response_code(502);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
|||||||
<?php
|
|
||||||
// File size is limited by Nginx site to 10M
|
|
||||||
// To speed things up, we do not include prerequisites
|
|
||||||
header('Content-Type: text/plain');
|
|
||||||
require_once "vars.inc.php";
|
|
||||||
// Do not show errors, we log to using error_log
|
|
||||||
ini_set('error_reporting', 0);
|
|
||||||
// Init database
|
|
||||||
//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
|
|
||||||
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
|
|
||||||
$opt = [
|
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
||||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
||||||
PDO::ATTR_EMULATE_PREPARES => false,
|
|
||||||
];
|
|
||||||
try {
|
|
||||||
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
|
||||||
}
|
|
||||||
catch (PDOException $e) {
|
|
||||||
error_log("FOOTER: " . $e . PHP_EOL);
|
|
||||||
http_response_code(501);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!function_exists('getallheaders')) {
|
|
||||||
function getallheaders() {
|
|
||||||
if (!is_array($_SERVER)) {
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
$headers = array();
|
|
||||||
foreach ($_SERVER as $name => $value) {
|
|
||||||
if (substr($name, 0, 5) == 'HTTP_') {
|
|
||||||
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $headers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read headers
|
|
||||||
$headers = getallheaders();
|
|
||||||
// Get Domain
|
|
||||||
$domain = $headers['Domain'];
|
|
||||||
// Get Username
|
|
||||||
$username = $headers['Username'];
|
|
||||||
// Get From
|
|
||||||
$from = $headers['From'];
|
|
||||||
// define empty footer
|
|
||||||
$empty_footer = json_encode(array(
|
|
||||||
'html' => '',
|
|
||||||
'plain' => '',
|
|
||||||
'skip_replies' => 0,
|
|
||||||
'vars' => array()
|
|
||||||
));
|
|
||||||
|
|
||||||
error_log("FOOTER: checking for domain " . $domain . ", user " . $username . " and address " . $from . PHP_EOL);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// try get $target_domain if $domain is an alias_domain
|
|
||||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain`
|
|
||||||
WHERE `alias_domain` = :alias_domain");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':alias_domain' => $domain
|
|
||||||
));
|
|
||||||
$alias_domain = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
if (!$alias_domain) {
|
|
||||||
$target_domain = $domain;
|
|
||||||
} else {
|
|
||||||
$target_domain = $alias_domain['target_domain'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// get footer associated with the domain
|
|
||||||
$stmt = $pdo->prepare("SELECT `plain`, `html`, `mbox_exclude`, `alias_domain_exclude`, `skip_replies` FROM `domain_wide_footer`
|
|
||||||
WHERE `domain` = :domain");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':domain' => $target_domain
|
|
||||||
));
|
|
||||||
$footer = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
// check if the sender is excluded
|
|
||||||
if (in_array($from, json_decode($footer['mbox_exclude']))){
|
|
||||||
$footer = false;
|
|
||||||
}
|
|
||||||
if (in_array($domain, json_decode($footer['alias_domain_exclude']))){
|
|
||||||
$footer = false;
|
|
||||||
}
|
|
||||||
if (empty($footer)){
|
|
||||||
echo $empty_footer;
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
error_log("FOOTER: " . json_encode($footer) . PHP_EOL);
|
|
||||||
|
|
||||||
// footer will be applied
|
|
||||||
// get custom mailbox attributes to insert into the footer
|
|
||||||
$stmt = $pdo->prepare("SELECT `custom_attributes` FROM `mailbox` WHERE `username` = :username");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':username' => $username
|
|
||||||
));
|
|
||||||
$custom_attributes = $stmt->fetch(PDO::FETCH_ASSOC)['custom_attributes'];
|
|
||||||
if (empty($custom_attributes)){
|
|
||||||
$custom_attributes = (object)array();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception $e) {
|
|
||||||
error_log("FOOTER: " . $e->getMessage() . PHP_EOL);
|
|
||||||
http_response_code(502);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// return footer
|
|
||||||
$footer["vars"] = $custom_attributes;
|
|
||||||
echo json_encode($footer);
|
|
@ -1,57 +0,0 @@
|
|||||||
<?php
|
|
||||||
header('Content-Type: text/plain');
|
|
||||||
ini_set('error_reporting', 0);
|
|
||||||
|
|
||||||
$redis = new Redis();
|
|
||||||
$redis->connect('redis-mailcow', 6379);
|
|
||||||
|
|
||||||
function in_net($addr, $net) {
|
|
||||||
$net = explode('/', $net);
|
|
||||||
if (count($net) > 1) {
|
|
||||||
$mask = $net[1];
|
|
||||||
}
|
|
||||||
$net = inet_pton($net[0]);
|
|
||||||
$addr = inet_pton($addr);
|
|
||||||
$length = strlen($net); // 4 for IPv4, 16 for IPv6
|
|
||||||
if (strlen($net) != strlen($addr)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!isset($mask)) {
|
|
||||||
$mask = $length * 8;
|
|
||||||
}
|
|
||||||
$addr_bin = '';
|
|
||||||
$net_bin = '';
|
|
||||||
for ($i = 0; $i < $length; ++$i) {
|
|
||||||
$addr_bin .= str_pad(decbin(ord(substr($addr, $i, $i+1))), 8, '0', STR_PAD_LEFT);
|
|
||||||
$net_bin .= str_pad(decbin(ord(substr($net, $i, $i+1))), 8, '0', STR_PAD_LEFT);
|
|
||||||
}
|
|
||||||
return substr($addr_bin, 0, $mask) == substr($net_bin, 0, $mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($_GET['host'])) {
|
|
||||||
try {
|
|
||||||
foreach ($redis->hGetAll('WHITELISTED_FWD_HOST') as $host => $source) {
|
|
||||||
if (in_net($_GET['host'], $host)) {
|
|
||||||
echo '200 PERMIT';
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
echo '200 DUNNO';
|
|
||||||
}
|
|
||||||
catch (RedisException $e) {
|
|
||||||
echo '200 DUNNO';
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
echo '240.240.240.240' . PHP_EOL;
|
|
||||||
foreach ($redis->hGetAll('WHITELISTED_FWD_HOST') as $host => $source) {
|
|
||||||
echo $host . PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (RedisException $e) {
|
|
||||||
echo '240.240.240.240' . PHP_EOL;
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
@ -1,2 +0,0 @@
|
|||||||
<html>
|
|
||||||
</html>
|
|
@ -1,2 +0,0 @@
|
|||||||
<?php
|
|
||||||
// PoC
|
|
@ -1,471 +0,0 @@
|
|||||||
<?php
|
|
||||||
/*
|
|
||||||
The match section performs AND operation on different matches: for example, if you have from and rcpt in the same rule,
|
|
||||||
then the rule matches only when from AND rcpt match. For similar matches, the OR rule applies: if you have multiple rcpt matches,
|
|
||||||
then any of these will trigger the rule. If a rule is triggered then no more rules are matched.
|
|
||||||
*/
|
|
||||||
header('Content-Type: text/plain');
|
|
||||||
require_once "vars.inc.php";
|
|
||||||
// Getting headers sent by the client.
|
|
||||||
ini_set('error_reporting', 0);
|
|
||||||
|
|
||||||
//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
|
|
||||||
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
|
|
||||||
$opt = [
|
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
||||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
||||||
PDO::ATTR_EMULATE_PREPARES => false,
|
|
||||||
];
|
|
||||||
try {
|
|
||||||
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
|
||||||
$stmt = $pdo->query("SELECT '1' FROM `filterconf`");
|
|
||||||
}
|
|
||||||
catch (PDOException $e) {
|
|
||||||
echo 'settings { }';
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if db changed and return header
|
|
||||||
$stmt = $pdo->prepare("SELECT GREATEST(COALESCE(MAX(UNIX_TIMESTAMP(UPDATE_TIME)), 1), COALESCE(MAX(UNIX_TIMESTAMP(CREATE_TIME)), 1)) AS `db_update_time` FROM `information_schema`.`tables`
|
|
||||||
WHERE (`TABLE_NAME` = 'filterconf' OR `TABLE_NAME` = 'settingsmap' OR `TABLE_NAME` = 'sogo_quick_contact' OR `TABLE_NAME` = 'alias')
|
|
||||||
AND TABLE_SCHEMA = :dbname;");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':dbname' => $database_name
|
|
||||||
));
|
|
||||||
$db_update_time = $stmt->fetch(PDO::FETCH_ASSOC)['db_update_time'];
|
|
||||||
if (empty($db_update_time)) {
|
|
||||||
$db_update_time = 1572048000;
|
|
||||||
}
|
|
||||||
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && (strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $db_update_time)) {
|
|
||||||
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 304);
|
|
||||||
exit;
|
|
||||||
} else {
|
|
||||||
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parse_email($email) {
|
|
||||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
|
|
||||||
$a = strrpos($email, '@');
|
|
||||||
return array('local' => substr($email, 0, $a), 'domain' => substr($email, $a));
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalize_email($email) {
|
|
||||||
$email = strtolower(str_replace('/', '\/', $email));
|
|
||||||
$gm = "@gmail.com";
|
|
||||||
if (substr_compare($email, $gm, -strlen($gm)) == 0) {
|
|
||||||
$email = explode('@', $email);
|
|
||||||
$email[0] = str_replace('.', '', $email[0]);
|
|
||||||
$email = implode('@', $email);
|
|
||||||
}
|
|
||||||
$gm_alt = "@googlemail.com";
|
|
||||||
if (substr_compare($email, $gm_alt, -strlen($gm_alt)) == 0) {
|
|
||||||
$email = explode('@', $email);
|
|
||||||
$email[0] = str_replace('.', '', $email[0]);
|
|
||||||
$email[1] = str_replace('@', '', $gm);
|
|
||||||
$email = implode('@', $email);
|
|
||||||
}
|
|
||||||
if (str_contains($email, "+")) {
|
|
||||||
$email = explode('@', $email);
|
|
||||||
$user = explode('+', $email[0]);
|
|
||||||
$email[0] = $user[0];
|
|
||||||
$email = implode('@', $email);
|
|
||||||
}
|
|
||||||
return $email;
|
|
||||||
}
|
|
||||||
|
|
||||||
function wl_by_sogo() {
|
|
||||||
global $pdo;
|
|
||||||
$rcpt = array();
|
|
||||||
$stmt = $pdo->query("SELECT DISTINCT(`sogo_folder_info`.`c_path2`) AS `user`, GROUP_CONCAT(`sogo_quick_contact`.`c_mail`) AS `contacts` FROM `sogo_folder_info`
|
|
||||||
INNER JOIN `sogo_quick_contact` ON `sogo_quick_contact`.`c_folder_id` = `sogo_folder_info`.`c_folder_id`
|
|
||||||
GROUP BY `c_path2`");
|
|
||||||
$sogo_contacts = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
while ($row = array_shift($sogo_contacts)) {
|
|
||||||
foreach (explode(',', $row['contacts']) as $contact) {
|
|
||||||
if (!filter_var($contact, FILTER_VALIDATE_EMAIL)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Explicit from, no mime_from, no regex - envelope must match
|
|
||||||
// mailcow white and blacklists also cover mime_from
|
|
||||||
$rcpt[$row['user']][] = normalize_email($contact);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $rcpt;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ucl_rcpts($object, $type) {
|
|
||||||
global $pdo;
|
|
||||||
$rcpt = array();
|
|
||||||
if ($type == 'mailbox') {
|
|
||||||
// Standard aliases
|
|
||||||
$stmt = $pdo->prepare("SELECT `address` FROM `alias`
|
|
||||||
WHERE `goto` = :object_goto
|
|
||||||
AND `address` NOT LIKE '@%'");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':object_goto' => $object
|
|
||||||
));
|
|
||||||
$standard_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
while ($row = array_shift($standard_aliases)) {
|
|
||||||
$local = parse_email($row['address'])['local'];
|
|
||||||
$domain = parse_email($row['address'])['domain'];
|
|
||||||
if (!empty($local) && !empty($domain)) {
|
|
||||||
$rcpt[] = '/^' . str_replace('/', '\/', $local) . '[+].*' . str_replace('/', '\/', $domain) . '$/i';
|
|
||||||
}
|
|
||||||
$rcpt[] = str_replace('/', '\/', $row['address']);
|
|
||||||
}
|
|
||||||
// Aliases by alias domains
|
|
||||||
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias` FROM `mailbox`
|
|
||||||
LEFT OUTER JOIN `alias_domain` ON `mailbox`.`domain` = `alias_domain`.`target_domain`
|
|
||||||
WHERE `mailbox`.`username` = :object");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':object' => $object
|
|
||||||
));
|
|
||||||
$by_domain_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
array_filter($by_domain_aliases);
|
|
||||||
while ($row = array_shift($by_domain_aliases)) {
|
|
||||||
if (!empty($row['alias'])) {
|
|
||||||
$local = parse_email($row['alias'])['local'];
|
|
||||||
$domain = parse_email($row['alias'])['domain'];
|
|
||||||
if (!empty($local) && !empty($domain)) {
|
|
||||||
$rcpt[] = '/^' . str_replace('/', '\/', $local) . '[+].*' . str_replace('/', '\/', $domain) . '$/i';
|
|
||||||
}
|
|
||||||
$rcpt[] = str_replace('/', '\/', $row['alias']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elseif ($type == 'domain') {
|
|
||||||
// Domain self
|
|
||||||
$rcpt[] = '/.*@' . $object . '/i';
|
|
||||||
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain`
|
|
||||||
WHERE `target_domain` = :object");
|
|
||||||
$stmt->execute(array(':object' => $object));
|
|
||||||
$alias_domains = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
array_filter($alias_domains);
|
|
||||||
while ($row = array_shift($alias_domains)) {
|
|
||||||
$rcpt[] = '/.*@' . $row['alias_domain'] . '/i';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $rcpt;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
settings {
|
|
||||||
watchdog {
|
|
||||||
priority = 10;
|
|
||||||
rcpt_mime = "/null@localhost/i";
|
|
||||||
from_mime = "/watchdog@localhost/i";
|
|
||||||
apply "default" {
|
|
||||||
symbols_disabled = ["HISTORY_SAVE", "ARC", "ARC_SIGNED", "DKIM", "DKIM_SIGNED", "CLAM_VIRUS"];
|
|
||||||
want_spam = yes;
|
|
||||||
actions {
|
|
||||||
reject = 9999.0;
|
|
||||||
greylist = 9998.0;
|
|
||||||
"add header" = 9997.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Start custom scores for users
|
|
||||||
*/
|
|
||||||
|
|
||||||
$stmt = $pdo->query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'highspamlevel' OR `option` = 'lowspamlevel'");
|
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
while ($row = array_shift($rows)) {
|
|
||||||
$username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']);
|
|
||||||
?>
|
|
||||||
score_<?=$username_sane;?> {
|
|
||||||
priority = 4;
|
|
||||||
<?php
|
|
||||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
|
||||||
?>
|
|
||||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
$stmt = $pdo->prepare("SELECT `option`, `value` FROM `filterconf`
|
|
||||||
WHERE (`option` = 'highspamlevel' OR `option` = 'lowspamlevel')
|
|
||||||
AND `object`= :object");
|
|
||||||
$stmt->execute(array(':object' => $row['object']));
|
|
||||||
$spamscore = $stmt->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_GROUP);
|
|
||||||
?>
|
|
||||||
apply "default" {
|
|
||||||
actions {
|
|
||||||
reject = <?=$spamscore['highspamlevel'][0];?>;
|
|
||||||
greylist = <?=$spamscore['lowspamlevel'][0] - 1;?>;
|
|
||||||
"add header" = <?=$spamscore['lowspamlevel'][0];?>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Start SOGo contacts whitelist
|
|
||||||
// Priority 4, lower than a domain whitelist (5) and lower than a mailbox whitelist (6)
|
|
||||||
*/
|
|
||||||
|
|
||||||
foreach (wl_by_sogo() as $user => $contacts) {
|
|
||||||
$username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $user);
|
|
||||||
?>
|
|
||||||
whitelist_sogo_<?=$username_sane;?> {
|
|
||||||
<?php
|
|
||||||
foreach ($contacts as $contact) {
|
|
||||||
?>
|
|
||||||
from = <?=json_encode($contact, JSON_UNESCAPED_SLASHES);?>;
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
priority = 4;
|
|
||||||
<?php
|
|
||||||
foreach (ucl_rcpts($user, 'mailbox') as $rcpt) {
|
|
||||||
?>
|
|
||||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
apply "default" {
|
|
||||||
SOGO_CONTACT = -99.0;
|
|
||||||
}
|
|
||||||
symbols [
|
|
||||||
"SOGO_CONTACT"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Start whitelist
|
|
||||||
*/
|
|
||||||
|
|
||||||
$stmt = $pdo->query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'whitelist_from'");
|
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
while ($row = array_shift($rows)) {
|
|
||||||
$username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']);
|
|
||||||
?>
|
|
||||||
whitelist_<?=$username_sane;?> {
|
|
||||||
<?php
|
|
||||||
$list_items = array();
|
|
||||||
$stmt = $pdo->prepare("SELECT `value` FROM `filterconf`
|
|
||||||
WHERE `object`= :object
|
|
||||||
AND `option` = 'whitelist_from'");
|
|
||||||
$stmt->execute(array(':object' => $row['object']));
|
|
||||||
$list_items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
foreach ($list_items as $item) {
|
|
||||||
?>
|
|
||||||
from = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
|
|
||||||
?>
|
|
||||||
priority = 5;
|
|
||||||
<?php
|
|
||||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
|
||||||
?>
|
|
||||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
?>
|
|
||||||
priority = 6;
|
|
||||||
<?php
|
|
||||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
|
||||||
?>
|
|
||||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
apply "default" {
|
|
||||||
MAILCOW_WHITE = -999.0;
|
|
||||||
}
|
|
||||||
symbols [
|
|
||||||
"MAILCOW_WHITE"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
whitelist_mime_<?=$username_sane;?> {
|
|
||||||
<?php
|
|
||||||
foreach ($list_items as $item) {
|
|
||||||
?>
|
|
||||||
from_mime = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
|
|
||||||
?>
|
|
||||||
priority = 5;
|
|
||||||
<?php
|
|
||||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
|
||||||
?>
|
|
||||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
?>
|
|
||||||
priority = 6;
|
|
||||||
<?php
|
|
||||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
|
||||||
?>
|
|
||||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
apply "default" {
|
|
||||||
MAILCOW_WHITE = -999.0;
|
|
||||||
}
|
|
||||||
symbols [
|
|
||||||
"MAILCOW_WHITE"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Start blacklist
|
|
||||||
*/
|
|
||||||
|
|
||||||
$stmt = $pdo->query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'blacklist_from'");
|
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
while ($row = array_shift($rows)) {
|
|
||||||
$username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']);
|
|
||||||
?>
|
|
||||||
blacklist_<?=$username_sane;?> {
|
|
||||||
<?php
|
|
||||||
$list_items = array();
|
|
||||||
$stmt = $pdo->prepare("SELECT `value` FROM `filterconf`
|
|
||||||
WHERE `object`= :object
|
|
||||||
AND `option` = 'blacklist_from'");
|
|
||||||
$stmt->execute(array(':object' => $row['object']));
|
|
||||||
$list_items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
foreach ($list_items as $item) {
|
|
||||||
?>
|
|
||||||
from = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
|
|
||||||
?>
|
|
||||||
priority = 5;
|
|
||||||
<?php
|
|
||||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
|
||||||
?>
|
|
||||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
?>
|
|
||||||
priority = 6;
|
|
||||||
<?php
|
|
||||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
|
||||||
?>
|
|
||||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
apply "default" {
|
|
||||||
MAILCOW_BLACK = 999.0;
|
|
||||||
}
|
|
||||||
symbols [
|
|
||||||
"MAILCOW_BLACK"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
blacklist_header_<?=$username_sane;?> {
|
|
||||||
<?php
|
|
||||||
foreach ($list_items as $item) {
|
|
||||||
?>
|
|
||||||
from_mime = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
|
|
||||||
?>
|
|
||||||
priority = 5;
|
|
||||||
<?php
|
|
||||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
|
||||||
?>
|
|
||||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
?>
|
|
||||||
priority = 6;
|
|
||||||
<?php
|
|
||||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
|
||||||
?>
|
|
||||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
apply "default" {
|
|
||||||
MAILCOW_BLACK = 999.0;
|
|
||||||
}
|
|
||||||
symbols [
|
|
||||||
"MAILCOW_BLACK"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Start traps
|
|
||||||
*/
|
|
||||||
|
|
||||||
?>
|
|
||||||
ham_trap {
|
|
||||||
<?php
|
|
||||||
foreach (ucl_rcpts('ham@localhost', 'mailbox') as $rcpt) {
|
|
||||||
?>
|
|
||||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
priority = 9;
|
|
||||||
apply "default" {
|
|
||||||
symbols_enabled = ["HISTORY_SAVE"];
|
|
||||||
}
|
|
||||||
symbols [
|
|
||||||
"HAM_TRAP"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
spam_trap {
|
|
||||||
<?php
|
|
||||||
foreach (ucl_rcpts('spam@localhost', 'mailbox') as $rcpt) {
|
|
||||||
?>
|
|
||||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
priority = 9;
|
|
||||||
apply "default" {
|
|
||||||
symbols_enabled = ["HISTORY_SAVE"];
|
|
||||||
}
|
|
||||||
symbols [
|
|
||||||
"SPAM_TRAP"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
<?php
|
|
||||||
// Start additional content
|
|
||||||
|
|
||||||
$stmt = $pdo->query("SELECT `id`, `content` FROM `settingsmap` WHERE `active` = '1'");
|
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
while ($row = array_shift($rows)) {
|
|
||||||
$username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['id']);
|
|
||||||
?>
|
|
||||||
additional_settings_<?=intval($row['id']);?> {
|
|
||||||
<?php
|
|
||||||
$content = preg_split('/\r\n|\r|\n/', $row['content']);
|
|
||||||
foreach ($content as $line) {
|
|
||||||
echo ' ' . $line . PHP_EOL;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
}
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once('../../../web/inc/vars.inc.php');
|
|
||||||
if (file_exists('../../../web/inc/vars.local.inc.php')) {
|
|
||||||
include_once('../../../web/inc/vars.local.inc.php');
|
|
||||||
}
|
|
||||||
?>
|
|
@ -1,3 +0,0 @@
|
|||||||
reject = 15;
|
|
||||||
add_header = 8;
|
|
||||||
greylist = 7;
|
|
@ -1,11 +0,0 @@
|
|||||||
clamav {
|
|
||||||
# Scan whole message
|
|
||||||
scan_mime_parts = false;
|
|
||||||
#scan_text_mime = true;
|
|
||||||
#scan_image_mime = true;
|
|
||||||
symbol = "CLAM_VIRUS";
|
|
||||||
type = "clamav";
|
|
||||||
log_clean = true;
|
|
||||||
servers = "clamd:3310";
|
|
||||||
max_size = 20971520;
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
# If false, messages with empty envelope from are not signed
|
|
||||||
allow_envfrom_empty = true;
|
|
||||||
# If true, envelope/header domain mismatch is ignored
|
|
||||||
allow_hdrfrom_mismatch = true;
|
|
||||||
# If true, multiple from headers are allowed (but only first is used)
|
|
||||||
allow_hdrfrom_multiple = false;
|
|
||||||
# If true, username does not need to contain matching domain
|
|
||||||
allow_username_mismatch = false;
|
|
||||||
# If false, messages from authenticated users are not selected for signing
|
|
||||||
sign_authenticated = false;
|
|
||||||
# Default path to key, can include '$domain' and '$selector' variables
|
|
||||||
path = "/data/dkim/keys/$domain.dkim";
|
|
||||||
# Default selector to use
|
|
||||||
selector = "dkim";
|
|
||||||
# If false, messages from local networks are not selected for signing
|
|
||||||
sign_local = false;
|
|
||||||
# Symbol to add when message is signed
|
|
||||||
symbol = "ARC_SIGNED";
|
|
||||||
# Whether to fallback to global config
|
|
||||||
try_fallback = true;
|
|
||||||
# Domain to use for DKIM signing: can be "header" or "envelope"
|
|
||||||
use_domain = "recipient";
|
|
||||||
# Whether to normalise domains to eSLD
|
|
||||||
use_esld = false;
|
|
||||||
# Whether to get keys from Redis
|
|
||||||
use_redis = true;
|
|
||||||
# Hash for DKIM keys in Redis
|
|
||||||
key_prefix = "DKIM_PRIV_KEYS";
|
|
||||||
# Selector map
|
|
||||||
selector_prefix = "DKIM_SELECTORS";
|
|
||||||
sign_inbound = true;
|
|
||||||
use_domain_sign_inbound = "recipient";
|
|
@ -1,6 +0,0 @@
|
|||||||
provider_type = "rspamd";
|
|
||||||
provider_info {
|
|
||||||
ip4 = "asn.rspamd.com";
|
|
||||||
ip6 = "asn6.rspamd.com";
|
|
||||||
}
|
|
||||||
symbol = "ASN";
|
|
@ -1,110 +0,0 @@
|
|||||||
MX_IMPLICIT {
|
|
||||||
expression = "MX_GOOD & MX_MISSING";
|
|
||||||
score = -0.01;
|
|
||||||
}
|
|
||||||
VIRUS_FOUND {
|
|
||||||
expression = "CLAM_VIRUS & !MAILCOW_WHITE";
|
|
||||||
score = 2000.0;
|
|
||||||
}
|
|
||||||
# Bad policy from free mail providers
|
|
||||||
FREEMAIL_POLICY_FAILURE {
|
|
||||||
expression = "FREEMAIL_FROM & !DMARC_POLICY_ALLOW & !MAILLIST& !WHITELISTED_FWD_HOST & -g+:policies";
|
|
||||||
score = 16.0;
|
|
||||||
}
|
|
||||||
# Applies to freemail with undisclosed recipients
|
|
||||||
FREEMAIL_TO_UNDISC_RCPT {
|
|
||||||
expression = "FREEMAIL_FROM & ( MISSING_TO | R_UNDISC_RCPT | TO_EQ_FROM )";
|
|
||||||
score = 5.0;
|
|
||||||
}
|
|
||||||
# Bad policy from non-whitelisted senders
|
|
||||||
# Remove SOGO_CONTACT symbol for fwd hosts and senders with broken policy
|
|
||||||
SOGO_CONTACT_EXCLUDE {
|
|
||||||
expression = "(-WHITELISTED_FWD_HOST | -g+:policies) & ^SOGO_CONTACT & !DMARC_POLICY_ALLOW";
|
|
||||||
}
|
|
||||||
# Remove MAILCOW_WHITE symbol for senders with broken policy recieved not from fwd hosts
|
|
||||||
MAILCOW_WHITE_EXCLUDE {
|
|
||||||
expression = "^MAILCOW_WHITE & (-DMARC_POLICY_REJECT | -DMARC_POLICY_QUARANTINE | -R_SPF_PERMFAIL) & !WHITELISTED_FWD_HOST";
|
|
||||||
}
|
|
||||||
# Spoofed header from and broken policy (excluding sieve host, rspamd host, whitelisted senders, authenticated senders and forward hosts)
|
|
||||||
SPOOFED_UNAUTH {
|
|
||||||
expression = "!MAILCOW_AUTH & !MAILCOW_WHITE & !RSPAMD_HOST & !SIEVE_HOST & MAILCOW_DOMAIN_HEADER_FROM & !WHITELISTED_FWD_HOST & -g+:policies";
|
|
||||||
score = 50.0;
|
|
||||||
}
|
|
||||||
# Only apply to inbound unauthed and not whitelisted
|
|
||||||
OLEFY_MACRO {
|
|
||||||
expression = "!MAILCOW_AUTH & !MAILCOW_WHITE & OLETOOLS";
|
|
||||||
score = 20.0;
|
|
||||||
policy = "remove_weight";
|
|
||||||
}
|
|
||||||
# Applies to a content filter map
|
|
||||||
BAD_WORD_BAD_TLD {
|
|
||||||
expression = "FISHY_TLD & ( BAD_WORDS | BAD_WORDS_DE )";
|
|
||||||
score = 10.0;
|
|
||||||
}
|
|
||||||
# Forged with bad policies and not fwd host, keep bad policy symbols
|
|
||||||
FORGED_W_BAD_POLICY {
|
|
||||||
expression = "( -g+:policies | -R_SPF_NA) & ( ~FROM_NEQ_ENVFROM | ~FORGED_SENDER ) & !WHITELISTED_FWD_HOST & !DMARC_POLICY_ALLOW";
|
|
||||||
score = 3.0;
|
|
||||||
}
|
|
||||||
# Keep negative (good) scores for rbl, policies and hfilter, disable neural group
|
|
||||||
WL_FWD_HOST {
|
|
||||||
expression = "-WHITELISTED_FWD_HOST & (^g+:rbl | ^g+:policies | ^g+:hfilter | ^g:neural)";
|
|
||||||
}
|
|
||||||
# Exclude X-Spam like flags from scoring from fwd and sieve hosts
|
|
||||||
UPSTREAM_CHECKS_EXCLUDE_FWD_HOST {
|
|
||||||
expression = "(-SIEVE_HOST | -WHITELISTED_FWD_HOST) & (^UNITEDINTERNET_SPAM | ^SPAM_FLAG | ^KLMS_SPAM | ^AOL_SPAM | ^MICROSOFT_SPAM)";
|
|
||||||
}
|
|
||||||
# Remove fuzzy group from bounces
|
|
||||||
BOUNCE_FUZZY {
|
|
||||||
expression = "-BOUNCE & ^g+:fuzzy";
|
|
||||||
}
|
|
||||||
# Remove bayes ham if fuzzy denied
|
|
||||||
FUZZY_HAM_MISMATCH {
|
|
||||||
expression = "( -FUZZY_DENIED | -MAILCOW_FUZZY_DENIED | -LOCAL_FUZZY_DENIED ) & ( ^BAYES_HAM | ^NEURAL_HAM_LONG | ^NEURAL_HAM_SHORT )";
|
|
||||||
}
|
|
||||||
# Remove bayes spam if local fuzzy white
|
|
||||||
FUZZY_SPAM_MISMATCH {
|
|
||||||
expression = "( -LOCAL_FUZZY_WHITE ) & ( ^BAYES_SPAM | ^NEURAL_SPAM_LONG | ^NEURAL_SPAM_SHORT )";
|
|
||||||
}
|
|
||||||
WL_FWD_HOST {
|
|
||||||
expression = "-WHITELISTED_FWD_HOST & (^g+:rbl | ^g+:policies | ^g+:hfilter | ^g:neural)";
|
|
||||||
}
|
|
||||||
ENCRYPTED_CHAT {
|
|
||||||
expression = "CHAT_VERSION_HEADER & ENCRYPTED_PGP";
|
|
||||||
}
|
|
||||||
|
|
||||||
CLAMD_SPAM_FOUND {
|
|
||||||
expression = "CLAM_SECI_SPAM & !MAILCOW_WHITE";
|
|
||||||
description = "Probably Spam, Securite Spam Flag set through ClamAV";
|
|
||||||
score = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
CLAMD_BAD_PDF {
|
|
||||||
expression = "CLAM_SECI_PDF & !MAILCOW_WHITE";
|
|
||||||
description = "Bad PDF Found, Securite bad PDF Flag set through ClamAV";
|
|
||||||
score = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
CLAMD_BAD_JPG {
|
|
||||||
expression = "CLAM_SECI_JPG & !MAILCOW_WHITE";
|
|
||||||
description = "Bad JPG Found, Securite bad JPG Flag set through ClamAV";
|
|
||||||
score = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
CLAMD_ASCII_MALWARE {
|
|
||||||
expression = "CLAM_SECI_ASCII & !MAILCOW_WHITE";
|
|
||||||
description = "ASCII malware found, Securite ASCII malware Flag set through ClamAV";
|
|
||||||
score = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
CLAMD_HTML_MALWARE {
|
|
||||||
expression = "CLAM_SECI_HTML & !MAILCOW_WHITE";
|
|
||||||
description = "HTML malware found, Securite HTML malware Flag set through ClamAV";
|
|
||||||
score = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
CLAMD_JS_MALWARE {
|
|
||||||
expression = "CLAM_SECI_JS & !MAILCOW_WHITE";
|
|
||||||
description = "JS malware found, Securite JS malware Flag set through ClamAV";
|
|
||||||
score = 8;
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
# If false, messages with empty envelope from are not signed
|
|
||||||
allow_envfrom_empty = true;
|
|
||||||
# If true, envelope/header domain mismatch is ignored
|
|
||||||
allow_hdrfrom_mismatch = true;
|
|
||||||
# If true, multiple from headers are allowed (but only first is used)
|
|
||||||
allow_hdrfrom_multiple = true;
|
|
||||||
# If true, username does not need to contain matching domain
|
|
||||||
allow_username_mismatch = true;
|
|
||||||
# If false, messages from authenticated users are not selected for signing
|
|
||||||
sign_authenticated = true;
|
|
||||||
# Default path to key, can include '$domain' and '$selector' variables
|
|
||||||
path = "/data/dkim/keys/$domain.dkim";
|
|
||||||
# Default selector to use
|
|
||||||
selector = "dkim";
|
|
||||||
# If false, messages from local networks are not selected for signing
|
|
||||||
sign_local = true;
|
|
||||||
# Symbol to add when message is signed
|
|
||||||
symbol = "DKIM_SIGNED";
|
|
||||||
# Whether to fallback to global config
|
|
||||||
try_fallback = true;
|
|
||||||
# Domain to use for DKIM signing: can be "header" or "envelope"
|
|
||||||
use_domain = "envelope";
|
|
||||||
# Whether to normalise domains to eSLD
|
|
||||||
use_esld = false;
|
|
||||||
# Whether to get keys from Redis
|
|
||||||
use_redis = true;
|
|
||||||
# Hash for DKIM keys in Redis
|
|
||||||
key_prefix = "DKIM_PRIV_KEYS";
|
|
||||||
# Selector map
|
|
||||||
selector_prefix = "DKIM_SELECTORS";
|
|
||||||
# Sieve is in sign_networks only
|
|
||||||
# forwards are arc signed, rejects are dkim signed
|
|
||||||
sign_networks = "/etc/rspamd/custom/dovecot_trusted.map";
|
|
||||||
use_domain_sign_networks = "header";
|
|
||||||
sign_headers = "from:sender:reply-to:subject:date:message-id:to:cc:mime-version:content-type:content-transfer-encoding:content-language:resent-to:resent-cc:resent-from:resent-sender:resent-message-id:in-reply-to:references:list-id:list-help:list-owner:list-unsubscribe:list-subscribe:list-post:list-unsubscribe-post:disposition-notification-to:disposition-notification-options:original-recipient:openpgp:autocrypt";
|
|
@ -1,12 +0,0 @@
|
|||||||
oletools {
|
|
||||||
# default olefy settings
|
|
||||||
servers = "olefy:10055";
|
|
||||||
# needs to be set explicitly for Rspamd < 1.9.5
|
|
||||||
scan_mime_parts = true;
|
|
||||||
# mime-part regex matching in content-type or filename
|
|
||||||
# block all macros
|
|
||||||
extended = true;
|
|
||||||
max_size = 3145728;
|
|
||||||
timeout = 20.0;
|
|
||||||
retransmits = 1;
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
rules {
|
|
||||||
WHITELIST_FORWARDING_HOST_NO_REJECT {
|
|
||||||
action = "add header";
|
|
||||||
expression = "WHITELISTED_FWD_HOST";
|
|
||||||
require_action = ["reject"];
|
|
||||||
}
|
|
||||||
WHITELIST_FORWARDING_HOST_NO_GREYLIST {
|
|
||||||
action = "no action";
|
|
||||||
expression = "WHITELISTED_FWD_HOST";
|
|
||||||
require_action = ["greylist", "soft reject"];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
rule "local" {
|
|
||||||
# Fuzzy storage server list
|
|
||||||
servers = "localhost:11445";
|
|
||||||
# Default symbol for unknown flags
|
|
||||||
symbol = "LOCAL_FUZZY_UNKNOWN";
|
|
||||||
# Additional mime types to store/check
|
|
||||||
mime_types = ["application/*"];
|
|
||||||
# Hash weight threshold for all maps
|
|
||||||
max_score = 100.0;
|
|
||||||
# Whether we can learn this storage
|
|
||||||
read_only = no;
|
|
||||||
# Ignore unknown flags
|
|
||||||
skip_unknown = yes;
|
|
||||||
# Hash generation algorithm
|
|
||||||
algorithm = "mumhash";
|
|
||||||
|
|
||||||
# Map flags to symbols
|
|
||||||
fuzzy_map = {
|
|
||||||
LOCAL_FUZZY_DENIED {
|
|
||||||
max_score = 10.0;
|
|
||||||
flag = 11;
|
|
||||||
}
|
|
||||||
LOCAL_FUZZY_WHITE {
|
|
||||||
max_score = 5.0;
|
|
||||||
flag = 13;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rule "mailcow" {
|
|
||||||
# Fuzzy storage server list
|
|
||||||
servers = "fuzzy.mailcow.email:11445";
|
|
||||||
# Default symbol for unknown flags
|
|
||||||
symbol = "MAILCOW_FUZZY_UNKNOWN";
|
|
||||||
# Additional mime types to store/check
|
|
||||||
mime_types = ["application/*"];
|
|
||||||
# Hash weight threshold for all maps
|
|
||||||
max_score = 100.0;
|
|
||||||
# Whether we can learn this storage
|
|
||||||
read_only = yes;
|
|
||||||
# Ignore unknown flags
|
|
||||||
skip_unknown = yes;
|
|
||||||
# Hash generation algorithm
|
|
||||||
algorithm = "mumhash";
|
|
||||||
# Encrypt connection
|
|
||||||
encryption_key = "oa7xjgdr9u7w3hq1xbttas6brgau8qc17yi7ur5huaeq6paq8h4y";
|
|
||||||
# Map flags to symbols
|
|
||||||
fuzzy_map = {
|
|
||||||
MAILCOW_FUZZY_DENIED {
|
|
||||||
max_score = 10.0;
|
|
||||||
flag = 11;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
symbols = {
|
|
||||||
"LOCAL_FUZZY_UNKNOWN" {
|
|
||||||
weight = 0.1;
|
|
||||||
}
|
|
||||||
"LOCAL_FUZZY_DENIED" {
|
|
||||||
weight = 15.0;
|
|
||||||
}
|
|
||||||
"MAILCOW_FUZZY_UNKNOWN" {
|
|
||||||
weight = 0.1;
|
|
||||||
}
|
|
||||||
"MAILCOW_FUZZY_DENIED" {
|
|
||||||
weight = 7.0;
|
|
||||||
}
|
|
||||||
"LOCAL_FUZZY_WHITE" {
|
|
||||||
weight = -10.0;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
whitelisted_ip = "http://nginx:8081/forwardinghosts.php";
|
|
||||||
ipv4_mask = 24;
|
|
||||||
ipv6_mask = 64;
|
|
||||||
message = "Greylisted, please try again later";
|
|
@ -1,59 +0,0 @@
|
|||||||
symbols {
|
|
||||||
"MAILCOW_AUTH" {
|
|
||||||
description = "mailcow authenticated";
|
|
||||||
score = -20.0;
|
|
||||||
}
|
|
||||||
"CTYPE_MIXED_BOGUS" {
|
|
||||||
score = 0.0;
|
|
||||||
}
|
|
||||||
"BAD_REP_POLICIES" {
|
|
||||||
score = 2.0;
|
|
||||||
}
|
|
||||||
"BAD_HEADER" {
|
|
||||||
score = 10.0;
|
|
||||||
}
|
|
||||||
"BULK_HEADER" {
|
|
||||||
score = 4.0;
|
|
||||||
}
|
|
||||||
"ENCRYPTED_CHAT" {
|
|
||||||
score = -20.0;
|
|
||||||
}
|
|
||||||
"SOGO_CONTACT" {
|
|
||||||
score = -99.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
group "MX" {
|
|
||||||
"MX_INVALID" {
|
|
||||||
score = 0.5;
|
|
||||||
description = "No connectable MX";
|
|
||||||
one_shot = true;
|
|
||||||
}
|
|
||||||
"MX_MISSING" {
|
|
||||||
score = 2.0;
|
|
||||||
description = "No MX record";
|
|
||||||
one_shot = true;
|
|
||||||
}
|
|
||||||
"MX_GOOD" {
|
|
||||||
score = -0.01;
|
|
||||||
description = "MX was ok";
|
|
||||||
one_shot = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
group "reputation" {
|
|
||||||
symbols = {
|
|
||||||
"IP_REPUTATION_HAM" {
|
|
||||||
weight = 1.0;
|
|
||||||
}
|
|
||||||
"IP_REPUTATION_SPAM" {
|
|
||||||
weight = 4.0;
|
|
||||||
}
|
|
||||||
"SENDER_REP_HAM" {
|
|
||||||
weight = 1.0;
|
|
||||||
}
|
|
||||||
"SENDER_REP_SPAM" {
|
|
||||||
weight = 2.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
symbols = {
|
|
||||||
"R_MIXED_CHARSET" {
|
|
||||||
weight = 1.0;
|
|
||||||
description = "Mixed characters in a message";
|
|
||||||
one_shot = true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
symbols = {
|
|
||||||
"HFILTER_HOSTNAME_UNKNOWN" {
|
|
||||||
score = 8.5;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
nrows = 1000;
|
|
@ -1,72 +0,0 @@
|
|||||||
rules {
|
|
||||||
QUARANTINE {
|
|
||||||
backend = "http";
|
|
||||||
url = "http://nginx:9081/pipe.php";
|
|
||||||
selector = "reject_no_global_bl";
|
|
||||||
formatter = "default";
|
|
||||||
meta_headers = true;
|
|
||||||
}
|
|
||||||
RLINFO {
|
|
||||||
backend = "http";
|
|
||||||
url = "http://nginx:9081/pipe_rl.php";
|
|
||||||
selector = "ratelimited";
|
|
||||||
formatter = "json";
|
|
||||||
}
|
|
||||||
PUSHOVERMAIL {
|
|
||||||
backend = "http";
|
|
||||||
url = "http://nginx:9081/pushover.php";
|
|
||||||
selector = "mailcow_rcpt";
|
|
||||||
formatter = "json";
|
|
||||||
meta_headers = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
custom_select {
|
|
||||||
mailcow_rcpt = <<EOD
|
|
||||||
return function(task)
|
|
||||||
local action = task:get_metric_action('default')
|
|
||||||
if task:has_symbol('NO_LOG_STAT') or (action == 'soft reject' or action == 'reject' or action == 'add header' or action == 'rewrite subject') then
|
|
||||||
return false
|
|
||||||
else
|
|
||||||
if task:get_symbol("RCPT_MAILCOW_DOMAIN") then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
EOD;
|
|
||||||
ratelimited = <<EOD
|
|
||||||
return function(task)
|
|
||||||
local ratelimited = task:get_symbol("RATELIMITED")
|
|
||||||
if ratelimited then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
EOD;
|
|
||||||
reject_no_global_bl = <<EOD
|
|
||||||
return function(task)
|
|
||||||
if not task:has_symbol('GLOBAL_SMTP_FROM_BL')
|
|
||||||
and not task:has_symbol('GLOBAL_MIME_FROM_BL')
|
|
||||||
and not task:has_symbol('LOCAL_BL_ASN')
|
|
||||||
and not task:has_symbol('GLOBAL_RCPT_BL')
|
|
||||||
and not task:has_symbol('BAD_SUBJECT_00')
|
|
||||||
and not task:has_symbol('MAILCOW_BLACK') then
|
|
||||||
local action = task:get_metric_action('default')
|
|
||||||
if action == 'reject' or action == 'add header' or action == 'rewrite subject' then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
EOD;
|
|
||||||
}
|
|
||||||
|
|
||||||
custom_format {
|
|
||||||
msgid = <<EOD
|
|
||||||
return function(task)
|
|
||||||
return task:get_message_id()
|
|
||||||
end
|
|
||||||
EOD;
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
|||||||
use = ["spam-header", "x-spamd-result", "x-rspamd-queue-id", "authentication-results", "fuzzy-hashes"];
|
|
||||||
skip_local = false;
|
|
||||||
skip_authenticated = true;
|
|
||||||
routines {
|
|
||||||
spam-header {
|
|
||||||
header = "X-Spam-Flag";
|
|
||||||
value = "YES";
|
|
||||||
remove = 1;
|
|
||||||
}
|
|
||||||
fuzzy-hashes {
|
|
||||||
header = "X-Rspamd-Fuzzy";
|
|
||||||
}
|
|
||||||
authentication-results {
|
|
||||||
header = "Authentication-Results";
|
|
||||||
add_smtp_user = false;
|
|
||||||
remove = 1;
|
|
||||||
spf_symbols {
|
|
||||||
pass = "R_SPF_ALLOW";
|
|
||||||
fail = "R_SPF_FAIL";
|
|
||||||
softfail = "R_SPF_SOFTFAIL";
|
|
||||||
neutral = "R_SPF_NEUTRAL";
|
|
||||||
temperror = "R_SPF_DNSFAIL";
|
|
||||||
none = "R_SPF_NA";
|
|
||||||
permerror = "R_SPF_PERMFAIL";
|
|
||||||
}
|
|
||||||
dkim_symbols {
|
|
||||||
pass = "R_DKIM_ALLOW";
|
|
||||||
fail = "R_DKIM_REJECT";
|
|
||||||
temperror = "R_DKIM_TEMPFAIL";
|
|
||||||
none = "R_DKIM_NA";
|
|
||||||
permerror = "R_DKIM_PERMFAIL";
|
|
||||||
}
|
|
||||||
dmarc_symbols {
|
|
||||||
pass = "DMARC_POLICY_ALLOW";
|
|
||||||
permerror = "DMARC_BAD_POLICY";
|
|
||||||
temperror = "DMARC_DNSFAIL";
|
|
||||||
none = "DMARC_NA";
|
|
||||||
reject = "DMARC_POLICY_REJECT";
|
|
||||||
softfail = "DMARC_POLICY_SOFTFAIL";
|
|
||||||
quarantine = "DMARC_POLICY_QUARANTINE";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
# Extensions that are treated as 'bad'
|
|
||||||
# Number is score multiply factor
|
|
||||||
bad_extensions = {
|
|
||||||
scr = 20,
|
|
||||||
lnk = 20,
|
|
||||||
exe = 20,
|
|
||||||
msi = 1,
|
|
||||||
msp = 1,
|
|
||||||
msu = 1,
|
|
||||||
jar = 2,
|
|
||||||
com = 20,
|
|
||||||
bat = 4,
|
|
||||||
cmd = 4,
|
|
||||||
ps1 = 4,
|
|
||||||
ace = 4,
|
|
||||||
arj = 4,
|
|
||||||
cab = 20,
|
|
||||||
vbs = 20,
|
|
||||||
hta = 4,
|
|
||||||
shs = 4,
|
|
||||||
wsc = 4,
|
|
||||||
wsf = 4,
|
|
||||||
iso = 8,
|
|
||||||
img = 8
|
|
||||||
};
|
|
||||||
|
|
||||||
# Extensions that are particularly penalized for archives
|
|
||||||
bad_archive_extensions = {
|
|
||||||
pptx = 0.5,
|
|
||||||
docx = 0.5,
|
|
||||||
xlsx = 0.5,
|
|
||||||
pdf = 1.0,
|
|
||||||
jar = 3,
|
|
||||||
js = 0.5,
|
|
||||||
vbs = 20,
|
|
||||||
exe = 20
|
|
||||||
};
|
|
||||||
|
|
||||||
# Used to detect another archive in archive
|
|
||||||
archive_extensions = {
|
|
||||||
zip = 1,
|
|
||||||
arj = 1,
|
|
||||||
rar = 1,
|
|
||||||
ace = 1,
|
|
||||||
7z = 1,
|
|
||||||
cab = 1
|
|
||||||
};
|
|
@ -1,7 +0,0 @@
|
|||||||
symbols = {
|
|
||||||
"MIME_DOUBLE_BAD_EXTENSION" {
|
|
||||||
weight = 0; # This rule has dynamic weight up to 4.0
|
|
||||||
description = "Bad extension cloaking";
|
|
||||||
one_shot = true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,181 +0,0 @@
|
|||||||
RCPT_MAILCOW_DOMAIN {
|
|
||||||
type = "rcpt";
|
|
||||||
filter = "email:domain";
|
|
||||||
map = "redis://DOMAIN_MAP";
|
|
||||||
symbols_set = ["RCPT_MAILCOW_DOMAIN"];
|
|
||||||
}
|
|
||||||
|
|
||||||
WHITELISTED_FWD_HOST {
|
|
||||||
type = "ip";
|
|
||||||
map = "redis://WHITELISTED_FWD_HOST";
|
|
||||||
symbols_set = ["WHITELISTED_FWD_HOST"];
|
|
||||||
}
|
|
||||||
|
|
||||||
BULK_HEADER {
|
|
||||||
type = "content";
|
|
||||||
map = "${LOCAL_CONFDIR}/custom/bulk_header.map";
|
|
||||||
filter = "headers"
|
|
||||||
regexp = true;
|
|
||||||
symbols_set = ["BULK_HEADER"];
|
|
||||||
}
|
|
||||||
|
|
||||||
CHAT_VERSION_HEADER {
|
|
||||||
type = "header";
|
|
||||||
header = "Chat-Version";
|
|
||||||
map = "${LOCAL_CONFDIR}/custom/chat_versions.map";
|
|
||||||
regexp = true;
|
|
||||||
symbols_set = ["CHAT_VERSION_HEADER"];
|
|
||||||
}
|
|
||||||
|
|
||||||
BAD_HEADER {
|
|
||||||
type = "content";
|
|
||||||
map = "${LOCAL_CONFDIR}/custom/bad_header.map";
|
|
||||||
filter = "headers"
|
|
||||||
regexp = true;
|
|
||||||
symbols_set = ["BAD_HEADER"];
|
|
||||||
}
|
|
||||||
|
|
||||||
LOCAL_BL_ASN {
|
|
||||||
require_symbols = "!MAILCOW_WHITE";
|
|
||||||
type = "asn";
|
|
||||||
map = "${LOCAL_CONFDIR}/custom/bad_asn.map";
|
|
||||||
score = 5;
|
|
||||||
description = "Sender's ASN is on the local blacklist";
|
|
||||||
symbols_set = ["LOCAL_BL_ASN"];
|
|
||||||
}
|
|
||||||
|
|
||||||
GLOBAL_SMTP_FROM_WL {
|
|
||||||
type = "from";
|
|
||||||
map = "${LOCAL_CONFDIR}/custom/global_smtp_from_whitelist.map";
|
|
||||||
regexp = true;
|
|
||||||
score = -2050;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLOBAL_SMTP_FROM_BL {
|
|
||||||
type = "from";
|
|
||||||
map = "${LOCAL_CONFDIR}/custom/global_smtp_from_blacklist.map";
|
|
||||||
regexp = true;
|
|
||||||
score = 2050;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLOBAL_MIME_FROM_WL {
|
|
||||||
type = "header";
|
|
||||||
header = "from";
|
|
||||||
filter = "email:addr";
|
|
||||||
map = "${LOCAL_CONFDIR}/custom/global_mime_from_whitelist.map";
|
|
||||||
regexp = true;
|
|
||||||
score = -2050;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLOBAL_MIME_FROM_BL {
|
|
||||||
type = "header";
|
|
||||||
header = "from";
|
|
||||||
filter = "email:addr";
|
|
||||||
map = "${LOCAL_CONFDIR}/custom/global_mime_from_blacklist.map";
|
|
||||||
regexp = true;
|
|
||||||
score = 2050;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLOBAL_RCPT_WL {
|
|
||||||
type = "rcpt";
|
|
||||||
map = "${LOCAL_CONFDIR}/custom/global_rcpt_whitelist.map";
|
|
||||||
regexp = true;
|
|
||||||
prefilter = true;
|
|
||||||
action = "accept";
|
|
||||||
}
|
|
||||||
|
|
||||||
GLOBAL_RCPT_BL {
|
|
||||||
type = "rcpt";
|
|
||||||
map = "${LOCAL_CONFDIR}/custom/global_rcpt_blacklist.map";
|
|
||||||
regexp = true;
|
|
||||||
prefilter = true;
|
|
||||||
action = "reject";
|
|
||||||
}
|
|
||||||
|
|
||||||
SIEVE_HOST {
|
|
||||||
type = "ip";
|
|
||||||
map = "${LOCAL_CONFDIR}/custom/dovecot_trusted.map";
|
|
||||||
symbols_set = ["SIEVE_HOST"];
|
|
||||||
}
|
|
||||||
|
|
||||||
RSPAMD_HOST {
|
|
||||||
type = "ip";
|
|
||||||
map = "${LOCAL_CONFDIR}/custom/rspamd_trusted.map";
|
|
||||||
symbols_set = ["RSPAMD_HOST"];
|
|
||||||
}
|
|
||||||
|
|
||||||
MAILCOW_DOMAIN_HEADER_FROM {
|
|
||||||
type = "header";
|
|
||||||
header = "from";
|
|
||||||
filter = "email:domain";
|
|
||||||
map = "redis://DOMAIN_MAP";
|
|
||||||
}
|
|
||||||
|
|
||||||
IP_WHITELIST {
|
|
||||||
type = "ip";
|
|
||||||
map = "${LOCAL_CONFDIR}/custom/ip_wl.map";
|
|
||||||
symbols_set = ["IP_WHITELIST"];
|
|
||||||
score = -2050;
|
|
||||||
}
|
|
||||||
|
|
||||||
FISHY_TLD {
|
|
||||||
type = "from";
|
|
||||||
filter = "email:domain";
|
|
||||||
map = "${LOCAL_CONFDIR}/custom/fishy_tlds.map";
|
|
||||||
regexp = true;
|
|
||||||
score = 0.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
BAD_WORDS {
|
|
||||||
type = "content";
|
|
||||||
filter = "text";
|
|
||||||
map = "${LOCAL_CONFDIR}/custom/bad_words.map";
|
|
||||||
regexp = true;
|
|
||||||
score = 0.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
BAD_WORDS_DE {
|
|
||||||
type = "content";
|
|
||||||
filter = "text";
|
|
||||||
map = "${LOCAL_CONFDIR}/custom/bad_words_de.map";
|
|
||||||
regexp = true;
|
|
||||||
score = 0.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
BAD_LANG {
|
|
||||||
type = 'selector';
|
|
||||||
selector = 'languages';
|
|
||||||
map = "${LOCAL_CONFDIR}/custom/bad_languages.map";
|
|
||||||
symbols_set = ["LANG_FILTER"];
|
|
||||||
regexp = true;
|
|
||||||
score = 5.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
BAZAAR_ABUSE_CH {
|
|
||||||
type = "selector";
|
|
||||||
selector = "attachments(hex,md5)";
|
|
||||||
map = "https://bazaar.abuse.ch/export/txt/md5/recent/";
|
|
||||||
score = 10.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
URLHAUS_ABUSE_CH {
|
|
||||||
type = "selector";
|
|
||||||
selector = "urls";
|
|
||||||
map = "https://urlhaus.abuse.ch/downloads/text_online/";
|
|
||||||
score = 10.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
SMTP_LIMITED_ACCESS {
|
|
||||||
type = "user";
|
|
||||||
map = "redis://SMTP_LIMITED_ACCESS";
|
|
||||||
symbols_set = ["SMTP_LIMITED_ACCESS"];
|
|
||||||
}
|
|
||||||
|
|
||||||
BAD_SUBJECT_00 {
|
|
||||||
type = "header";
|
|
||||||
header = "subject";
|
|
||||||
regexp = true;
|
|
||||||
map = "http://fuzzy.mailcow.email/bad-subject-regex.txt";
|
|
||||||
score = 6.0;
|
|
||||||
symbols_set = ["BAD_SUBJECT_00"];
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
timeout = 8.0;
|
|
||||||
symbol_bad_mx = "MX_INVALID";
|
|
||||||
symbol_no_mx = "MX_MISSING";
|
|
||||||
symbol_good_mx = "MX_GOOD";
|
|
||||||
expire = 86400;
|
|
||||||
key_prefix = "rmx";
|
|
||||||
enabled = true;
|
|
@ -1,9 +0,0 @@
|
|||||||
dns {
|
|
||||||
enable_dnssec = true;
|
|
||||||
}
|
|
||||||
map_watch_interval = 30s;
|
|
||||||
disable_monitoring = true;
|
|
||||||
# In case a task times out (like DNS lookup), soft reject the message
|
|
||||||
# instead of silently accepting the message without further processing.
|
|
||||||
soft_reject_on_timeout = true;
|
|
||||||
local_addrs = /etc/rspamd/custom/mailcow_networks.map;
|
|
@ -1 +0,0 @@
|
|||||||
phishtank_enabled = false;
|
|
@ -1,26 +0,0 @@
|
|||||||
symbols = {
|
|
||||||
"ARC_REJECT" {
|
|
||||||
score = 0.1;
|
|
||||||
}
|
|
||||||
"R_SPF_FAIL" {
|
|
||||||
score = 8.0;
|
|
||||||
}
|
|
||||||
"R_SPF_PERMFAIL" {
|
|
||||||
score = 8.0;
|
|
||||||
}
|
|
||||||
"R_SPF_SOFTFAIL" {
|
|
||||||
score = 0.1;
|
|
||||||
}
|
|
||||||
"R_DKIM_REJECT" {
|
|
||||||
score = 8.0;
|
|
||||||
}
|
|
||||||
"DMARC_POLICY_REJECT" {
|
|
||||||
weight = 16.0;
|
|
||||||
}
|
|
||||||
"DMARC_POLICY_QUARANTINE" {
|
|
||||||
weight = 8.0;
|
|
||||||
}
|
|
||||||
"DMARC_POLICY_SOFTFAIL" {
|
|
||||||
weight = 0.1;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
# Uncomment below to apply the ratelimits globally. Use Ratelimits inside mailcow UI to overwrite them for a specific domain/mailbox.
|
|
||||||
# rates {
|
|
||||||
# # Format: "1 / 1h" or "20 / 1m" etc.
|
|
||||||
# to = "100 / 1s";
|
|
||||||
# to_ip = "100 / 1s";
|
|
||||||
# to_ip_from = "100 / 1s";
|
|
||||||
# bounce_to = "100 / 1h";
|
|
||||||
# bounce_to_ip = "7 / 1m";
|
|
||||||
# }
|
|
@ -1,26 +0,0 @@
|
|||||||
rbls {
|
|
||||||
interserver_ip {
|
|
||||||
symbol = "RBL_INTERSERVER_IP";
|
|
||||||
rbl = "rbl.interserver.net";
|
|
||||||
from = true;
|
|
||||||
ipv6 = false;
|
|
||||||
returncodes {
|
|
||||||
RBL_INTERSERVER_BAD_IP = "127.0.0.2";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
interserver_uri {
|
|
||||||
symbol = "RBL_INTERSERVER_URI";
|
|
||||||
rbl = "rbluri.interserver.net";
|
|
||||||
ignore_defaults = true;
|
|
||||||
no_ip = true;
|
|
||||||
dkim = true;
|
|
||||||
emails = true;
|
|
||||||
urls = true;
|
|
||||||
returncodes = {
|
|
||||||
RBL_INTERSERVER_BAD_URI = "127.0.0.2";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.include(try=true,override=true,priority=5) "$LOCAL_CONFDIR/custom/dqs-rbl.conf"
|
|
||||||
|
|
||||||
}
|
|
@ -1,277 +0,0 @@
|
|||||||
symbols = {
|
|
||||||
"RBL_UCEPROTECT_LEVEL1" {
|
|
||||||
score = 3.5;
|
|
||||||
}
|
|
||||||
"RBL_UCEPROTECT_LEVEL2" {
|
|
||||||
score = 1.5;
|
|
||||||
}
|
|
||||||
"RECEIVED_SPAMHAUS_XBL" {
|
|
||||||
weight = 0.0;
|
|
||||||
description = "Received address is listed in ZEN XBL";
|
|
||||||
}
|
|
||||||
"RBL_INTERSERVER_BAD_URI" {
|
|
||||||
score = 4.0;
|
|
||||||
description = "Listed on Interserver RBL";
|
|
||||||
}
|
|
||||||
"RBL_INTERSERVER_BAD_IP" {
|
|
||||||
score = 4.0;
|
|
||||||
description = "Listed on Interserver RBL";
|
|
||||||
}
|
|
||||||
|
|
||||||
"SPAMHAUS_ZEN" {
|
|
||||||
weight = 7.0;
|
|
||||||
}
|
|
||||||
"SH_AUTHBL_RECEIVED" {
|
|
||||||
weight = 4.0;
|
|
||||||
}
|
|
||||||
"RBL_DBL_SPAM" {
|
|
||||||
weight = 7.0;
|
|
||||||
}
|
|
||||||
"RBL_DBL_PHISH" {
|
|
||||||
weight = 7.0;
|
|
||||||
}
|
|
||||||
"RBL_DBL_MALWARE" {
|
|
||||||
weight = 7.0;
|
|
||||||
}
|
|
||||||
"RBL_DBL_BOTNET" {
|
|
||||||
weight = 7.0;
|
|
||||||
}
|
|
||||||
"RBL_DBL_ABUSED_SPAM" {
|
|
||||||
weight = 3.0;
|
|
||||||
}
|
|
||||||
"RBL_DBL_ABUSED_PHISH" {
|
|
||||||
weight = 3.0;
|
|
||||||
}
|
|
||||||
"RBL_DBL_ABUSED_MALWARE" {
|
|
||||||
weight = 3.0;
|
|
||||||
}
|
|
||||||
"RBL_DBL_ABUSED_BOTNET" {
|
|
||||||
weight = 3.0;
|
|
||||||
}
|
|
||||||
"RBL_ZRD_VERY_FRESH_DOMAIN" {
|
|
||||||
weight = 7.0;
|
|
||||||
}
|
|
||||||
"RBL_ZRD_FRESH_DOMAIN" {
|
|
||||||
weight = 4.0;
|
|
||||||
}
|
|
||||||
"ZRD_VERY_FRESH_DOMAIN" {
|
|
||||||
weight = 7.0;
|
|
||||||
}
|
|
||||||
"ZRD_FRESH_DOMAIN" {
|
|
||||||
weight = 4.0;
|
|
||||||
}
|
|
||||||
"SH_EMAIL_DBL" {
|
|
||||||
weight = 7.0;
|
|
||||||
}
|
|
||||||
"SH_EMAIL_DBL_ABUSED" {
|
|
||||||
weight = 7.0;
|
|
||||||
}
|
|
||||||
"SH_EMAIL_ZRD_VERY_FRESH_DOMAIN" {
|
|
||||||
weight = 7.0;
|
|
||||||
}
|
|
||||||
"SH_EMAIL_ZRD_FRESH_DOMAIN" {
|
|
||||||
weight = 4.0;
|
|
||||||
}
|
|
||||||
"RBL_DBL_DONT_QUERY_IPS" {
|
|
||||||
weight = 0.0;
|
|
||||||
}
|
|
||||||
"RBL_ZRD_DONT_QUERY_IPS" {
|
|
||||||
weight = 0.0;
|
|
||||||
}
|
|
||||||
"SH_EMAIL_ZRD_DONT_QUERY_IPS" {
|
|
||||||
weight = 0.0;
|
|
||||||
}
|
|
||||||
"SH_EMAIL_DBL_DONT_QUERY_IPS" {
|
|
||||||
weight = 0.0;
|
|
||||||
}
|
|
||||||
"DBL" {
|
|
||||||
weight = 0.0;
|
|
||||||
description = "DBL unknown result";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"DBL_SPAM" {
|
|
||||||
weight = 7;
|
|
||||||
description = "DBL uribl spam";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"DBL_PHISH" {
|
|
||||||
weight = 7;
|
|
||||||
description = "DBL uribl phishing";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"DBL_MALWARE" {
|
|
||||||
weight = 7;
|
|
||||||
description = "DBL uribl malware";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"DBL_BOTNET" {
|
|
||||||
weight = 7;
|
|
||||||
description = "DBL uribl botnet C&C domain";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
"DBLABUSED_SPAM_FULLURLS" {
|
|
||||||
weight = 5.5;
|
|
||||||
description = "DBL uribl abused legit spam";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"DBLABUSED_PHISH_FULLURLS" {
|
|
||||||
weight = 5.5;
|
|
||||||
description = "DBL uribl abused legit phish";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"DBLABUSED_MALWARE_FULLURLS" {
|
|
||||||
weight = 5.5;
|
|
||||||
description = "DBL uribl abused legit malware";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"DBLABUSED_BOTNET_FULLURLS" {
|
|
||||||
weight = 5.5;
|
|
||||||
description = "DBL uribl abused legit botnet";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
|
|
||||||
"DBL_ABUSE" {
|
|
||||||
weight = 5.5;
|
|
||||||
description = "DBL uribl abused legit spam";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"DBL_ABUSE_REDIR" {
|
|
||||||
weight = 1.5;
|
|
||||||
description = "DBL uribl abused spammed redirector domain";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"DBL_ABUSE_PHISH" {
|
|
||||||
weight = 5.5;
|
|
||||||
description = "DBL uribl abused legit phish";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"DBL_ABUSE_MALWARE" {
|
|
||||||
weight = 5.5;
|
|
||||||
description = "DBL uribl abused legit malware";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"DBL_ABUSE_BOTNET" {
|
|
||||||
weight = 5.5;
|
|
||||||
description = "DBL uribl abused legit botnet C&C";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"DBL_PROHIBIT" {
|
|
||||||
weight = 0.0;
|
|
||||||
description = "DBL uribl IP queries prohibited!";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"DBL_BLOCKED_OPENRESOLVER" {
|
|
||||||
weight = 0.0;
|
|
||||||
description = "You are querying Spamhaus from an open resolver, please see https://www.spamhaus.org/returnc/pub/";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"DBL_BLOCKED" {
|
|
||||||
weight = 0.0;
|
|
||||||
description = "You are exceeding the query limit, please see https://www.spamhaus.org/returnc/vol/";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"SPAMHAUS_ZEN_URIBL" {
|
|
||||||
weight = 0.0;
|
|
||||||
description = "Spamhaus ZEN URIBL: Filtered result";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"URIBL_SBL" {
|
|
||||||
weight = 6.5;
|
|
||||||
description = "A domain in the message body resolves to an IP listed in Spamhaus SBL";
|
|
||||||
one_shot = true;
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"URIBL_SBL_CSS" {
|
|
||||||
weight = 6.5;
|
|
||||||
description = "A domain in the message body resolves to an IP listed in Spamhaus SBL CSS";
|
|
||||||
one_shot = true;
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"URIBL_PBL" {
|
|
||||||
weight = 0.01;
|
|
||||||
description = "A domain in the message body resolves to an IP listed in Spamhaus PBL";
|
|
||||||
one_shot = true;
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"URIBL_DROP" {
|
|
||||||
weight = 6.5;
|
|
||||||
description = "A domain in the message body resolves to an IP listed in Spamhaus DROP";
|
|
||||||
one_shot = true;
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"URIBL_XBL" {
|
|
||||||
weight = 5.0;
|
|
||||||
description = "A domain in the message body resolves to an IP listed in Spamhaus XBL";
|
|
||||||
one_shot = true;
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
"SPAMHAUS_SBL_URL" {
|
|
||||||
weight = 6.5;
|
|
||||||
description = "A numeric URL in the message body is listed in Spamhaus SBL";
|
|
||||||
one_shot = true;
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
|
|
||||||
"SH_HBL_EMAIL" {
|
|
||||||
weight = 7;
|
|
||||||
description = "Email listed in HBL";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
|
|
||||||
"SH_HBL_FILE_MALICIOUS" {
|
|
||||||
weight = 7;
|
|
||||||
description = "An attachment hash is listed in Spamhaus HBL as malicious";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
|
|
||||||
"SH_HBL_FILE_SUSPICIOUS" {
|
|
||||||
weight = 5;
|
|
||||||
description = "An attachment hash is listed in Spamhaus HBL as suspicious";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
|
|
||||||
"RBL_SPAMHAUS_CW_BTC" {
|
|
||||||
score = 7;
|
|
||||||
description = "Bitcoin found in Spamhaus cryptowallet list";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
|
|
||||||
"RBL_SPAMHAUS_CW_ETH" {
|
|
||||||
score = 7;
|
|
||||||
description = "Ethereum found in Spamhaus cryptowallet list";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
|
|
||||||
"RBL_SPAMHAUS_CW_BCH" {
|
|
||||||
score = 7;
|
|
||||||
description = "Bitcoinhash found in Spamhaus cryptowallet list";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
|
|
||||||
"RBL_SPAMHAUS_CW_XMR" {
|
|
||||||
score = 7;
|
|
||||||
description = "Monero found in Spamhaus cryptowallet list";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
|
|
||||||
"RBL_SPAMHAUS_CW_LTC" {
|
|
||||||
score = 7;
|
|
||||||
description = "Litecoin found in Spamhaus cryptowallet list";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
|
|
||||||
"RBL_SPAMHAUS_CW_XRP" {
|
|
||||||
score = 7;
|
|
||||||
description = "Ripple found in Spamhaus cryptowallet list";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
|
|
||||||
"RBL_SPAMHAUS_HBL_URL" {
|
|
||||||
score = 7;
|
|
||||||
description = "URL found in spamhaus HBL blocklist";
|
|
||||||
groups = ["spamhaus"];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
servers = "redis:6379";
|
|
||||||
timeout = 10;
|
|
@ -1,9 +0,0 @@
|
|||||||
rules {
|
|
||||||
ip_reputation = {
|
|
||||||
selector "ip" {
|
|
||||||
}
|
|
||||||
backend "redis" {
|
|
||||||
}
|
|
||||||
symbol = "IP_REPUTATION";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
ruleset = "/etc/rspamd/custom/sa-rules";
|
|
@ -1,26 +0,0 @@
|
|||||||
classifier "bayes" {
|
|
||||||
# name = "custom"; # 'name' parameter must be set if multiple classifiers are defined
|
|
||||||
learn_condition = 'return require("lua_bayes_learn").can_learn';
|
|
||||||
new_schema = true;
|
|
||||||
tokenizer {
|
|
||||||
name = "osb";
|
|
||||||
}
|
|
||||||
backend = "redis";
|
|
||||||
min_tokens = 11;
|
|
||||||
min_learns = 5;
|
|
||||||
expire = 7776000;
|
|
||||||
statfile {
|
|
||||||
symbol = "BAYES_HAM";
|
|
||||||
spam = false;
|
|
||||||
}
|
|
||||||
statfile {
|
|
||||||
symbol = "BAYES_SPAM";
|
|
||||||
spam = true;
|
|
||||||
}
|
|
||||||
autolearn {
|
|
||||||
spam_threshold = 12.0;
|
|
||||||
ham_threshold = -4.5;
|
|
||||||
check_balance = true;
|
|
||||||
min_balance = 0.9;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
symbols = {
|
|
||||||
"BAYES_SPAM" {
|
|
||||||
weight = 4.5;
|
|
||||||
description = "Message probably spam, probability: ";
|
|
||||||
}
|
|
||||||
"BAYES_HAM" {
|
|
||||||
weight = -5.5;
|
|
||||||
description = "Message probably ham, probability: ";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
local custom_keywords = {}
|
|
||||||
|
|
||||||
custom_keywords.mailcow = function(task)
|
|
||||||
local rspamd_logger = require "rspamd_logger"
|
|
||||||
local dyn_rl_symbol = task:get_symbol("DYN_RL")
|
|
||||||
if dyn_rl_symbol then
|
|
||||||
local rl_value = dyn_rl_symbol[1].options[1]
|
|
||||||
local rl_object = dyn_rl_symbol[1].options[2]
|
|
||||||
if rl_value and rl_object then
|
|
||||||
rspamd_logger.infox(rspamd_config, "DYN_RL symbol has value %s for object %s, returning %s...", rl_value, rl_object, "rs_dynrl_" .. rl_object)
|
|
||||||
return "rs_dynrl_" .. rl_object, rl_value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return custom_keywords
|
|
@ -1,701 +0,0 @@
|
|||||||
rspamd_config.MAILCOW_AUTH = {
|
|
||||||
callback = function(task)
|
|
||||||
local uname = task:get_user()
|
|
||||||
if uname then
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
local monitoring_hosts = rspamd_config:add_map{
|
|
||||||
url = "/etc/rspamd/custom/monitoring_nolog.map",
|
|
||||||
description = "Monitoring hosts",
|
|
||||||
type = "regexp"
|
|
||||||
}
|
|
||||||
|
|
||||||
rspamd_config:register_symbol({
|
|
||||||
name = 'SMTP_ACCESS',
|
|
||||||
type = 'postfilter',
|
|
||||||
callback = function(task)
|
|
||||||
local util = require("rspamd_util")
|
|
||||||
local rspamd_logger = require "rspamd_logger"
|
|
||||||
local rspamd_ip = require 'rspamd_ip'
|
|
||||||
local uname = task:get_user()
|
|
||||||
local limited_access = task:get_symbol("SMTP_LIMITED_ACCESS")
|
|
||||||
|
|
||||||
if not uname then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
if not limited_access then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local hash_key = 'SMTP_ALLOW_NETS_' .. uname
|
|
||||||
|
|
||||||
local redis_params = rspamd_parse_redis_server('smtp_access')
|
|
||||||
local ip = task:get_from_ip()
|
|
||||||
|
|
||||||
if ip == nil or not ip:is_valid() then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local from_ip_string = tostring(ip)
|
|
||||||
smtp_access_table = {from_ip_string}
|
|
||||||
|
|
||||||
local maxbits = 128
|
|
||||||
local minbits = 32
|
|
||||||
if ip:get_version() == 4 then
|
|
||||||
maxbits = 32
|
|
||||||
minbits = 8
|
|
||||||
end
|
|
||||||
for i=maxbits,minbits,-1 do
|
|
||||||
local nip = ip:apply_mask(i):to_string() .. "/" .. i
|
|
||||||
table.insert(smtp_access_table, nip)
|
|
||||||
end
|
|
||||||
local function smtp_access_cb(err, data)
|
|
||||||
if err then
|
|
||||||
rspamd_logger.infox(rspamd_config, "smtp_access query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err)
|
|
||||||
return false
|
|
||||||
else
|
|
||||||
rspamd_logger.infox(rspamd_config, "checking ip %s for smtp_access in %s", from_ip_string, hash_key)
|
|
||||||
for k,v in pairs(data) do
|
|
||||||
if (v and v ~= userdata and v == '1') then
|
|
||||||
rspamd_logger.infox(rspamd_config, "found ip in smtp_access map")
|
|
||||||
task:insert_result(true, 'SMTP_ACCESS', 0.0, from_ip_string)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
rspamd_logger.infox(rspamd_config, "couldnt find ip in smtp_access map")
|
|
||||||
task:insert_result(true, 'SMTP_ACCESS', 999.0, from_ip_string)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
table.insert(smtp_access_table, 1, hash_key)
|
|
||||||
local redis_ret_user = rspamd_redis_make_request(task,
|
|
||||||
redis_params, -- connect params
|
|
||||||
hash_key, -- hash key
|
|
||||||
false, -- is write
|
|
||||||
smtp_access_cb, --callback
|
|
||||||
'HMGET', -- command
|
|
||||||
smtp_access_table -- arguments
|
|
||||||
)
|
|
||||||
if not redis_ret_user then
|
|
||||||
rspamd_logger.infox(rspamd_config, "cannot check smtp_access redis map")
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
priority = 10
|
|
||||||
})
|
|
||||||
|
|
||||||
rspamd_config:register_symbol({
|
|
||||||
name = 'POSTMASTER_HANDLER',
|
|
||||||
type = 'prefilter',
|
|
||||||
callback = function(task)
|
|
||||||
local rcpts = task:get_recipients('smtp')
|
|
||||||
local rspamd_logger = require "rspamd_logger"
|
|
||||||
local lua_util = require "lua_util"
|
|
||||||
local from = task:get_from(1)
|
|
||||||
|
|
||||||
-- not applying to mails with more than one rcpt to avoid bypassing filters by addressing postmaster
|
|
||||||
if rcpts and #rcpts == 1 then
|
|
||||||
for _,rcpt in ipairs(rcpts) do
|
|
||||||
local rcpt_split = rspamd_str_split(rcpt['addr'], '@')
|
|
||||||
if #rcpt_split == 2 then
|
|
||||||
if rcpt_split[1] == 'postmaster' then
|
|
||||||
task:set_pre_result('accept', 'whitelisting postmaster smtp rcpt')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if from then
|
|
||||||
for _,fr in ipairs(from) do
|
|
||||||
local fr_split = rspamd_str_split(fr['addr'], '@')
|
|
||||||
if #fr_split == 2 then
|
|
||||||
if fr_split[1] == 'postmaster' and task:get_user() then
|
|
||||||
-- no whitelist, keep signatures
|
|
||||||
task:insert_result(true, 'POSTMASTER_FROM', -2500.0)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end,
|
|
||||||
priority = 10
|
|
||||||
})
|
|
||||||
|
|
||||||
rspamd_config:register_symbol({
|
|
||||||
name = 'KEEP_SPAM',
|
|
||||||
type = 'prefilter',
|
|
||||||
callback = function(task)
|
|
||||||
local util = require("rspamd_util")
|
|
||||||
local rspamd_logger = require "rspamd_logger"
|
|
||||||
local rspamd_ip = require 'rspamd_ip'
|
|
||||||
local uname = task:get_user()
|
|
||||||
|
|
||||||
if uname then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local redis_params = rspamd_parse_redis_server('keep_spam')
|
|
||||||
local ip = task:get_from_ip()
|
|
||||||
|
|
||||||
if ip == nil or not ip:is_valid() then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local from_ip_string = tostring(ip)
|
|
||||||
ip_check_table = {from_ip_string}
|
|
||||||
|
|
||||||
local maxbits = 128
|
|
||||||
local minbits = 32
|
|
||||||
if ip:get_version() == 4 then
|
|
||||||
maxbits = 32
|
|
||||||
minbits = 8
|
|
||||||
end
|
|
||||||
for i=maxbits,minbits,-1 do
|
|
||||||
local nip = ip:apply_mask(i):to_string() .. "/" .. i
|
|
||||||
table.insert(ip_check_table, nip)
|
|
||||||
end
|
|
||||||
local function keep_spam_cb(err, data)
|
|
||||||
if err then
|
|
||||||
rspamd_logger.infox(rspamd_config, "keep_spam query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err)
|
|
||||||
return false
|
|
||||||
else
|
|
||||||
for k,v in pairs(data) do
|
|
||||||
if (v and v ~= userdata and v == '1') then
|
|
||||||
rspamd_logger.infox(rspamd_config, "found ip in keep_spam map, setting pre-result")
|
|
||||||
task:set_pre_result('accept', 'ip matched with forward hosts')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
table.insert(ip_check_table, 1, 'KEEP_SPAM')
|
|
||||||
local redis_ret_user = rspamd_redis_make_request(task,
|
|
||||||
redis_params, -- connect params
|
|
||||||
'KEEP_SPAM', -- hash key
|
|
||||||
false, -- is write
|
|
||||||
keep_spam_cb, --callback
|
|
||||||
'HMGET', -- command
|
|
||||||
ip_check_table -- arguments
|
|
||||||
)
|
|
||||||
if not redis_ret_user then
|
|
||||||
rspamd_logger.infox(rspamd_config, "cannot check keep_spam redis map")
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
priority = 19
|
|
||||||
})
|
|
||||||
|
|
||||||
rspamd_config:register_symbol({
|
|
||||||
name = 'TLS_HEADER',
|
|
||||||
type = 'postfilter',
|
|
||||||
callback = function(task)
|
|
||||||
local rspamd_logger = require "rspamd_logger"
|
|
||||||
local tls_tag = task:get_request_header('TLS-Version')
|
|
||||||
if type(tls_tag) == 'nil' then
|
|
||||||
task:set_milter_reply({
|
|
||||||
add_headers = {['X-Last-TLS-Session-Version'] = 'None'}
|
|
||||||
})
|
|
||||||
else
|
|
||||||
task:set_milter_reply({
|
|
||||||
add_headers = {['X-Last-TLS-Session-Version'] = tostring(tls_tag)}
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
priority = 12
|
|
||||||
})
|
|
||||||
|
|
||||||
rspamd_config:register_symbol({
|
|
||||||
name = 'TAG_MOO',
|
|
||||||
type = 'postfilter',
|
|
||||||
callback = function(task)
|
|
||||||
local util = require("rspamd_util")
|
|
||||||
local rspamd_logger = require "rspamd_logger"
|
|
||||||
local redis_params = rspamd_parse_redis_server('taghandler')
|
|
||||||
local rspamd_http = require "rspamd_http"
|
|
||||||
local rcpts = task:get_recipients('smtp')
|
|
||||||
local lua_util = require "lua_util"
|
|
||||||
|
|
||||||
local tagged_rcpt = task:get_symbol("TAGGED_RCPT")
|
|
||||||
local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN")
|
|
||||||
|
|
||||||
local function remove_moo_tag()
|
|
||||||
local moo_tag_header = task:get_header('X-Moo-Tag', false)
|
|
||||||
if moo_tag_header then
|
|
||||||
task:set_milter_reply({
|
|
||||||
remove_headers = {['X-Moo-Tag'] = 0},
|
|
||||||
})
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
if tagged_rcpt and tagged_rcpt[1].options and mailcow_domain then
|
|
||||||
local tag = tagged_rcpt[1].options[1]
|
|
||||||
rspamd_logger.infox("found tag: %s", tag)
|
|
||||||
local action = task:get_metric_action('default')
|
|
||||||
rspamd_logger.infox("metric action now: %s", action)
|
|
||||||
|
|
||||||
if action ~= 'no action' and action ~= 'greylist' then
|
|
||||||
rspamd_logger.infox("skipping tag handler for action: %s", action)
|
|
||||||
remove_moo_tag()
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function http_callback(err_message, code, body, headers)
|
|
||||||
if body ~= nil and body ~= "" then
|
|
||||||
rspamd_logger.infox(rspamd_config, "expanding rcpt to \"%s\"", body)
|
|
||||||
|
|
||||||
local function tag_callback_subject(err, data)
|
|
||||||
if err or type(data) ~= 'string' then
|
|
||||||
rspamd_logger.infox(rspamd_config, "subject tag handler rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying subfolder tag handler...", body, data, err)
|
|
||||||
|
|
||||||
local function tag_callback_subfolder(err, data)
|
|
||||||
if err or type(data) ~= 'string' then
|
|
||||||
rspamd_logger.infox(rspamd_config, "subfolder tag handler for rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\")", body, data, err)
|
|
||||||
remove_moo_tag()
|
|
||||||
else
|
|
||||||
rspamd_logger.infox("Add X-Moo-Tag header")
|
|
||||||
task:set_milter_reply({
|
|
||||||
add_headers = {['X-Moo-Tag'] = 'YES'}
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local redis_ret_subfolder = rspamd_redis_make_request(task,
|
|
||||||
redis_params, -- connect params
|
|
||||||
body, -- hash key
|
|
||||||
false, -- is write
|
|
||||||
tag_callback_subfolder, --callback
|
|
||||||
'HGET', -- command
|
|
||||||
{'RCPT_WANTS_SUBFOLDER_TAG', body} -- arguments
|
|
||||||
)
|
|
||||||
if not redis_ret_subfolder then
|
|
||||||
rspamd_logger.infox(rspamd_config, "cannot make request to load tag handler for rcpt")
|
|
||||||
remove_moo_tag()
|
|
||||||
end
|
|
||||||
|
|
||||||
else
|
|
||||||
rspamd_logger.infox("user wants subject modified for tagged mail")
|
|
||||||
local sbj = task:get_header('Subject')
|
|
||||||
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
|
|
||||||
task:set_milter_reply({
|
|
||||||
remove_headers = {
|
|
||||||
['Subject'] = 1,
|
|
||||||
['X-Moo-Tag'] = 0
|
|
||||||
},
|
|
||||||
add_headers = {['Subject'] = new_sbj}
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local redis_ret_subject = rspamd_redis_make_request(task,
|
|
||||||
redis_params, -- connect params
|
|
||||||
body, -- hash key
|
|
||||||
false, -- is write
|
|
||||||
tag_callback_subject, --callback
|
|
||||||
'HGET', -- command
|
|
||||||
{'RCPT_WANTS_SUBJECT_TAG', body} -- arguments
|
|
||||||
)
|
|
||||||
if not redis_ret_subject then
|
|
||||||
rspamd_logger.infox(rspamd_config, "cannot make request to load tag handler for rcpt")
|
|
||||||
remove_moo_tag()
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if rcpts and #rcpts == 1 then
|
|
||||||
for _,rcpt in ipairs(rcpts) do
|
|
||||||
local rcpt_split = rspamd_str_split(rcpt['addr'], '@')
|
|
||||||
if #rcpt_split == 2 then
|
|
||||||
if rcpt_split[1] == 'postmaster' then
|
|
||||||
rspamd_logger.infox(rspamd_config, "not expanding postmaster alias")
|
|
||||||
remove_moo_tag()
|
|
||||||
else
|
|
||||||
rspamd_http.request({
|
|
||||||
task=task,
|
|
||||||
url='http://nginx:8081/aliasexp.php',
|
|
||||||
body='',
|
|
||||||
callback=http_callback,
|
|
||||||
headers={Rcpt=rcpt['addr']},
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
remove_moo_tag()
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
priority = 19
|
|
||||||
})
|
|
||||||
|
|
||||||
rspamd_config:register_symbol({
|
|
||||||
name = 'BCC',
|
|
||||||
type = 'postfilter',
|
|
||||||
callback = function(task)
|
|
||||||
local util = require("rspamd_util")
|
|
||||||
local rspamd_http = require "rspamd_http"
|
|
||||||
local rspamd_logger = require "rspamd_logger"
|
|
||||||
|
|
||||||
local from_table = {}
|
|
||||||
local rcpt_table = {}
|
|
||||||
|
|
||||||
if task:has_symbol('ENCRYPTED_CHAT') then
|
|
||||||
return -- stop
|
|
||||||
end
|
|
||||||
|
|
||||||
local send_mail = function(task, bcc_dest)
|
|
||||||
local lua_smtp = require "lua_smtp"
|
|
||||||
local function sendmail_cb(ret, err)
|
|
||||||
if not ret then
|
|
||||||
rspamd_logger.errx(task, 'BCC SMTP ERROR: %s', err)
|
|
||||||
else
|
|
||||||
rspamd_logger.infox(rspamd_config, "BCC SMTP SUCCESS TO %s", bcc_dest)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not bcc_dest then
|
|
||||||
return -- stop
|
|
||||||
end
|
|
||||||
-- dot stuff content before sending
|
|
||||||
local email_content = tostring(task:get_content())
|
|
||||||
email_content = string.gsub(email_content, "\r\n%.", "\r\n..")
|
|
||||||
-- send mail
|
|
||||||
lua_smtp.sendmail({
|
|
||||||
task = task,
|
|
||||||
host = os.getenv("IPV4_NETWORK") .. '.253',
|
|
||||||
port = 591,
|
|
||||||
from = task:get_from(stp)[1].addr,
|
|
||||||
recipients = bcc_dest,
|
|
||||||
helo = 'bcc',
|
|
||||||
timeout = 20,
|
|
||||||
}, email_content, sendmail_cb)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- determine from
|
|
||||||
local from = task:get_from('smtp')
|
|
||||||
if from then
|
|
||||||
for _, a in ipairs(from) do
|
|
||||||
table.insert(from_table, a['addr']) -- add this rcpt to table
|
|
||||||
table.insert(from_table, '@' .. a['domain']) -- add this rcpts domain to table
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return -- stop
|
|
||||||
end
|
|
||||||
|
|
||||||
-- determine rcpts
|
|
||||||
local rcpts = task:get_recipients('smtp')
|
|
||||||
if rcpts then
|
|
||||||
for _, a in ipairs(rcpts) do
|
|
||||||
table.insert(rcpt_table, a['addr']) -- add this rcpt to table
|
|
||||||
table.insert(rcpt_table, '@' .. a['domain']) -- add this rcpts domain to table
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return -- stop
|
|
||||||
end
|
|
||||||
|
|
||||||
local action = task:get_metric_action('default')
|
|
||||||
rspamd_logger.infox("metric action now: %s", action)
|
|
||||||
|
|
||||||
local function rcpt_callback(err_message, code, body, headers)
|
|
||||||
if err_message == nil and code == 201 and body ~= nil then
|
|
||||||
if action == 'no action' or action == 'add header' or action == 'rewrite subject' then
|
|
||||||
send_mail(task, body)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function from_callback(err_message, code, body, headers)
|
|
||||||
if err_message == nil and code == 201 and body ~= nil then
|
|
||||||
if action == 'no action' or action == 'add header' or action == 'rewrite subject' then
|
|
||||||
send_mail(task, body)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if rcpt_table then
|
|
||||||
for _,e in ipairs(rcpt_table) do
|
|
||||||
rspamd_logger.infox(rspamd_config, "checking bcc for rcpt address %s", e)
|
|
||||||
rspamd_http.request({
|
|
||||||
task=task,
|
|
||||||
url='http://nginx:8081/bcc.php',
|
|
||||||
body='',
|
|
||||||
callback=rcpt_callback,
|
|
||||||
headers={Rcpt=e}
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if from_table then
|
|
||||||
for _,e in ipairs(from_table) do
|
|
||||||
rspamd_logger.infox(rspamd_config, "checking bcc for from address %s", e)
|
|
||||||
rspamd_http.request({
|
|
||||||
task=task,
|
|
||||||
url='http://nginx:8081/bcc.php',
|
|
||||||
body='',
|
|
||||||
callback=from_callback,
|
|
||||||
headers={From=e}
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end,
|
|
||||||
priority = 20
|
|
||||||
})
|
|
||||||
|
|
||||||
rspamd_config:register_symbol({
|
|
||||||
name = 'DYN_RL_CHECK',
|
|
||||||
type = 'prefilter',
|
|
||||||
callback = function(task)
|
|
||||||
local util = require("rspamd_util")
|
|
||||||
local redis_params = rspamd_parse_redis_server('dyn_rl')
|
|
||||||
local rspamd_logger = require "rspamd_logger"
|
|
||||||
local envfrom = task:get_from(1)
|
|
||||||
local uname = task:get_user()
|
|
||||||
if not envfrom or not uname then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
local uname = uname:lower()
|
|
||||||
|
|
||||||
local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case
|
|
||||||
|
|
||||||
local function redis_cb_user(err, data)
|
|
||||||
|
|
||||||
if err or type(data) ~= 'string' then
|
|
||||||
rspamd_logger.infox(rspamd_config, "dynamic ratelimit request for user %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying dynamic ratelimit for domain...", uname, data, err)
|
|
||||||
|
|
||||||
local function redis_key_cb_domain(err, data)
|
|
||||||
if err or type(data) ~= 'string' then
|
|
||||||
rspamd_logger.infox(rspamd_config, "dynamic ratelimit request for domain %s returned invalid or empty data (\"%s\") or error (\"%s\")", env_from_domain, data, err)
|
|
||||||
else
|
|
||||||
rspamd_logger.infox(rspamd_config, "found dynamic ratelimit in redis for domain %s with value %s", env_from_domain, data)
|
|
||||||
task:insert_result('DYN_RL', 0.0, data, env_from_domain)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local redis_ret_domain = rspamd_redis_make_request(task,
|
|
||||||
redis_params, -- connect params
|
|
||||||
env_from_domain, -- hash key
|
|
||||||
false, -- is write
|
|
||||||
redis_key_cb_domain, --callback
|
|
||||||
'HGET', -- command
|
|
||||||
{'RL_VALUE', env_from_domain} -- arguments
|
|
||||||
)
|
|
||||||
if not redis_ret_domain then
|
|
||||||
rspamd_logger.infox(rspamd_config, "cannot make request to load ratelimit for domain")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
rspamd_logger.infox(rspamd_config, "found dynamic ratelimit in redis for user %s with value %s", uname, data)
|
|
||||||
task:insert_result('DYN_RL', 0.0, data, uname)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
local redis_ret_user = rspamd_redis_make_request(task,
|
|
||||||
redis_params, -- connect params
|
|
||||||
uname, -- hash key
|
|
||||||
false, -- is write
|
|
||||||
redis_cb_user, --callback
|
|
||||||
'HGET', -- command
|
|
||||||
{'RL_VALUE', uname} -- arguments
|
|
||||||
)
|
|
||||||
if not redis_ret_user then
|
|
||||||
rspamd_logger.infox(rspamd_config, "cannot make request to load ratelimit for user")
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end,
|
|
||||||
flags = 'empty',
|
|
||||||
priority = 20
|
|
||||||
})
|
|
||||||
|
|
||||||
rspamd_config:register_symbol({
|
|
||||||
name = 'NO_LOG_STAT',
|
|
||||||
type = 'postfilter',
|
|
||||||
callback = function(task)
|
|
||||||
local from = task:get_header('From')
|
|
||||||
if from and (monitoring_hosts:get_key(from) or from == "watchdog@localhost") then
|
|
||||||
task:set_flag('no_log')
|
|
||||||
task:set_flag('no_stat')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
||||||
rspamd_config:register_symbol({
|
|
||||||
name = 'MOO_FOOTER',
|
|
||||||
type = 'prefilter',
|
|
||||||
callback = function(task)
|
|
||||||
local cjson = require "cjson"
|
|
||||||
local lua_mime = require "lua_mime"
|
|
||||||
local lua_util = require "lua_util"
|
|
||||||
local rspamd_logger = require "rspamd_logger"
|
|
||||||
local rspamd_http = require "rspamd_http"
|
|
||||||
local envfrom = task:get_from(1)
|
|
||||||
local uname = task:get_user()
|
|
||||||
if not envfrom or not uname then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
local uname = uname:lower()
|
|
||||||
local env_from_domain = envfrom[1].domain:lower()
|
|
||||||
local env_from_addr = envfrom[1].addr:lower()
|
|
||||||
|
|
||||||
-- determine newline type
|
|
||||||
local function newline(task)
|
|
||||||
local t = task:get_newlines_type()
|
|
||||||
|
|
||||||
if t == 'cr' then
|
|
||||||
return '\r'
|
|
||||||
elseif t == 'lf' then
|
|
||||||
return '\n'
|
|
||||||
end
|
|
||||||
|
|
||||||
return '\r\n'
|
|
||||||
end
|
|
||||||
-- retrieve footer
|
|
||||||
local function footer_cb(err_message, code, data, headers)
|
|
||||||
if err or type(data) ~= 'string' then
|
|
||||||
rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err)
|
|
||||||
else
|
|
||||||
|
|
||||||
-- parse json string
|
|
||||||
local footer = cjson.decode(data)
|
|
||||||
if not footer then
|
|
||||||
rspamd_logger.infox(rspamd_config, "parsing domain wide footer for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err)
|
|
||||||
else
|
|
||||||
if footer and type(footer) == "table" and (footer.html and footer.html ~= "" or footer.plain and footer.plain ~= "") then
|
|
||||||
rspamd_logger.infox(rspamd_config, "found domain wide footer for user %s: html=%s, plain=%s, vars=%s", uname, footer.html, footer.plain, footer.vars)
|
|
||||||
|
|
||||||
if footer.skip_replies ~= 0 then
|
|
||||||
in_reply_to = task:get_header_raw('in-reply-to')
|
|
||||||
if in_reply_to then
|
|
||||||
rspamd_logger.infox(rspamd_config, "mail is a reply - skip footer")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local envfrom_mime = task:get_from(2)
|
|
||||||
local from_name = ""
|
|
||||||
if envfrom_mime and envfrom_mime[1].name then
|
|
||||||
from_name = envfrom_mime[1].name
|
|
||||||
elseif envfrom and envfrom[1].name then
|
|
||||||
from_name = envfrom[1].name
|
|
||||||
end
|
|
||||||
|
|
||||||
-- default replacements
|
|
||||||
local replacements = {
|
|
||||||
auth_user = uname,
|
|
||||||
from_user = envfrom[1].user,
|
|
||||||
from_name = from_name,
|
|
||||||
from_addr = envfrom[1].addr,
|
|
||||||
from_domain = envfrom[1].domain:lower()
|
|
||||||
}
|
|
||||||
-- add custom mailbox attributes
|
|
||||||
if footer.vars and type(footer.vars) == "string" then
|
|
||||||
local footer_vars = cjson.decode(footer.vars)
|
|
||||||
|
|
||||||
if type(footer_vars) == "table" then
|
|
||||||
for key, value in pairs(footer_vars) do
|
|
||||||
replacements[key] = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if footer.html and footer.html ~= "" then
|
|
||||||
footer.html = lua_util.jinja_template(footer.html, replacements, true)
|
|
||||||
end
|
|
||||||
if footer.plain and footer.plain ~= "" then
|
|
||||||
footer.plain = lua_util.jinja_template(footer.plain, replacements, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- add footer
|
|
||||||
local out = {}
|
|
||||||
local rewrite = lua_mime.add_text_footer(task, footer.html, footer.plain) or {}
|
|
||||||
|
|
||||||
local seen_cte
|
|
||||||
local newline_s = newline(task)
|
|
||||||
|
|
||||||
local function rewrite_ct_cb(name, hdr)
|
|
||||||
if rewrite.need_rewrite_ct then
|
|
||||||
if name:lower() == 'content-type' then
|
|
||||||
local nct = string.format('%s: %s/%s; charset=utf-8',
|
|
||||||
'Content-Type', rewrite.new_ct.type, rewrite.new_ct.subtype)
|
|
||||||
out[#out + 1] = nct
|
|
||||||
-- update Content-Type header
|
|
||||||
task:set_milter_reply({
|
|
||||||
remove_headers = {['Content-Type'] = 0},
|
|
||||||
})
|
|
||||||
task:set_milter_reply({
|
|
||||||
add_headers = {['Content-Type'] = string.format('%s/%s; charset=utf-8', rewrite.new_ct.type, rewrite.new_ct.subtype)}
|
|
||||||
})
|
|
||||||
return
|
|
||||||
elseif name:lower() == 'content-transfer-encoding' then
|
|
||||||
out[#out + 1] = string.format('%s: %s',
|
|
||||||
'Content-Transfer-Encoding', 'quoted-printable')
|
|
||||||
-- update Content-Transfer-Encoding header
|
|
||||||
task:set_milter_reply({
|
|
||||||
remove_headers = {['Content-Transfer-Encoding'] = 0},
|
|
||||||
})
|
|
||||||
task:set_milter_reply({
|
|
||||||
add_headers = {['Content-Transfer-Encoding'] = 'quoted-printable'}
|
|
||||||
})
|
|
||||||
seen_cte = true
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
out[#out + 1] = hdr.raw:gsub('\r?\n?$', '')
|
|
||||||
end
|
|
||||||
|
|
||||||
task:headers_foreach(rewrite_ct_cb, {full = true})
|
|
||||||
|
|
||||||
if not seen_cte and rewrite.need_rewrite_ct then
|
|
||||||
out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', 'quoted-printable')
|
|
||||||
end
|
|
||||||
|
|
||||||
-- End of headers
|
|
||||||
out[#out + 1] = newline_s
|
|
||||||
|
|
||||||
if rewrite.out then
|
|
||||||
for _,o in ipairs(rewrite.out) do
|
|
||||||
out[#out + 1] = o
|
|
||||||
end
|
|
||||||
else
|
|
||||||
out[#out + 1] = task:get_rawbody()
|
|
||||||
end
|
|
||||||
local out_parts = {}
|
|
||||||
for _,o in ipairs(out) do
|
|
||||||
if type(o) ~= 'table' then
|
|
||||||
out_parts[#out_parts + 1] = o
|
|
||||||
out_parts[#out_parts + 1] = newline_s
|
|
||||||
else
|
|
||||||
local removePrefix = "--\x0D\x0AContent-Type"
|
|
||||||
if string.lower(string.sub(tostring(o[1]), 1, string.len(removePrefix))) == string.lower(removePrefix) then
|
|
||||||
o[1] = string.sub(tostring(o[1]), string.len("--\x0D\x0A") + 1)
|
|
||||||
end
|
|
||||||
out_parts[#out_parts + 1] = o[1]
|
|
||||||
if o[2] then
|
|
||||||
out_parts[#out_parts + 1] = newline_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
task:set_message(out_parts)
|
|
||||||
else
|
|
||||||
rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\")", uname, data)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- fetch footer
|
|
||||||
rspamd_http.request({
|
|
||||||
task=task,
|
|
||||||
url='http://nginx:8081/footer.php',
|
|
||||||
body='',
|
|
||||||
callback=footer_cb,
|
|
||||||
headers={Domain=env_from_domain,Username=uname,From=env_from_addr},
|
|
||||||
})
|
|
||||||
|
|
||||||
return true
|
|
||||||
end,
|
|
||||||
priority = 1
|
|
||||||
})
|
|
@ -1,260 +0,0 @@
|
|||||||
<?php
|
|
||||||
// File size is limited by Nginx site to 10M
|
|
||||||
// To speed things up, we do not include prerequisites
|
|
||||||
header('Content-Type: text/plain');
|
|
||||||
require_once "vars.inc.php";
|
|
||||||
// Do not show errors, we log to using error_log
|
|
||||||
ini_set('error_reporting', 0);
|
|
||||||
// Init database
|
|
||||||
//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
|
|
||||||
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
|
|
||||||
$opt = [
|
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
||||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
||||||
PDO::ATTR_EMULATE_PREPARES => false,
|
|
||||||
];
|
|
||||||
try {
|
|
||||||
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
|
||||||
}
|
|
||||||
catch (PDOException $e) {
|
|
||||||
error_log("QUARANTINE: " . $e . PHP_EOL);
|
|
||||||
http_response_code(501);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
// Init Redis
|
|
||||||
$redis = new Redis();
|
|
||||||
$redis->connect('redis-mailcow', 6379);
|
|
||||||
|
|
||||||
// Functions
|
|
||||||
function parse_email($email) {
|
|
||||||
if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
|
|
||||||
$a = strrpos($email, '@');
|
|
||||||
return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1));
|
|
||||||
}
|
|
||||||
if (!function_exists('getallheaders')) {
|
|
||||||
function getallheaders() {
|
|
||||||
if (!is_array($_SERVER)) {
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
$headers = array();
|
|
||||||
foreach ($_SERVER as $name => $value) {
|
|
||||||
if (substr($name, 0, 5) == 'HTTP_') {
|
|
||||||
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $headers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$raw_data_content = file_get_contents('php://input');
|
|
||||||
$raw_data = mb_convert_encoding($raw_data_content, 'HTML-ENTITIES', "UTF-8");
|
|
||||||
$headers = getallheaders();
|
|
||||||
|
|
||||||
$qid = $headers['X-Rspamd-Qid'];
|
|
||||||
$fuzzy = $headers['X-Rspamd-Fuzzy'];
|
|
||||||
$subject = iconv_mime_decode($headers['X-Rspamd-Subject']);
|
|
||||||
$score = $headers['X-Rspamd-Score'];
|
|
||||||
$rcpts = $headers['X-Rspamd-Rcpt'];
|
|
||||||
$user = $headers['X-Rspamd-User'];
|
|
||||||
$ip = $headers['X-Rspamd-Ip'];
|
|
||||||
$action = $headers['X-Rspamd-Action'];
|
|
||||||
$sender = $headers['X-Rspamd-From'];
|
|
||||||
$symbols = $headers['X-Rspamd-Symbols'];
|
|
||||||
|
|
||||||
$raw_size = (int)$_SERVER['CONTENT_LENGTH'];
|
|
||||||
|
|
||||||
if (empty($sender)) {
|
|
||||||
error_log("QUARANTINE: Unknown sender, assuming empty-env-from@localhost" . PHP_EOL);
|
|
||||||
$sender = 'empty-env-from@localhost';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($fuzzy == 'unknown') {
|
|
||||||
$fuzzy = '[]';
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$max_size = (int)$redis->Get('Q_MAX_SIZE');
|
|
||||||
if (($max_size * 1048576) < $raw_size) {
|
|
||||||
error_log(sprintf("QUARANTINE: Message too large: %d b exceeds %d b", $raw_size, ($max_size * 1048576)) . PHP_EOL);
|
|
||||||
http_response_code(505);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
if ($exclude_domains = $redis->Get('Q_EXCLUDE_DOMAINS')) {
|
|
||||||
$exclude_domains = json_decode($exclude_domains, true);
|
|
||||||
}
|
|
||||||
$retention_size = (int)$redis->Get('Q_RETENTION_SIZE');
|
|
||||||
}
|
|
||||||
catch (RedisException $e) {
|
|
||||||
error_log("QUARANTINE: " . $e . PHP_EOL);
|
|
||||||
http_response_code(504);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$rcpt_final_mailboxes = array();
|
|
||||||
|
|
||||||
// Loop through all rcpts
|
|
||||||
foreach (json_decode($rcpts, true) as $rcpt) {
|
|
||||||
// Remove tag
|
|
||||||
$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt);
|
|
||||||
|
|
||||||
// Break rcpt into local part and domain part
|
|
||||||
$parsed_rcpt = parse_email($rcpt);
|
|
||||||
|
|
||||||
// Skip if not a mailcow handled domain
|
|
||||||
try {
|
|
||||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (RedisException $e) {
|
|
||||||
error_log("QUARANTINE: " . $e . PHP_EOL);
|
|
||||||
http_response_code(504);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip if domain is excluded
|
|
||||||
if (in_array($parsed_rcpt['domain'], $exclude_domains)) {
|
|
||||||
error_log(sprintf("QUARANTINE: Skipped domain %s", $parsed_rcpt['domain']) . PHP_EOL);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always assume rcpt is not a final mailbox but an alias for a mailbox or further aliases
|
|
||||||
//
|
|
||||||
// rcpt
|
|
||||||
// |
|
|
||||||
// mailbox <-- goto ---> alias1, alias2, mailbox2
|
|
||||||
// | |
|
|
||||||
// mailbox3 |
|
|
||||||
// |
|
|
||||||
// alias3 ---> mailbox4
|
|
||||||
//
|
|
||||||
try {
|
|
||||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':rcpt' => $rcpt
|
|
||||||
));
|
|
||||||
$gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
|
||||||
if (empty($gotos)) {
|
|
||||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':rcpt' => '@' . $parsed_rcpt['domain']
|
|
||||||
));
|
|
||||||
$gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
|
||||||
}
|
|
||||||
if (empty($gotos)) {
|
|
||||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :rcpt AND `active` = '1'");
|
|
||||||
$stmt->execute(array(':rcpt' => $parsed_rcpt['domain']));
|
|
||||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
|
||||||
if ($goto_branch) {
|
|
||||||
$gotos = $parsed_rcpt['local'] . '@' . $goto_branch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$gotos_array = explode(',', $gotos);
|
|
||||||
|
|
||||||
$loop_c = 0;
|
|
||||||
|
|
||||||
while (count($gotos_array) != 0 && $loop_c <= 20) {
|
|
||||||
|
|
||||||
// Loop through all found gotos
|
|
||||||
foreach ($gotos_array as $index => &$goto) {
|
|
||||||
error_log("RCPT RESOVLER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL);
|
|
||||||
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');");
|
|
||||||
$stmt->execute(array(':goto' => $goto));
|
|
||||||
$username = $stmt->fetch(PDO::FETCH_ASSOC)['username'];
|
|
||||||
if (!empty($username)) {
|
|
||||||
error_log("RCPT RESOVLER: http pipe: mailbox found: " . $username . PHP_EOL);
|
|
||||||
// Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate
|
|
||||||
if (!in_array($username, $rcpt_final_mailboxes)) {
|
|
||||||
$rcpt_final_mailboxes[] = $username;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$parsed_goto = parse_email($goto);
|
|
||||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
|
||||||
error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'");
|
|
||||||
$stmt->execute(array(':goto' => $goto));
|
|
||||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
|
||||||
if ($goto_branch) {
|
|
||||||
error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
|
|
||||||
$goto_branch_array = explode(',', $goto_branch);
|
|
||||||
} else {
|
|
||||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'");
|
|
||||||
$stmt->execute(array(':domain' => $parsed_goto['domain']));
|
|
||||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
|
||||||
if ($goto_branch) {
|
|
||||||
error_log("RCPT RESOVLER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL);
|
|
||||||
$goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// goto item was processed, unset
|
|
||||||
unset($gotos_array[$index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge goto branch array derived from previous loop (if any), filter duplicates and unset goto branch array
|
|
||||||
if (!empty($goto_branch_array)) {
|
|
||||||
$gotos_array = array_unique(array_merge($gotos_array, $goto_branch_array));
|
|
||||||
unset($goto_branch_array);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reindex array
|
|
||||||
$gotos_array = array_values($gotos_array);
|
|
||||||
|
|
||||||
// Force exit if loop cannot be solved
|
|
||||||
// Postfix does not allow for alias loops, so this should never happen.
|
|
||||||
$loop_c++;
|
|
||||||
error_log("RCPT RESOVLER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (PDOException $e) {
|
|
||||||
error_log("RCPT RESOVLER: " . $e->getMessage() . PHP_EOL);
|
|
||||||
http_response_code(502);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($rcpt_final_mailboxes as $rcpt_final) {
|
|
||||||
error_log("QUARANTINE: quarantine pipe: processing quarantine message for rcpt " . $rcpt_final . PHP_EOL);
|
|
||||||
try {
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO `quarantine` (`qid`, `subject`, `score`, `sender`, `rcpt`, `symbols`, `user`, `ip`, `msg`, `action`, `fuzzy_hashes`)
|
|
||||||
VALUES (:qid, :subject, :score, :sender, :rcpt, :symbols, :user, :ip, :msg, :action, :fuzzy_hashes)");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':qid' => $qid,
|
|
||||||
':subject' => $subject,
|
|
||||||
':score' => $score,
|
|
||||||
':sender' => $sender,
|
|
||||||
':rcpt' => $rcpt_final,
|
|
||||||
':symbols' => $symbols,
|
|
||||||
':user' => $user,
|
|
||||||
':ip' => $ip,
|
|
||||||
':msg' => $raw_data,
|
|
||||||
':action' => $action,
|
|
||||||
':fuzzy_hashes' => $fuzzy
|
|
||||||
));
|
|
||||||
$stmt = $pdo->prepare('DELETE FROM `quarantine` WHERE `rcpt` = :rcpt AND `id` NOT IN (
|
|
||||||
SELECT `id`
|
|
||||||
FROM (
|
|
||||||
SELECT `id`
|
|
||||||
FROM `quarantine`
|
|
||||||
WHERE `rcpt` = :rcpt2
|
|
||||||
ORDER BY id DESC
|
|
||||||
LIMIT :retention_size
|
|
||||||
) x
|
|
||||||
);');
|
|
||||||
$stmt->execute(array(
|
|
||||||
':rcpt' => $rcpt_final,
|
|
||||||
':rcpt2' => $rcpt_final,
|
|
||||||
':retention_size' => $retention_size
|
|
||||||
));
|
|
||||||
}
|
|
||||||
catch (PDOException $e) {
|
|
||||||
error_log("QUARANTINE: " . $e->getMessage() . PHP_EOL);
|
|
||||||
http_response_code(503);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
|||||||
<?php
|
|
||||||
// File size is limited by Nginx site to 10M
|
|
||||||
// To speed things up, we do not include prerequisites
|
|
||||||
header('Content-Type: text/plain');
|
|
||||||
require_once "vars.inc.php";
|
|
||||||
// Do not show errors, we log to using error_log
|
|
||||||
ini_set('error_reporting', 0);
|
|
||||||
// Init Redis
|
|
||||||
$redis = new Redis();
|
|
||||||
try {
|
|
||||||
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
|
|
||||||
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$redis->connect('redis-mailcow', 6379);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception $e) {
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$raw_data_content = file_get_contents('php://input');
|
|
||||||
$raw_data_decoded = json_decode($raw_data_content, true);
|
|
||||||
|
|
||||||
$data['time'] = time();
|
|
||||||
$data['rcpt'] = implode(', ', $raw_data_decoded['rcpt']);
|
|
||||||
$data['from'] = $raw_data_decoded['from'];
|
|
||||||
$data['user'] = $raw_data_decoded['user'];
|
|
||||||
$symbol_rl_key = array_search('RATELIMITED', array_column($raw_data_decoded['symbols'], 'name'));
|
|
||||||
$data['rl_info'] = implode($raw_data_decoded['symbols'][$symbol_rl_key]['options']);
|
|
||||||
preg_match('/(.+)\((.+)\)/i', $data['rl_info'], $rl_matches);
|
|
||||||
if (!empty($rl_matches[1]) && !empty($rl_matches[2])) {
|
|
||||||
$data['rl_name'] = $rl_matches[1];
|
|
||||||
$data['rl_hash'] = $rl_matches[2];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$data['rl_name'] = 'err';
|
|
||||||
$data['rl_hash'] = 'err';
|
|
||||||
}
|
|
||||||
$data['qid'] = $raw_data_decoded['qid'];
|
|
||||||
$data['ip'] = $raw_data_decoded['ip'];
|
|
||||||
$data['message_id'] = $raw_data_decoded['message_id'];
|
|
||||||
$data['header_subject'] = implode(' ', $raw_data_decoded['header_subject']);
|
|
||||||
$data['header_from'] = implode(', ', $raw_data_decoded['header_from']);
|
|
||||||
|
|
||||||
$redis->lpush('RL_LOG', json_encode($data));
|
|
||||||
exit;
|
|
||||||
|
|
@ -1,275 +0,0 @@
|
|||||||
<?php
|
|
||||||
// File size is limited by Nginx site to 10M
|
|
||||||
// To speed things up, we do not include prerequisites
|
|
||||||
header('Content-Type: text/plain');
|
|
||||||
require_once "vars.inc.php";
|
|
||||||
// Do not show errors, we log to using error_log
|
|
||||||
ini_set('error_reporting', 0);
|
|
||||||
// Init database
|
|
||||||
//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
|
|
||||||
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
|
|
||||||
$opt = [
|
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
||||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
||||||
PDO::ATTR_EMULATE_PREPARES => false,
|
|
||||||
];
|
|
||||||
try {
|
|
||||||
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
|
||||||
}
|
|
||||||
catch (PDOException $e) {
|
|
||||||
error_log("NOTIFY: " . $e . PHP_EOL);
|
|
||||||
http_response_code(501);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
// Init Redis
|
|
||||||
$redis = new Redis();
|
|
||||||
$redis->connect('redis-mailcow', 6379);
|
|
||||||
|
|
||||||
// Functions
|
|
||||||
function parse_email($email) {
|
|
||||||
if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
|
|
||||||
$a = strrpos($email, '@');
|
|
||||||
return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1));
|
|
||||||
}
|
|
||||||
if (!function_exists('getallheaders')) {
|
|
||||||
function getallheaders() {
|
|
||||||
if (!is_array($_SERVER)) {
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
$headers = array();
|
|
||||||
foreach ($_SERVER as $name => $value) {
|
|
||||||
if (substr($name, 0, 5) == 'HTTP_') {
|
|
||||||
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $headers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$headers = getallheaders();
|
|
||||||
$json_body = json_decode(file_get_contents('php://input'));
|
|
||||||
|
|
||||||
$qid = $headers['X-Rspamd-Qid'];
|
|
||||||
$rcpts = $headers['X-Rspamd-Rcpt'];
|
|
||||||
$sender = $headers['X-Rspamd-From'];
|
|
||||||
$ip = $headers['X-Rspamd-Ip'];
|
|
||||||
$subject = iconv_mime_decode($headers['X-Rspamd-Subject']);
|
|
||||||
$messageid= $json_body->message_id;
|
|
||||||
$priority = 0;
|
|
||||||
|
|
||||||
$symbols_array = json_decode($headers['X-Rspamd-Symbols'], true);
|
|
||||||
if (is_array($symbols_array)) {
|
|
||||||
foreach ($symbols_array as $symbol) {
|
|
||||||
if ($symbol['name'] == 'HAS_X_PRIO_ONE') {
|
|
||||||
$priority = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$sender_address = $json_body->header_from[0];
|
|
||||||
$sender_name = '-';
|
|
||||||
if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $sender_address, $matches)) {
|
|
||||||
$sender_address = $matches['address'];
|
|
||||||
$sender_name = trim($matches['name'], '"\' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
$to_address = $json_body->header_to[0];
|
|
||||||
$to_name = '-';
|
|
||||||
if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $to_address, $matches)) {
|
|
||||||
$to_address = $matches['address'];
|
|
||||||
$to_name = trim($matches['name'], '"\' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
$rcpt_final_mailboxes = array();
|
|
||||||
|
|
||||||
// Loop through all rcpts
|
|
||||||
foreach (json_decode($rcpts, true) as $rcpt) {
|
|
||||||
// Remove tag
|
|
||||||
$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt);
|
|
||||||
|
|
||||||
// Break rcpt into local part and domain part
|
|
||||||
$parsed_rcpt = parse_email($rcpt);
|
|
||||||
|
|
||||||
// Skip if not a mailcow handled domain
|
|
||||||
try {
|
|
||||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (RedisException $e) {
|
|
||||||
error_log("NOTIFY: " . $e . PHP_EOL);
|
|
||||||
http_response_code(504);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always assume rcpt is not a final mailbox but an alias for a mailbox or further aliases
|
|
||||||
//
|
|
||||||
// rcpt
|
|
||||||
// |
|
|
||||||
// mailbox <-- goto ---> alias1, alias2, mailbox2
|
|
||||||
// | |
|
|
||||||
// mailbox3 |
|
|
||||||
// |
|
|
||||||
// alias3 ---> mailbox4
|
|
||||||
//
|
|
||||||
try {
|
|
||||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':rcpt' => $rcpt
|
|
||||||
));
|
|
||||||
$gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
|
||||||
if (empty($gotos)) {
|
|
||||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':rcpt' => '@' . $parsed_rcpt['domain']
|
|
||||||
));
|
|
||||||
$gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
|
||||||
}
|
|
||||||
if (empty($gotos)) {
|
|
||||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :rcpt AND `active` = '1'");
|
|
||||||
$stmt->execute(array(':rcpt' => $parsed_rcpt['domain']));
|
|
||||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
|
||||||
if ($goto_branch) {
|
|
||||||
$gotos = $parsed_rcpt['local'] . '@' . $goto_branch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$gotos_array = explode(',', $gotos);
|
|
||||||
|
|
||||||
$loop_c = 0;
|
|
||||||
|
|
||||||
while (count($gotos_array) != 0 && $loop_c <= 20) {
|
|
||||||
|
|
||||||
// Loop through all found gotos
|
|
||||||
foreach ($gotos_array as $index => &$goto) {
|
|
||||||
error_log("RCPT RESOVLER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL);
|
|
||||||
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');");
|
|
||||||
$stmt->execute(array(':goto' => $goto));
|
|
||||||
$username = $stmt->fetch(PDO::FETCH_ASSOC)['username'];
|
|
||||||
if (!empty($username)) {
|
|
||||||
error_log("RCPT RESOVLER: http pipe: mailbox found: " . $username . PHP_EOL);
|
|
||||||
// Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate
|
|
||||||
if (!in_array($username, $rcpt_final_mailboxes)) {
|
|
||||||
$rcpt_final_mailboxes[] = $username;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$parsed_goto = parse_email($goto);
|
|
||||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
|
||||||
error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'");
|
|
||||||
$stmt->execute(array(':goto' => $goto));
|
|
||||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
|
||||||
if ($goto_branch) {
|
|
||||||
error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
|
|
||||||
$goto_branch_array = explode(',', $goto_branch);
|
|
||||||
} else {
|
|
||||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'");
|
|
||||||
$stmt->execute(array(':domain' => $parsed_goto['domain']));
|
|
||||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
|
||||||
if ($goto_branch) {
|
|
||||||
error_log("RCPT RESOVLER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL);
|
|
||||||
$goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// goto item was processed, unset
|
|
||||||
unset($gotos_array[$index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge goto branch array derived from previous loop (if any), filter duplicates and unset goto branch array
|
|
||||||
if (!empty($goto_branch_array)) {
|
|
||||||
$gotos_array = array_unique(array_merge($gotos_array, $goto_branch_array));
|
|
||||||
unset($goto_branch_array);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reindex array
|
|
||||||
$gotos_array = array_values($gotos_array);
|
|
||||||
|
|
||||||
// Force exit if loop cannot be solved
|
|
||||||
// Postfix does not allow for alias loops, so this should never happen.
|
|
||||||
$loop_c++;
|
|
||||||
error_log("RCPT RESOVLER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (PDOException $e) {
|
|
||||||
error_log("RCPT RESOVLER: " . $e->getMessage() . PHP_EOL);
|
|
||||||
http_response_code(502);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
foreach ($rcpt_final_mailboxes as $rcpt_final) {
|
|
||||||
error_log("NOTIFY: pushover pipe: processing pushover message for rcpt " . $rcpt_final . PHP_EOL);
|
|
||||||
$stmt = $pdo->prepare("SELECT * FROM `pushover`
|
|
||||||
WHERE `username` = :username AND `active` = '1'");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':username' => $rcpt_final
|
|
||||||
));
|
|
||||||
$api_data = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
if (isset($api_data['key']) && isset($api_data['token'])) {
|
|
||||||
$title = (!empty($api_data['title'])) ? $api_data['title'] : 'Mail';
|
|
||||||
$text = (!empty($api_data['text'])) ? $api_data['text'] : 'You\'ve got mail 📧';
|
|
||||||
$attributes = json_decode($api_data['attributes'], true);
|
|
||||||
$senders = explode(',', $api_data['senders']);
|
|
||||||
$senders = array_filter($senders);
|
|
||||||
$senders_regex = $api_data['senders_regex'];
|
|
||||||
$sender_validated = false;
|
|
||||||
if (empty($senders) && empty($senders_regex)) {
|
|
||||||
$sender_validated = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (!empty($senders)) {
|
|
||||||
if (in_array($sender, $senders)) {
|
|
||||||
$sender_validated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!empty($senders_regex) && $sender_validated !== true) {
|
|
||||||
if (preg_match($senders_regex, $sender)) {
|
|
||||||
$sender_validated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($sender_validated === false) {
|
|
||||||
error_log("NOTIFY: pushover pipe: skipping unwanted sender " . $sender);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ($attributes['only_x_prio'] == "1" && $priority == 0) {
|
|
||||||
error_log("NOTIFY: pushover pipe: mail has no X-Priority: 1 header, skipping");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$post_fields = array(
|
|
||||||
"token" => $api_data['token'],
|
|
||||||
"user" => $api_data['key'],
|
|
||||||
"title" => sprintf("%s", str_replace(
|
|
||||||
array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}', '{MSG_ID}'),
|
|
||||||
array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address, $messageid), $title)
|
|
||||||
),
|
|
||||||
"priority" => $priority,
|
|
||||||
"message" => sprintf("%s", str_replace(
|
|
||||||
array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}', '{MSG_ID}', '\n'),
|
|
||||||
array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address, $messageid, PHP_EOL), $text)
|
|
||||||
),
|
|
||||||
"sound" => $attributes['sound'] ?? "pushover"
|
|
||||||
);
|
|
||||||
if ($attributes['evaluate_x_prio'] == "1" && $priority == 1) {
|
|
||||||
$post_fields['expire'] = 600;
|
|
||||||
$post_fields['retry'] = 120;
|
|
||||||
$post_fields['priority'] = 2;
|
|
||||||
}
|
|
||||||
curl_setopt_array($ch = curl_init(), array(
|
|
||||||
CURLOPT_URL => "https://api.pushover.net/1/messages.json",
|
|
||||||
CURLOPT_POSTFIELDS => $post_fields,
|
|
||||||
CURLOPT_SAFE_UPLOAD => true,
|
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
|
||||||
));
|
|
||||||
$result = curl_exec($ch);
|
|
||||||
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
||||||
curl_close($ch);
|
|
||||||
error_log("NOTIFY: result: " . $httpcode . PHP_EOL);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once('../../../web/inc/vars.inc.php');
|
|
||||||
if (file_exists('../../../web/inc/vars.local.inc.php')) {
|
|
||||||
include_once('../../../web/inc/vars.local.inc.php');
|
|
||||||
}
|
|
||||||
?>
|
|
@ -1,5 +0,0 @@
|
|||||||
level = "silent";
|
|
||||||
type = "console";
|
|
||||||
systemd = false;
|
|
||||||
.include "$CONFDIR/logging.inc"
|
|
||||||
.include(try=true; priority=20) "$CONFDIR/override.d/logging.custom.inc"
|
|
@ -1,4 +0,0 @@
|
|||||||
whitelisted_rcpts = "postmaster,mailer-daemon";
|
|
||||||
max_rcpt = 25;
|
|
||||||
custom_keywords = "/etc/rspamd/lua/ratelimit.lua";
|
|
||||||
info_symbol = "RATELIMITED";
|
|
@ -1,7 +0,0 @@
|
|||||||
bind_socket = "*:11334";
|
|
||||||
count = 1;
|
|
||||||
secure_ip = "127.0.0.1";
|
|
||||||
secure_ip = "::1";
|
|
||||||
bind_socket = "/var/lib/rspamd/rspamd.sock mode=0666 owner=nobody";
|
|
||||||
.include(try=true; priority=10) "$CONFDIR/override.d/worker-controller-password.inc"
|
|
||||||
.include(try=true; priority=30) "$CONFDIR/override.d/worker-controller.custom.inc"
|
|
@ -1,12 +0,0 @@
|
|||||||
# Socket to listen on (UDP and TCP from rspamd 1.3)
|
|
||||||
bind_socket = "*:11445";
|
|
||||||
allow_update = ["127.0.0.1", "::1"];
|
|
||||||
# Number of processes to serve this storage (useful for read scaling)
|
|
||||||
count = 1;
|
|
||||||
# Backend ("sqlite" or "redis" - default "sqlite")
|
|
||||||
backend = "redis";
|
|
||||||
# Hashes storage time (3 months)
|
|
||||||
expire = 90d;
|
|
||||||
# Synchronize updates to the storage each minute
|
|
||||||
sync = 1min;
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
|||||||
bind_socket = "*:11333";
|
|
||||||
task_timeout = 25s;
|
|
||||||
count = 1;
|
|
||||||
.include(try=true; priority=30) "$CONFDIR/override.d/worker-normal.custom.inc"
|
|
@ -1,9 +0,0 @@
|
|||||||
bind_socket = "rspamd:9900";
|
|
||||||
milter = true;
|
|
||||||
upstream "local" {
|
|
||||||
name = "localhost";
|
|
||||||
default = true;
|
|
||||||
hosts = "rspamd:11333"
|
|
||||||
}
|
|
||||||
reject_message = "This message does not meet our delivery requirements";
|
|
||||||
.include(try=true; priority=30) "$CONFDIR/override.d/worker-proxy.custom.inc"
|
|
@ -1 +0,0 @@
|
|||||||
This is where you should copy any rspamd custom module
|
|
@ -1 +0,0 @@
|
|||||||
# rspamd.conf.local
|
|
@ -1,2 +0,0 @@
|
|||||||
# rspamd.conf.override
|
|
||||||
|
|
267
Stable2.0/docker-entrypoint.sh
Normal file
267
Stable2.0/docker-entrypoint.sh
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# mkdir -p /etc/rspamd/plugins.d \
|
||||||
|
# /etc/rspamd/custom
|
||||||
|
|
||||||
|
# touch /etc/rspamd/rspamd.conf.local \
|
||||||
|
# /etc/rspamd/rspamd.conf.override
|
||||||
|
|
||||||
|
chmod 755 /var/lib/rspamd
|
||||||
|
|
||||||
|
|
||||||
|
[[ ! -f /etc/rspamd/override.d/worker-controller-password.inc ]] && echo '# Autogenerated' > /etc/rspamd/override.d/worker-controller-password.inc
|
||||||
|
|
||||||
|
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
||||||
|
cat <<EOF > /etc/rspamd/local.d/redis.conf
|
||||||
|
read_servers = "redis:6379";
|
||||||
|
write_servers = "${REDIS_SLAVEOF_IP}:${REDIS_SLAVEOF_PORT}";
|
||||||
|
timeout = 10;
|
||||||
|
EOF
|
||||||
|
until [[ $(redis-cli -h redis-mailcow PING) == "PONG" ]]; do
|
||||||
|
echo "Waiting for Redis @redis-mailcow..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
until [[ $(redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} PING) == "PONG" ]]; do
|
||||||
|
echo "Waiting for Redis @${REDIS_SLAVEOF_IP}..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
redis-cli -h redis-mailcow SLAVEOF ${REDIS_SLAVEOF_IP} ${REDIS_SLAVEOF_PORT}
|
||||||
|
else
|
||||||
|
cat <<EOF > /etc/rspamd/local.d/redis.conf
|
||||||
|
servers = "redis:6379";
|
||||||
|
timeout = 10;
|
||||||
|
db = 1
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Provide additional lua modules
|
||||||
|
ln -s /usr/lib/$(uname -m)-linux-gnu/liblua5.1-cjson.so.0.0.0 /usr/lib/rspamd/cjson.so
|
||||||
|
|
||||||
|
chown -R _rspamd:_rspamd /var/lib/rspamd \
|
||||||
|
/etc/rspamd/local.d \
|
||||||
|
/etc/rspamd/override.d \
|
||||||
|
/etc/rspamd/rspamd.conf.local \
|
||||||
|
/etc/rspamd/rspamd.conf.override \
|
||||||
|
/etc/rspamd/plugins.d
|
||||||
|
|
||||||
|
# Fix missing default global maps, if any
|
||||||
|
# These exists in mailcow UI and should not be removed
|
||||||
|
touch /etc/rspamd/custom/global_mime_from_blacklist.map \
|
||||||
|
/etc/rspamd/custom/global_rcpt_blacklist.map \
|
||||||
|
/etc/rspamd/custom/global_smtp_from_blacklist.map \
|
||||||
|
/etc/rspamd/custom/global_mime_from_whitelist.map \
|
||||||
|
/etc/rspamd/custom/global_rcpt_whitelist.map \
|
||||||
|
/etc/rspamd/custom/global_smtp_from_whitelist.map \
|
||||||
|
/etc/rspamd/custom/bad_languages.map \
|
||||||
|
/etc/rspamd/custom/sa-rules \
|
||||||
|
/etc/rspamd/custom/dovecot_trusted.map \
|
||||||
|
/etc/rspamd/custom/rspamd_trusted.map \
|
||||||
|
/etc/rspamd/custom/mailcow_networks.map \
|
||||||
|
/etc/rspamd/custom/ip_wl.map \
|
||||||
|
/etc/rspamd/custom/fishy_tlds.map \
|
||||||
|
/etc/rspamd/custom/bad_words.map \
|
||||||
|
/etc/rspamd/custom/bad_asn.map \
|
||||||
|
/etc/rspamd/custom/bad_words_de.map \
|
||||||
|
/etc/rspamd/custom/bulk_header.map \
|
||||||
|
/etc/rspamd/custom/bad_header.map
|
||||||
|
|
||||||
|
# www-data (82) group needs to write to these files
|
||||||
|
chown _rspamd:_rspamd /etc/rspamd/custom/
|
||||||
|
chmod 0755 /etc/rspamd/custom/.
|
||||||
|
chmod 644 -R /etc/rspamd/custom/*
|
||||||
|
|
||||||
|
# Run hooks
|
||||||
|
for file in /hooks/*; do
|
||||||
|
if [ -x "${file}" ]; then
|
||||||
|
echo "Running hook ${file}"
|
||||||
|
"${file}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# If DQS KEY is set in mailcow.conf add Spamhaus DQS RBLs
|
||||||
|
if [[ ! -z ${SPAMHAUS_DQS_KEY} ]]; then
|
||||||
|
cat <<EOF > /etc/rspamd/custom/dqs-rbl.conf
|
||||||
|
# Autogenerated by mailcow. DO NOT TOUCH!
|
||||||
|
spamhaus {
|
||||||
|
rbl = "${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net";
|
||||||
|
from = false;
|
||||||
|
}
|
||||||
|
spamhaus_from {
|
||||||
|
from = true;
|
||||||
|
received = false;
|
||||||
|
rbl = "${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net";
|
||||||
|
returncodes {
|
||||||
|
SPAMHAUS_ZEN = [ "127.0.0.2", "127.0.0.3", "127.0.0.4", "127.0.0.5", "127.0.0.6", "127.0.0.7", "127.0.0.9", "127.0.0.10", "127.0.0.11" ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spamhaus_authbl_received {
|
||||||
|
# Check if the sender client is listed in AuthBL (AuthBL is *not* part of ZEN)
|
||||||
|
rbl = "${SPAMHAUS_DQS_KEY}.authbl.dq.spamhaus.net";
|
||||||
|
from = false;
|
||||||
|
received = true;
|
||||||
|
ipv6 = true;
|
||||||
|
returncodes {
|
||||||
|
SH_AUTHBL_RECEIVED = "127.0.0.20"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spamhaus_dbl {
|
||||||
|
# Add checks on the HELO string
|
||||||
|
rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
|
||||||
|
helo = true;
|
||||||
|
rdns = true;
|
||||||
|
dkim = true;
|
||||||
|
disable_monitoring = true;
|
||||||
|
returncodes {
|
||||||
|
RBL_DBL_SPAM = "127.0.1.2";
|
||||||
|
RBL_DBL_PHISH = "127.0.1.4";
|
||||||
|
RBL_DBL_MALWARE = "127.0.1.5";
|
||||||
|
RBL_DBL_BOTNET = "127.0.1.6";
|
||||||
|
RBL_DBL_ABUSED_SPAM = "127.0.1.102";
|
||||||
|
RBL_DBL_ABUSED_PHISH = "127.0.1.104";
|
||||||
|
RBL_DBL_ABUSED_MALWARE = "127.0.1.105";
|
||||||
|
RBL_DBL_ABUSED_BOTNET = "127.0.1.106";
|
||||||
|
RBL_DBL_DONT_QUERY_IPS = "127.0.1.255";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spamhaus_dbl_fullurls {
|
||||||
|
ignore_defaults = true;
|
||||||
|
no_ip = true;
|
||||||
|
rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
|
||||||
|
selector = 'urls:get_host'
|
||||||
|
disable_monitoring = true;
|
||||||
|
returncodes {
|
||||||
|
DBLABUSED_SPAM_FULLURLS = "127.0.1.102";
|
||||||
|
DBLABUSED_PHISH_FULLURLS = "127.0.1.104";
|
||||||
|
DBLABUSED_MALWARE_FULLURLS = "127.0.1.105";
|
||||||
|
DBLABUSED_BOTNET_FULLURLS = "127.0.1.106";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spamhaus_zrd {
|
||||||
|
# Add checks on the HELO string also for DQS
|
||||||
|
rbl = "${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net";
|
||||||
|
helo = true;
|
||||||
|
rdns = true;
|
||||||
|
dkim = true;
|
||||||
|
disable_monitoring = true;
|
||||||
|
returncodes {
|
||||||
|
RBL_ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"];
|
||||||
|
RBL_ZRD_FRESH_DOMAIN = [
|
||||||
|
"127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24"
|
||||||
|
];
|
||||||
|
RBL_ZRD_DONT_QUERY_IPS = "127.0.2.255";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"SPAMHAUS_ZEN_URIBL" {
|
||||||
|
enabled = true;
|
||||||
|
rbl = "${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net";
|
||||||
|
resolve_ip = true;
|
||||||
|
checks = ['urls'];
|
||||||
|
replyto = true;
|
||||||
|
emails = true;
|
||||||
|
ipv4 = true;
|
||||||
|
ipv6 = true;
|
||||||
|
emails_domainonly = true;
|
||||||
|
returncodes {
|
||||||
|
URIBL_SBL = "127.0.0.2";
|
||||||
|
URIBL_SBL_CSS = "127.0.0.3";
|
||||||
|
URIBL_XBL = ["127.0.0.4", "127.0.0.5", "127.0.0.6", "127.0.0.7"];
|
||||||
|
URIBL_PBL = ["127.0.0.10", "127.0.0.11"];
|
||||||
|
URIBL_DROP = "127.0.0.9";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SH_EMAIL_DBL {
|
||||||
|
ignore_defaults = true;
|
||||||
|
replyto = true;
|
||||||
|
emails_domainonly = true;
|
||||||
|
disable_monitoring = true;
|
||||||
|
rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
|
||||||
|
returncodes = {
|
||||||
|
SH_EMAIL_DBL = [
|
||||||
|
"127.0.1.2",
|
||||||
|
"127.0.1.4",
|
||||||
|
"127.0.1.5",
|
||||||
|
"127.0.1.6"
|
||||||
|
];
|
||||||
|
SH_EMAIL_DBL_ABUSED = [
|
||||||
|
"127.0.1.102",
|
||||||
|
"127.0.1.104",
|
||||||
|
"127.0.1.105",
|
||||||
|
"127.0.1.106"
|
||||||
|
];
|
||||||
|
SH_EMAIL_DBL_DONT_QUERY_IPS = [ "127.0.1.255" ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SH_EMAIL_ZRD {
|
||||||
|
ignore_defaults = true;
|
||||||
|
replyto = true;
|
||||||
|
emails_domainonly = true;
|
||||||
|
disable_monitoring = true;
|
||||||
|
rbl = "${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net";
|
||||||
|
returncodes = {
|
||||||
|
SH_EMAIL_ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"];
|
||||||
|
SH_EMAIL_ZRD_FRESH_DOMAIN = [
|
||||||
|
"127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24"
|
||||||
|
];
|
||||||
|
SH_EMAIL_ZRD_DONT_QUERY_IPS = [ "127.0.2.255" ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"DBL" {
|
||||||
|
# override the defaults for DBL defined in modules.d/rbl.conf
|
||||||
|
rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
|
||||||
|
disable_monitoring = true;
|
||||||
|
}
|
||||||
|
"ZRD" {
|
||||||
|
ignore_defaults = true;
|
||||||
|
rbl = "${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net";
|
||||||
|
no_ip = true;
|
||||||
|
dkim = true;
|
||||||
|
emails = true;
|
||||||
|
emails_domainonly = true;
|
||||||
|
urls = true;
|
||||||
|
returncodes = {
|
||||||
|
ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"];
|
||||||
|
ZRD_FRESH_DOMAIN = ["127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spamhaus_sbl_url {
|
||||||
|
ignore_defaults = true
|
||||||
|
rbl = "${SPAMHAUS_DQS_KEY}.sbl.dq.spamhaus.net";
|
||||||
|
checks = ['urls'];
|
||||||
|
disable_monitoring = true;
|
||||||
|
returncodes {
|
||||||
|
SPAMHAUS_SBL_URL = "127.0.0.2";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SH_HBL_EMAIL {
|
||||||
|
ignore_defaults = true;
|
||||||
|
rbl = "_email.${SPAMHAUS_DQS_KEY}.hbl.dq.spamhaus.net";
|
||||||
|
emails_domainonly = false;
|
||||||
|
selector = "from('smtp').lower;from('mime').lower";
|
||||||
|
ignore_whitelist = true;
|
||||||
|
checks = ['emails', 'replyto'];
|
||||||
|
hash = "sha1";
|
||||||
|
returncodes = {
|
||||||
|
SH_HBL_EMAIL = [
|
||||||
|
"127.0.3.2"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spamhaus_dqs_hbl {
|
||||||
|
symbol = "HBL_FILE_UNKNOWN";
|
||||||
|
rbl = "_file.${SPAMHAUS_DQS_KEY}.hbl.dq.spamhaus.net.";
|
||||||
|
selector = "attachments('rbase32', 'sha256')";
|
||||||
|
ignore_whitelist = true;
|
||||||
|
ignore_defaults = true;
|
||||||
|
returncodes {
|
||||||
|
SH_HBL_FILE_MALICIOUS = "127.0.3.10";
|
||||||
|
SH_HBL_FILE_SUSPICIOUS = "127.0.3.15";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
rm -rf /etc/rspamd/custom/dqs-rbl.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$@"
|
@ -9,8 +9,9 @@ VERSION=$(git ls-remote --tags -q https://github.com/rspamd/rspamd | sed -n "s/^
|
|||||||
|
|
||||||
IMAGE_NAME=docker-rspamd
|
IMAGE_NAME=docker-rspamd
|
||||||
|
|
||||||
|
zip -r config
|
||||||
|
|
||||||
docker build \
|
docker build \
|
||||||
--no-cache \
|
|
||||||
--build-arg VERSION=${VERSION} \
|
--build-arg VERSION=${VERSION} \
|
||||||
--build-arg COMMIT=$(git ls-remote --tags -q https://github.com/rspamd/rspamd | sed -n "s/^\([[:xdigit:]]\{40\}\)[[:blank:]]refs\/tags\/${VERSION}^{}$/\1/p" | xargs git rev-parse --short) \
|
--build-arg COMMIT=$(git ls-remote --tags -q https://github.com/rspamd/rspamd | sed -n "s/^\([[:xdigit:]]\{40\}\)[[:blank:]]refs\/tags\/${VERSION}^{}$/\1/p" | xargs git rev-parse --short) \
|
||||||
--build-arg BRANCH=$(git rev-parse --abbrev-ref HEAD) \
|
--build-arg BRANCH=$(git rev-parse --abbrev-ref HEAD) \
|
||||||
|
12
Stable2.0/set_worker_password.sh
Normal file
12
Stable2.0/set_worker_password.sh
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
password_file='/etc/rspamd/override.d/worker-controller-password.inc'
|
||||||
|
password_hash=`/usr/bin/rspamadm pw -e -p $1`
|
||||||
|
|
||||||
|
echo 'enable_password = "'$password_hash'";' > $password_file
|
||||||
|
|
||||||
|
if grep -q "$password_hash" "$password_file"; then
|
||||||
|
echo "OK"
|
||||||
|
else
|
||||||
|
echo "ERROR"
|
||||||
|
fi
|
Reference in New Issue
Block a user