Merge branch 'master' into master

This commit is contained in:
Markus Heberling
2019-01-01 14:20:22 +01:00
committed by GitHub
51 changed files with 2305 additions and 645 deletions

View File

@@ -117,7 +117,7 @@ RAND_USER=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 16 | head -n 1)
RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 24 | head -n 1)
echo ${RAND_USER}@mailcow.local:{SHA1}$(echo -n ${RAND_PASS} | sha1sum | awk '{print $1}') > /usr/local/etc/dovecot/dovecot-master.passwd
echo ${RAND_USER}@mailcow.local::5000:5000:::: > /usr/local/etc/dovecot/dovecot-master.userdb
echo ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/sieve.creds
# 401 is user dovecot

View File

@@ -13,3 +13,6 @@ catch_non_zero "/usr/bin/redis-cli -h redis LTRIM DOVECOT_MAILLOG 0 LOG_LINES"
catch_non_zero "/usr/bin/redis-cli -h redis LTRIM SOGO_LOG 0 LOG_LINES"
catch_non_zero "/usr/bin/redis-cli -h redis LTRIM NETFILTER_LOG 0 LOG_LINES"
catch_non_zero "/usr/bin/redis-cli -h redis LTRIM AUTODISCOVER_LOG 0 LOG_LINES"
catch_non_zero "/usr/bin/redis-cli -h redis LTRIM API_LOG 0 LOG_LINES"
catch_non_zero "/usr/bin/redis-cli -h redis LTRIM RL_LOG 0 LOG_LINES"

View File

@@ -1,11 +1,11 @@
FROM php:7.2-fpm-alpine3.8
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
ENV APCU_PECL 5.1.12
ENV APCU_PECL 5.1.16
ENV IMAGICK_PECL 3.4.3
ENV MAILPARSE_PECL 3.0.2
ENV MEMCACHED_PECL 3.0.4
ENV REDIS_PECL 4.1.1
ENV MEMCACHED_PECL 3.1.3
ENV REDIS_PECL 4.2.0
RUN apk add -U --no-cache autoconf \
bash \

View File

@@ -23,12 +23,25 @@ fi
# Check of mysql_upgrade
CONTAINER_ID=
# Todo: Better check if upgrade failed
# This can happen due to a broken sogo_view
[ -s /mysql_upgrade_loop ] && SQL_LOOP_C=$(cat /mysql_upgrade_loop)
CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"mysql-mailcow\")) | .id")
if [[ ! -z ${CONTAINER_ID} ]]; then
if [[ ! -z "${CONTAINER_ID}" ]] && [[ "${CONTAINER_ID}" =~ [^a-zA-Z0-9] ]]; then
SQL_UPGRADE_RETURN=$(curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_upgrade"}' --silent -H 'Content-type: application/json' | jq -r .type)
if [[ ${SQL_UPGRADE_RETURN} == 'warning' ]]; then
echo "MySQL applied an upgrade, restarting PHP-FPM..."
exit 1
if [ -z ${SQL_LOOP_C} ]; then
echo 1 > /mysql_upgrade_loop
echo "MySQL applied an upgrade, restarting PHP-FPM..."
exit 1
else
rm /mysql_upgrade_loop
echo "MySQL was not applied previously, skipping. Restart php-fpm-mailcow to retry or run mysql_upgrade manually."
while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
echo "Waiting for SQL to return..."
sleep 2
done
fi
fi
fi

View File

@@ -85,7 +85,17 @@ query = SELECT GROUP_CONCAT(transport SEPARATOR '') AS transport_maps
AS transport_view;
EOF
cat <<EOF > /opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf
cat <<EOF > /opt/postfix/conf/sql/mysql_transport_maps.cf
user = ${DBUSER}
password = ${DBPASS}
hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
query = SELECT CONCAT('smtp_via_transport_maps:', nexthop) AS transport FROM transports
WHERE active = '1'
AND destination = '%s';
EOF
cat <<EOF > /opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf
user = ${DBUSER}
password = ${DBPASS}
hosts = unix:/var/run/mysqld/mysqld.sock
@@ -98,9 +108,22 @@ query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM relayhosts
SELECT CONCAT('@', alias_domain) FROM alias_domain
)
)
AND active = '1'
AND username != '';
EOF
cat <<EOF > /opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf
user = ${DBUSER}
password = ${DBPASS}
hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM transports
WHERE nexthop = '%s'
AND active = '1'
AND username != ''
LIMIT 1;
EOF
cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_domain_catchall_maps.cf
user = ${DBUSER}
password = ${DBPASS}

View File

@@ -21,6 +21,7 @@ RUN apt-get update && apt-get install -y \
COPY settings.conf /etc/rspamd/settings.conf
COPY docker-entrypoint.sh /docker-entrypoint.sh
COPY ratelimit.lua /usr/share/rspamd/lua/ratelimit.lua
ENTRYPOINT ["/docker-entrypoint.sh"]

View File

@@ -0,0 +1,864 @@
--[[
Copyright (c) 2011-2017, Vsevolod Stakhov <vsevolod@highsecure.ru>
Copyright (c) 2016-2017, Andrew Lewis <nerf@judo.za.org>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
]]--
if confighelp then
return
end
local rspamd_logger = require "rspamd_logger"
local rspamd_util = require "rspamd_util"
local rspamd_lua_utils = require "lua_util"
local lua_redis = require "lua_redis"
local fun = require "fun"
local lua_maps = require "lua_maps"
local lua_util = require "lua_util"
local rspamd_hash = require "rspamd_cryptobox_hash"
local lua_selectors = require "lua_selectors"
local ts = require("tableshape").types
-- A plugin that implements ratelimits using redis
local E = {}
local N = 'ratelimit'
local redis_params
-- Senders that are considered as bounce
local settings = {
bounce_senders = { 'postmaster', 'mailer-daemon', '', 'null', 'fetchmail-daemon', 'mdaemon' },
-- Do not check ratelimits for these recipients
whitelisted_rcpts = { 'postmaster', 'mailer-daemon' },
prefix = 'RL',
ham_factor_rate = 1.01,
spam_factor_rate = 0.99,
ham_factor_burst = 1.02,
spam_factor_burst = 0.98,
max_rate_mult = 5,
max_bucket_mult = 10,
expire = 60 * 60 * 24 * 2, -- 2 days by default
limits = {},
allow_local = false,
}
-- Checks bucket, updating it if needed
-- KEYS[1] - prefix to update, e.g. RL_<triplet>_<seconds>
-- KEYS[2] - current time in milliseconds
-- KEYS[3] - bucket leak rate (messages per millisecond)
-- KEYS[4] - bucket burst
-- KEYS[5] - expire for a bucket
-- return 1 if message should be ratelimited and 0 if not
-- Redis keys used:
-- l - last hit
-- b - current burst
-- dr - current dynamic rate multiplier (*10000)
-- db - current dynamic burst multiplier (*10000)
local bucket_check_script = [[
local last = redis.call('HGET', KEYS[1], 'l')
local now = tonumber(KEYS[2])
local dynr, dynb, leaked = 0, 0, 0
if not last then
-- New bucket
redis.call('HSET', KEYS[1], 'l', KEYS[2])
redis.call('HSET', KEYS[1], 'b', '0')
redis.call('HSET', KEYS[1], 'dr', '10000')
redis.call('HSET', KEYS[1], 'db', '10000')
redis.call('EXPIRE', KEYS[1], KEYS[5])
return {0, '0', '1', '1', '0'}
end
last = tonumber(last)
local burst = tonumber(redis.call('HGET', KEYS[1], 'b'))
-- Perform leak
if burst > 0 then
if last < tonumber(KEYS[2]) then
local rate = tonumber(KEYS[3])
dynr = tonumber(redis.call('HGET', KEYS[1], 'dr')) / 10000.0
if dynr == 0 then dynr = 0.0001 end
rate = rate * dynr
leaked = ((now - last) * rate)
if leaked > burst then leaked = burst end
burst = burst - leaked
redis.call('HINCRBYFLOAT', KEYS[1], 'b', -(leaked))
redis.call('HSET', KEYS[1], 'l', KEYS[2])
end
dynb = tonumber(redis.call('HGET', KEYS[1], 'db')) / 10000.0
if dynb == 0 then dynb = 0.0001 end
if burst > 0 and (burst + 1) > tonumber(KEYS[4]) * dynb then
return {1, tostring(burst), tostring(dynr), tostring(dynb), tostring(leaked)}
end
else
burst = 0
redis.call('HSET', KEYS[1], 'b', '0')
end
return {0, tostring(burst), tostring(dynr), tostring(dynb), tostring(leaked)}
]]
local bucket_check_id
-- Updates a bucket
-- KEYS[1] - prefix to update, e.g. RL_<triplet>_<seconds>
-- KEYS[2] - current time in milliseconds
-- KEYS[3] - dynamic rate multiplier
-- KEYS[4] - dynamic burst multiplier
-- KEYS[5] - max dyn rate (min: 1/x)
-- KEYS[6] - max burst rate (min: 1/x)
-- KEYS[7] - expire for a bucket
-- Redis keys used:
-- l - last hit
-- b - current burst
-- dr - current dynamic rate multiplier
-- db - current dynamic burst multiplier
local bucket_update_script = [[
local last = redis.call('HGET', KEYS[1], 'l')
local now = tonumber(KEYS[2])
if not last then
-- New bucket
redis.call('HSET', KEYS[1], 'l', KEYS[2])
redis.call('HSET', KEYS[1], 'b', '1')
redis.call('HSET', KEYS[1], 'dr', '10000')
redis.call('HSET', KEYS[1], 'db', '10000')
redis.call('EXPIRE', KEYS[1], KEYS[7])
return {1, 1, 1}
end
local dr, db = 1.0, 1.0
if tonumber(KEYS[5]) > 1 then
local rate_mult = tonumber(KEYS[3])
local rate_limit = tonumber(KEYS[5])
dr = tonumber(redis.call('HGET', KEYS[1], 'dr')) / 10000
if rate_mult > 1.0 and dr < rate_limit then
dr = dr * rate_mult
if dr > 0.0001 then
redis.call('HSET', KEYS[1], 'dr', tostring(math.floor(dr * 10000)))
else
redis.call('HSET', KEYS[1], 'dr', '1')
end
elseif rate_mult < 1.0 and dr > (1.0 / rate_limit) then
dr = dr * rate_mult
if dr > 0.0001 then
redis.call('HSET', KEYS[1], 'dr', tostring(math.floor(dr * 10000)))
else
redis.call('HSET', KEYS[1], 'dr', '1')
end
end
end
if tonumber(KEYS[6]) > 1 then
local rate_mult = tonumber(KEYS[4])
local rate_limit = tonumber(KEYS[6])
db = tonumber(redis.call('HGET', KEYS[1], 'db')) / 10000
if rate_mult > 1.0 and db < rate_limit then
db = db * rate_mult
if db > 0.0001 then
redis.call('HSET', KEYS[1], 'db', tostring(math.floor(db * 10000)))
else
redis.call('HSET', KEYS[1], 'db', '1')
end
elseif rate_mult < 1.0 and db > (1.0 / rate_limit) then
db = db * rate_mult
if db > 0.0001 then
redis.call('HSET', KEYS[1], 'db', tostring(math.floor(db * 10000)))
else
redis.call('HSET', KEYS[1], 'db', '1')
end
end
end
local burst = tonumber(redis.call('HGET', KEYS[1], 'b'))
if burst < 0 then burst = 0 end
redis.call('HINCRBYFLOAT', KEYS[1], 'b', 1)
redis.call('HSET', KEYS[1], 'l', KEYS[2])
redis.call('EXPIRE', KEYS[1], KEYS[7])
return {tostring(burst), tostring(dr), tostring(db)}
]]
local bucket_update_id
-- message_func(task, limit_type, prefix, bucket, limit_key)
local message_func = function(_, limit_type, _, _, _)
return string.format('Ratelimit "%s" exceeded', limit_type)
end
local function load_scripts(cfg, ev_base)
bucket_check_id = lua_redis.add_redis_script(bucket_check_script, redis_params)
bucket_update_id = lua_redis.add_redis_script(bucket_update_script, redis_params)
end
local limit_parser
local function parse_string_limit(lim, no_error)
local function parse_time_suffix(s)
if s == 's' then
return 1
elseif s == 'm' then
return 60
elseif s == 'h' then
return 3600
elseif s == 'd' then
return 86400
end
end
local function parse_num_suffix(s)
if s == '' then
return 1
elseif s == 'k' then
return 1000
elseif s == 'm' then
return 1000000
elseif s == 'g' then
return 1000000000
end
end
local lpeg = require "lpeg"
if not limit_parser then
local digit = lpeg.R("09")
limit_parser = {}
limit_parser.integer =
(lpeg.S("+-") ^ -1) *
(digit ^ 1)
limit_parser.fractional =
(lpeg.P(".") ) *
(digit ^ 1)
limit_parser.number =
(limit_parser.integer *
(limit_parser.fractional ^ -1)) +
(lpeg.S("+-") * limit_parser.fractional)
limit_parser.time = lpeg.Cf(lpeg.Cc(1) *
(limit_parser.number / tonumber) *
((lpeg.S("smhd") / parse_time_suffix) ^ -1),
function (acc, val) return acc * val end)
limit_parser.suffixed_number = lpeg.Cf(lpeg.Cc(1) *
(limit_parser.number / tonumber) *
((lpeg.S("kmg") / parse_num_suffix) ^ -1),
function (acc, val) return acc * val end)
limit_parser.limit = lpeg.Ct(limit_parser.suffixed_number *
(lpeg.S(" ") ^ 0) * lpeg.S("/") * (lpeg.S(" ") ^ 0) *
limit_parser.time)
end
local t = lpeg.match(limit_parser.limit, lim)
if t and t[1] and t[2] and t[2] ~= 0 then
return t[2], t[1]
end
if not no_error then
rspamd_logger.errx(rspamd_config, 'bad limit: %s', lim)
end
return nil
end
local function str_to_rate(str)
local divider,divisor = parse_string_limit(str, false)
if not divisor then
rspamd_logger.errx(rspamd_config, 'bad rate string: %s', str)
return nil
end
return divisor / divider
end
local bucket_schema = ts.shape{
burst = ts.number + ts.string / lua_util.dehumanize_number,
rate = ts.number + ts.string / str_to_rate
}
local function parse_limit(name, data)
if type(data) == 'table' then
-- 2 cases here:
-- * old limit in format [burst, rate]
-- * vector of strings in Andrew's string format (removed from 1.8.2)
-- * proper bucket table
if #data == 2 and tonumber(data[1]) and tonumber(data[2]) then
-- Old style ratelimit
rspamd_logger.warnx(rspamd_config, 'old style ratelimit for %s', name)
if tonumber(data[1]) > 0 and tonumber(data[2]) > 0 then
return {
burst = data[1],
rate = data[2]
}
elseif data[1] ~= 0 then
rspamd_logger.warnx(rspamd_config, 'invalid numbers for %s', name)
else
rspamd_logger.infox(rspamd_config, 'disable limit %s, burst is zero', name)
end
return nil
else
local parsed_bucket,err = bucket_schema:transform(data)
if not parsed_bucket or err then
rspamd_logger.errx(rspamd_config, 'cannot parse bucket for %s: %s; original value: %s',
name, err, data)
else
return parsed_bucket
end
end
elseif type(data) == 'string' then
local rep_rate, burst = parse_string_limit(data)
rspamd_logger.warnx(rspamd_config, 'old style rate bucket config detected for %s: %s',
name, data)
if rep_rate and burst then
return {
burst = burst,
rate = burst / rep_rate -- reciprocal
}
end
end
return nil
end
--- Check whether this addr is bounce
local function check_bounce(from)
return fun.any(function(b) return b == from end, settings.bounce_senders)
end
local keywords = {
['ip'] = {
['get_value'] = function(task)
local ip = task:get_ip()
if ip and ip:is_valid() then return tostring(ip) end
return nil
end,
},
['rip'] = {
['get_value'] = function(task)
local ip = task:get_ip()
if ip and ip:is_valid() and not ip:is_local() then return tostring(ip) end
return nil
end,
},
['from'] = {
['get_value'] = function(task)
local from = task:get_from(0)
if ((from or E)[1] or E).addr then
return string.lower(from[1]['addr'])
end
return nil
end,
},
['bounce'] = {
['get_value'] = function(task)
local from = task:get_from(0)
if not ((from or E)[1] or E).user then
return '_'
end
if check_bounce(from[1]['user']) then return '_' else return nil end
end,
},
['asn'] = {
['get_value'] = function(task)
local asn = task:get_mempool():get_variable('asn')
if not asn then
return nil
else
return asn
end
end,
},
['user'] = {
['get_value'] = function(task)
local auser = task:get_user()
if not auser then
return nil
else
return auser
end
end,
},
['to'] = {
['get_value'] = function(task)
return task:get_principal_recipient()
end,
},
['digest'] = {
['get_value'] = function(task)
return task:get_digest()
end,
},
['attachments'] = {
['get_value'] = function(task)
local parts = task:get_parts() or E
local digests = {}
for _,p in ipairs(parts) do
if p:get_filename() then
table.insert(digests, p:get_digest())
end
end
if #digests > 0 then
return table.concat(digests, '')
end
return nil
end,
},
['files'] = {
['get_value'] = function(task)
local parts = task:get_parts() or E
local files = {}
for _,p in ipairs(parts) do
local fname = p:get_filename()
if fname then
table.insert(files, fname)
end
end
if #files > 0 then
return table.concat(files, ':')
end
return nil
end,
},
}
local function gen_rate_key(task, rtype, bucket)
local key_t = {tostring(lua_util.round(100000.0 / bucket.burst))}
local key_keywords = lua_util.str_split(rtype, '_')
local have_user = false
for _, v in ipairs(key_keywords) do
local ret
if keywords[v] and type(keywords[v]['get_value']) == 'function' then
ret = keywords[v]['get_value'](task)
end
if not ret then return nil end
if v == 'user' then have_user = true end
if type(ret) ~= 'string' then ret = tostring(ret) end
table.insert(key_t, ret)
end
if have_user and not task:get_user() then
return nil
end
return table.concat(key_t, ":")
end
local function make_prefix(redis_key, name, bucket)
local hash_len = 24
if hash_len > #redis_key then hash_len = #redis_key end
local hash = settings.prefix ..
string.sub(rspamd_hash.create(redis_key):base32(), 1, hash_len)
-- Fill defaults
if not bucket.spam_factor_rate then
bucket.spam_factor_rate = settings.spam_factor_rate
end
if not bucket.ham_factor_rate then
bucket.ham_factor_rate = settings.ham_factor_rate
end
if not bucket.spam_factor_burst then
bucket.spam_factor_burst = settings.spam_factor_burst
end
if not bucket.ham_factor_burst then
bucket.ham_factor_burst = settings.ham_factor_burst
end
return {
bucket = bucket,
name = name,
hash = hash
}
end
local function limit_to_prefixes(task, k, v, prefixes)
local n = 0
for _,bucket in ipairs(v.buckets) do
if v.selector then
local selectors = lua_selectors.process_selectors(task, v.selector)
if selectors then
local combined = lua_selectors.combine_selectors(task, selectors, ':')
if type(combined) == 'string' then
prefixes[combined] = make_prefix(combined, k, bucket)
n = n + 1
else
fun.each(function(p)
prefixes[p] = make_prefix(p, k, bucket)
n = n + 1
end, combined)
end
end
else
local prefix = gen_rate_key(task, k, bucket)
if prefix then
if type(prefix) == 'string' then
prefixes[prefix] = make_prefix(prefix, k, bucket)
n = n + 1
else
fun.each(function(p)
prefixes[p] = make_prefix(p, k, bucket)
n = n + 1
end, prefix)
end
end
end
end
return n
end
local function ratelimit_cb(task)
if not settings.allow_local and
rspamd_lua_utils.is_rspamc_or_controller(task) then return end
-- Get initial task data
local ip = task:get_from_ip()
if ip and ip:is_valid() and settings.whitelisted_ip then
if settings.whitelisted_ip:get_key(ip) then
-- Do not check whitelisted ip
rspamd_logger.infox(task, 'skip ratelimit for whitelisted IP')
return
end
end
-- Parse all rcpts
local rcpts = task:get_recipients()
local rcpts_user = {}
if rcpts then
fun.each(function(r)
fun.each(function(type) table.insert(rcpts_user, r[type]) end, {'user', 'addr'})
end, rcpts)
if fun.any(function(r) return settings.whitelisted_rcpts:get_key(r) end, rcpts_user) then
rspamd_logger.infox(task, 'skip ratelimit for whitelisted recipient')
return
end
end
-- Get user (authuser)
if settings.whitelisted_user then
local auser = task:get_user()
if settings.whitelisted_user:get_key(auser) then
rspamd_logger.infox(task, 'skip ratelimit for whitelisted user')
return
end
end
-- Now create all ratelimit prefixes
local prefixes = {}
local nprefixes = 0
for k,v in pairs(settings.limits) do
nprefixes = nprefixes + limit_to_prefixes(task, k, v, prefixes)
end
for k, hdl in pairs(settings.custom_keywords or E) do
local ret, redis_key, bd = pcall(hdl, task)
if ret then
local bucket = parse_limit(k, bd)
if bucket then
prefixes[redis_key] = make_prefix(redis_key, k, bucket)
end
nprefixes = nprefixes + 1
else
rspamd_logger.errx(task, 'cannot call handler for %s: %s',
k, redis_key)
end
end
local function gen_check_cb(prefix, bucket, lim_name, lim_key)
return function(err, data)
if err then
rspamd_logger.errx('cannot check limit %s: %s %s', prefix, err, data)
elseif type(data) == 'table' and data[1] then
lua_util.debugm(N, task,
"got reply for limit %s (%s / %s); %s burst, %s:%s dyn, %s leaked",
prefix, bucket.burst, bucket.rate,
data[2], data[3], data[4], data[5])
if data[1] == 1 then
-- set symbol only and do NOT soft reject
if settings.symbol then
task:insert_result(settings.symbol, 0.0,
string.format('%s(%s)', lim_name, lim_key))
rspamd_logger.infox(task,
'set_symbol_only: ratelimit "%s(%s)" exceeded, (%s / %s): %s (%s:%s dyn); redis key: %s',
lim_name, prefix,
bucket.burst, bucket.rate,
data[2], data[3], data[4], lim_key)
return
-- set INFO symbol and soft reject
elseif settings.info_symbol then
task:insert_result(settings.info_symbol, 1.0,
string.format('%s(%s)', lim_name, lim_key))
end
rspamd_logger.infox(task,
'ratelimit "%s(%s)" exceeded, (%s / %s): %s (%s:%s dyn); redis key: %s',
lim_name, prefix,
bucket.burst, bucket.rate,
data[2], data[3], data[4], lim_key)
task:set_pre_result('soft reject',
message_func(task, lim_name, prefix, bucket, lim_key), N)
end
end
end
end
-- Don't do anything if pre-result has been already set
if task:has_pre_result() then return end
if nprefixes > 0 then
-- Save prefixes to the cache to allow update
task:cache_set('ratelimit_prefixes', prefixes)
local now = rspamd_util.get_time()
now = lua_util.round(now * 1000.0) -- Get milliseconds
-- Now call check script for all defined prefixes
for pr,value in pairs(prefixes) do
local bucket = value.bucket
local rate = (bucket.rate) / 1000.0 -- Leak rate in messages/ms
lua_util.debugm(N, task, "check limit %s:%s -> %s (%s/%s)",
value.name, pr, value.hash, bucket.burst, bucket.rate)
lua_redis.exec_redis_script(bucket_check_id,
{key = value.hash, task = task, is_write = true},
gen_check_cb(pr, bucket, value.name, value.hash),
{value.hash, tostring(now), tostring(rate), tostring(bucket.burst),
tostring(settings.expire)})
end
end
end
local function ratelimit_update_cb(task)
if task:has_flag('skip') then return end
if not settings.allow_local and lua_util.is_rspamc_or_controller(task) then return end
local prefixes = task:cache_get('ratelimit_prefixes')
if prefixes then
if task:has_pre_result() then
-- Already rate limited/greylisted, do nothing
lua_util.debugm(N, task, 'pre-action has been set, do not update')
return
end
local verdict = lua_util.get_task_verdict(task)
-- Update each bucket
for k, v in pairs(prefixes) do
local bucket = v.bucket
local function update_bucket_cb(err, data)
if err then
rspamd_logger.errx(task, 'cannot update rate bucket %s: %s',
k, err)
else
lua_util.debugm(N, task,
"updated limit %s:%s -> %s (%s/%s), burst: %s, dyn_rate: %s, dyn_burst: %s",
v.name, k, v.hash,
bucket.burst, bucket.rate,
data[1], data[2], data[3])
end
end
local now = rspamd_util.get_time()
now = lua_util.round(now * 1000.0) -- Get milliseconds
local mult_burst = 1.0
local mult_rate = 1.0
if verdict == 'spam' or verdict == 'junk' then
mult_burst = bucket.spam_factor_burst or 1.0
mult_rate = bucket.spam_factor_rate or 1.0
elseif verdict == 'ham' then
mult_burst = bucket.ham_factor_burst or 1.0
mult_rate = bucket.ham_factor_rate or 1.0
end
lua_redis.exec_redis_script(bucket_update_id,
{key = v.hash, task = task, is_write = true},
update_bucket_cb,
{v.hash, tostring(now), tostring(mult_rate), tostring(mult_burst),
tostring(settings.max_rate_mult), tostring(settings.max_bucket_mult),
tostring(settings.expire)})
end
end
end
local opts = rspamd_config:get_all_opt(N)
if opts then
settings = lua_util.override_defaults(settings, opts)
if opts['limit'] then
rspamd_logger.errx(rspamd_config, 'Legacy ratelimit config format no longer supported')
end
if opts['rates'] and type(opts['rates']) == 'table' then
-- new way of setting limits
fun.each(function(t, lim)
local buckets = {}
if type(lim) == 'table' and lim.bucket then
if lim.bucket[1] then
for _,bucket in ipairs(lim.bucket) do
local b = parse_limit(t, bucket)
if not b then
rspamd_logger.errx(rspamd_config, 'bad ratelimit bucket for %s: "%s"',
t, b)
return
end
table.insert(buckets, b)
end
else
local bucket = parse_limit(t, lim.bucket)
if not bucket then
rspamd_logger.errx(rspamd_config, 'bad ratelimit bucket for %s: "%s"',
t, lim.bucket)
return
end
buckets = {bucket}
end
settings.limits[t] = {
buckets = buckets
}
if lim.selector then
local selector = lua_selectors.parse_selector(rspamd_config, lim.selector)
if not selector then
rspamd_logger.errx(rspamd_config, 'bad ratelimit selector for %s: "%s"',
t, lim.selector)
settings.limits[t] = nil
return
end
settings.limits[t].selector = selector
end
else
rspamd_logger.warnx(rspamd_config, 'old syntax for ratelimits: %s', lim)
buckets = parse_limit(t, lim)
if buckets then
settings.limits[t] = {
buckets = {buckets}
}
end
end
end, opts['rates'])
end
-- Display what's enabled
fun.each(function(s)
rspamd_logger.infox(rspamd_config, 'enabled ratelimit: %s', s)
end, fun.map(function(n,d)
return string.format('%s [%s]', n,
table.concat(fun.totable(fun.map(function(v)
return string.format('%s msgs burst, %s msgs/sec rate',
v.burst, v.rate)
end, d.buckets)), '; ')
)
end, settings.limits))
-- Ret, ret, ret: stupid legacy stuff:
-- If we have a string with commas then load it as as static map
-- otherwise, apply normal logic of Rspamd maps
local wrcpts = opts['whitelisted_rcpts']
if type(wrcpts) == 'string' then
if string.find(wrcpts, ',') then
settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl(
lua_util.rspamd_str_split(wrcpts, ','), 'set', 'Ratelimit whitelisted rcpts')
else
settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl(wrcpts, 'set',
'Ratelimit whitelisted rcpts')
end
elseif type(opts['whitelisted_rcpts']) == 'table' then
settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl(wrcpts, 'set',
'Ratelimit whitelisted rcpts')
else
-- Stupid default...
settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl(
settings.whitelisted_rcpts, 'set', 'Ratelimit whitelisted rcpts')
end
if opts['whitelisted_ip'] then
settings.whitelisted_ip = lua_maps.rspamd_map_add('ratelimit', 'whitelisted_ip', 'radix',
'Ratelimit whitelist ip map')
end
if opts['whitelisted_user'] then
settings.whitelisted_user = lua_maps.rspamd_map_add('ratelimit', 'whitelisted_user', 'set',
'Ratelimit whitelist user map')
end
settings.custom_keywords = {}
if opts['custom_keywords'] then
local ret, res_or_err = pcall(loadfile(opts['custom_keywords']))
if ret then
opts['custom_keywords'] = {}
if type(res_or_err) == 'table' then
for k,hdl in pairs(res_or_err) do
settings['custom_keywords'][k] = hdl
end
elseif type(res_or_err) == 'function' then
settings['custom_keywords']['custom'] = res_or_err
end
else
rspamd_logger.errx(rspamd_config, 'cannot execute %s: %s',
opts['custom_keywords'], res_or_err)
settings['custom_keywords'] = {}
end
end
if opts['message_func'] then
message_func = assert(load(opts['message_func']))()
end
redis_params = lua_redis.parse_redis_server('ratelimit')
if not redis_params then
rspamd_logger.infox(rspamd_config, 'no servers are specified, disabling module')
lua_util.disable_module(N, "redis")
else
local s = {
type = 'prefilter,nostat',
name = 'RATELIMIT_CHECK',
priority = 7,
callback = ratelimit_cb,
flags = 'empty',
}
if settings.symbol then
s.name = settings.symbol
elseif settings.info_symbol then
s.name = settings.info_symbol
end
rspamd_config:register_symbol(s)
rspamd_config:register_symbol {
type = 'idempotent',
name = 'RATELIMIT_UPDATE',
callback = ratelimit_update_cb,
}
end
end
rspamd_config:add_on_load(function(cfg, ev_base, worker)
load_scripts(cfg, ev_base)
end)

View File

@@ -36,7 +36,7 @@ RUN mkdir /usr/share/doc/sogo \
sogo \
sogo-activesync \
&& rm -rf /var/lib/apt/lists/* \
&& echo '* * * * * sogo /usr/sbin/sogo-ealarms-notify 2>/dev/null' > /etc/cron.d/sogo \
&& echo '* * * * * sogo /usr/sbin/sogo-ealarms-notify -p /etc/sogo/sieve.creds 2>/dev/null' > /etc/cron.d/sogo \
&& echo '* * * * * sogo /usr/sbin/sogo-tool expire-sessions 60' >> /etc/cron.d/sogo \
&& echo '0 0 * * * sogo /usr/sbin/sogo-tool update-autoreply -p /etc/sogo/sieve.creds' >> /etc/cron.d/sogo \
&& touch /etc/default/locale
@@ -44,9 +44,6 @@ RUN mkdir /usr/share/doc/sogo \
COPY ./bootstrap-sogo.sh /bootstrap-sogo.sh
COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
COPY supervisord.conf /etc/supervisor/supervisord.conf
COPY theme-blue.js /usr/lib/GNUstep/SOGo/WebServerResources/js/theme-blue.js
COPY theme-blue.css /usr/lib/GNUstep/SOGo/WebServerResources/css/theme-default.css
COPY sogo-full.svg /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg
COPY acl.diff /acl.diff
COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh

View File

@@ -167,43 +167,6 @@ echo ' </dict>
chown sogo:sogo -R /var/lib/sogo/
chmod 600 /var/lib/sogo/GNUstep/Defaults/sogod.plist
# Prevent theme switching
sed -i \
-e 's/eaf5e9/E3F2FD/g' \
-e 's/cbe5c8/BBDEFB/g' \
-e 's/aad6a5/90CAF9/g' \
-e 's/88c781/64B5F6/g' \
-e 's/66b86a/42A5F5/g' \
-e 's/56b04c/2196F3/g' \
-e 's/4da143/1E88E5/g' \
-e 's/388e3c/1976D2/g' \
-e 's/367d2e/1565C0/g' \
-e 's/225e1b/0D47A1/g' \
-e 's/fafafa/82B1FF/g' \
-e 's/69f0ae/448AFF/g' \
-e 's/00e676/2979ff/g' \
-e 's/00c853/2962ff/g' \
/usr/lib/GNUstep/SOGo/WebServerResources/js/Common/Common.app.js \
/usr/lib/GNUstep/SOGo/WebServerResources/js/Common.js
sed -i \
-e 's/default: "900"/default: "700"/g' \
-e 's/default: "500"/default: "700"/g' \
-e 's/"hue-1": "400"/"hue-1": "500"/g' \
-e 's/"hue-1": "A100"/"hue-1": "500"/g' \
-e 's/"hue-2": "800"/"hue-2": "700"/g' \
-e 's/"hue-2": "300"/"hue-2": "700"/g' \
-e 's/"hue-3": "A700"/"hue-3": "A200"/' \
-e 's/default:"900"/default:"700"/g' \
-e 's/default:"500"/default:"700"/g' \
-e 's/"hue-1":"400"/"hue-1":"500"/g' \
-e 's/"hue-1":"A100"/"hue-1":"500"/g' \
-e 's/"hue-2":"800"/"hue-2":"700"/g' \
-e 's/"hue-2":"300"/"hue-2":"700"/g' \
-e 's/"hue-3":"A700"/"hue-3":"A200"/' \
/usr/lib/GNUstep/SOGo/WebServerResources/js/Common/Common.app.js \
/usr/lib/GNUstep/SOGo/WebServerResources/js/Common.js
# Patch ACLs
if [[ ${ACL_ANYONE} == 'allow' ]]; then
#enable any or authenticated targets for ACL
@@ -217,4 +180,7 @@ else
fi
fi
# Copy logo, if any
[[ -f /etc/sogo/sogo-full.svg ]] && cp /etc/sogo/sogo-full.svg /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg
exec gosu sogo /usr/sbin/sogod

View File

@@ -1,160 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
width="640px"
height="350px"
viewBox="78.712 58.488 640 350"
style="enable-background:new 78.712 58.488 640 350;"
xml:space="preserve"
inkscape:version="0.91 r13725"
sodipodi:docname="sogo-full.svg"><metadata
id="metadata9"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs7" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1721"
inkscape:window-height="1177"
id="namedview5"
showgrid="false"
inkscape:zoom="0.8396893"
inkscape:cx="360.23913"
inkscape:cy="334.02085"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" /><path
style="fill:#1976d2;fill-opacity:0.71428573"
d="M648.541,145.679c-9.947,0-17.009-7.278-17.009-17.048c0-9.777,7.062-17.057,17.009-17.057 c10.024,0,17.086,7.279,17.086,17.057C665.627,138.401,658.565,145.679,648.541,145.679z M648.511,94.893 c-19.693,0-33.679,14.4-33.679,33.738c0,19.33,13.985,33.729,33.679,33.729c19.822,0,33.808-14.4,33.808-33.729 C682.318,109.293,668.333,94.893,648.511,94.893z M648.482,179.843c-29.889,0-51.123-21.868-51.123-51.212 c0-29.353,21.234-51.209,51.123-51.209c30.082,0,51.307,21.856,51.307,51.209C699.789,157.975,678.564,179.843,648.482,179.843z M648.442,58.488c-40.929,0-69.995,29.946-69.995,70.143c0,40.189,29.066,70.125,69.995,70.125c41.194,0,70.27-29.937,70.27-70.125 C718.712,88.434,689.637,58.488,648.442,58.488z M158.166,183.902l-21.018-5.008c-19.131-4.396-28.849-9.413-28.849-23.21 c0-15.684,15.99-21.965,30.419-21.965c14.667,0,25.382,7.329,31.693,18.737c0.02,0.048,0.051,0.097,0.09,0.157 c0.127,0.247,0.276,0.484,0.403,0.731l0.03-0.02c1.985,3.002,5.323,5.008,8.919,5.008c6.122,0,10.558-4.425,10.558-10.547 c0-2.341-0.504-4.82-1.601-6.688c-10.764-18.302-28.513-26.192-48.838-26.192c-27.594,0-54.262,13.797-54.262,44.218 c0,27.921,27.605,36.079,37.64,38.578l20.069,4.71c15.368,3.763,27.912,8.791,27.912,23.517c0,16.938-17.561,23.943-34.499,23.943 c-17.245,0-30.015-9.37-38.814-22.37h-0.01c-1.956-3-4.988-4.328-8.702-4.328c-5.984,0-10.805,5.185-10.587,11.162 c0.098,2.438,0.909,4.637,2.153,6.405c13.787,20.633,33.728,28.41,55.96,28.41c28.543,0,57.085-13.143,57.085-45.132 C193.918,203.325,178.551,188.613,158.166,183.902z M298.479,250.312c-33.866,0-55.199-25.403-55.199-58.331 c0-32.939,21.333-58.343,55.199-58.343c34.192,0,55.516,25.403,55.516,58.343C353.996,224.91,332.672,250.312,298.479,250.312z M298.479,114.823c-45.471,0-77.777,32.93-77.777,77.158c0,44.217,32.306,77.146,77.777,77.146 c45.786,0,78.093-32.929,78.093-77.146C376.572,147.753,344.266,114.823,298.479,114.823z M518.715,234.312 c-0.771,0.74-1.549,1.472-2.399,2.175c-1.106,1.014-2.391,2.112-3.854,3.208c-8.829,6.391-19.979,10.094-33.017,10.094 c-33.876,0-55.198-25.402-55.198-58.332c0-32.939,21.322-58.342,55.198-58.342c34.183,0,55.506,25.403,55.506,58.342 C534.951,208.653,529.135,223.774,518.715,234.312z M468.097,317.938c2.528,0,5.146-0.168,7.863-0.504 c5.018-0.631,9.588-0.909,13.729-0.909c19.24,0.109,29.036,5.7,34.943,12.158c5.895,6.499,8.168,15.311,8.158,22.796 c0.01,3.586-0.555,6.795-1.177,8.721c-2.944,8.93-8.888,15.002-17.996,19.576c-9.035,4.484-21.095,6.777-33.707,6.757 c-4.514,0-9.105-0.288-13.639-0.831c-8.573-0.987-19.911-4.671-28.13-11.093c-4.138-3.199-6.458-6.991-8.858-11.485 c-2.379-4.514-2.783-9.748-2.783-16.442v-0.742c0-12.346,4.84-20.544,11.051-26.5c3.07-2.904,5.69-5.064,7.99-6.438 c0.366-0.218,0.438-0.416,0.755-0.593C452.39,316.014,459.684,317.968,468.097,317.938z M479.445,114.301 c-45.471,0-77.786,32.929-77.786,77.157c0,29.887,14.765,54.598,38.378,67.489c-0.314,0.314-0.621,0.641-0.916,0.966 c-6.104,6.687-9.226,15.25-9.236,23.913c-0.008,3.821,0.624,7.741,1.977,11.494c-3.062,1.956-6.717,4.634-10.46,8.147 c-9.026,8.408-18.734,22.541-19.021,42.097c-0.01,0.454-0.01,0.829-0.01,1.118c-0.01,10.071,2.379,19.157,6.459,26.774 c6.133,11.466,15.683,19.445,25.539,24.77c9.917,5.334,20.257,8.166,29.273,9.274c5.373,0.643,10.826,0.988,16.268,0.988 c15.151-0.02,30.261-2.578,43.409-9.019c13.085-6.34,24.333-17.253,29.192-32.562c1.443-4.553,2.212-9.719,2.231-15.428 c-0.02-11.595-3.349-25.759-13.767-37.452c-10.421-11.734-27.654-19.566-51.288-19.459c-5.138,0-10.606,0.356-16.426,1.078 c-1.877,0.227-3.596,0.334-5.166,0.334c-7.239-0.048-10.872-2.053-13.036-4.098c-2.133-2.084-3.2-4.839-3.229-8.058 c-0.01-3.28,1.284-6.727,3.467-9.078c2.231-2.332,5.008-3.91,9.846-3.97c0.436,0,0.9,0.01,1.374,0.05 c3.101,0.216,6.112,0.325,9.037,0.325c24.188,0.047,42.38-7.448,54.756-17.759c12.415-10.312,18.971-22.854,22.071-32.76l-0.04-0.01 c3.37-8.899,5.197-18.715,5.197-29.166C557.539,147.229,525.234,114.301,479.445,114.301z"
id="path3" /><g
id="g3"
transform="matrix(0.69327133,0,0,0.69327133,-230.59227,-153.05511)"><g
id="g5"><g
id="g7"><path
d="m 748.616,546.705 c -6.272,4.929 9.576,36.937 20.52,33.516 10.944,-3.42 -10.945,-41.04 -20.52,-33.516 z"
id="path19"
inkscape:connector-curvature="0"
style="fill:#acef29" /></g></g><g
id="g37"><g
id="g39"><g
id="g41" /></g><g
id="g45"><g
id="g47"><g
id="g49" /></g></g></g><g
id="g57"><g
id="g59"><g
id="g61" /></g><g
id="g65"><g
id="g67"><g
id="g69" /></g></g></g><g
id="g73"
style="opacity:0.38999999"><g
id="g75" /></g><g
id="g81" /><g
id="g85" /><g
id="g99"><polyline
points="690.928,453.92 678.401,490.532 689.894,539.76 710.135,559.116 "
id="polyline101"
style="fill:#3d5263" /><g
id="g103"><g
id="g105"><polyline
points="665.082,423.023 677.433,450.19 693.064,457.2 715.139,427.604 "
id="polyline107"
style="fill:#fef3df" /><g
id="g109"><path
d="m 705.288,438.868 c 0,0 -17.599,-18.89 -38.165,-13.309 0,0 4.277,25.767 36.096,32.372 l 2.164,6.413 c 0,0 -49.958,-5.925 -46.456,-49.964 0,0 36.728,-16.138 55.372,9.881"
id="path111"
inkscape:connector-curvature="0"
style="fill:#b58765" /><polyline
points="855.735,417.576 844.957,445.404 829.753,453.295 806.023,425.008 "
id="polyline113"
style="fill:#fef3df" /><path
d="m 816.503,435.691 c 0,0 16.491,-19.864 37.34,-15.466 0,0 -2.797,25.969 -34.187,34.38 l -1.796,6.527 c 0,0 49.537,-8.768 43.528,-52.535 0,0 -37.588,-14.015 -54.719,13.026"
id="path115"
inkscape:connector-curvature="0"
style="fill:#b58765" /></g></g><path
d="m 721.173,570.346 42.418,-1.212 15.59845,-160.5887 c -42.319,1.209 -92.18245,30.1357 -91.14645,66.4047 0.031,1.102 0.125,2.111 0.182,3.163 0.181,2.98 0.504,5.741 0.89,8.424 1.602,11.197 4.722,20.488 7.355,32.71 1.27,5.906 4.299,17.614 4.299,17.614 0.052,0.308 0.143,0.606 0.196,0.913 2.281,12.491 9.666,24.028 20.208,32.572 z"
id="path117"
inkscape:connector-curvature="0"
style="fill:#b58765"
sodipodi:nodetypes="cccccccccc" /><path
d="m 758.532,407.21 4.626,161.937 42.418,-1.212 c 10.038,-9.132 16.75,-21.072 18.317,-33.672 0.035,-0.31 0.107,-0.613 0.141,-0.923 0,0 2.354,-11.862 3.287,-17.831 1.932,-12.352 4.518,-21.807 5.478,-33.075 0.23,-2.702 0.393,-5.477 0.4,-8.463 0.002,-1.053 0.036,-2.066 0.005,-3.168 -1.037,-36.269 -32.35,-64.802 -74.672,-63.593 z"
id="path119"
inkscape:connector-curvature="0"
style="fill:#b58765" /><g
id="g121"><g
id="g123"><path
d="m 822.293,541.988 c 0.613,21.473 -25.496,39.648 -58.32,40.586 -32.83,0.938 -59.932,-15.717 -60.546,-37.19 -0.614,-21.477 25.494,-39.648 58.324,-40.586 32.825,-0.938 59.929,15.713 60.542,37.19 z"
id="path125"
inkscape:connector-curvature="0"
style="fill:#fef3df" /></g></g><g
id="g127"><g
id="g129"><g
id="g131"><path
d="m 735.761,538.45 c 0.135,4.712 -3.578,8.644 -8.294,8.778 -4.708,0.134 -8.641,-3.579 -8.776,-8.291 -0.135,-4.718 3.58,-8.644 8.288,-8.779 4.717,-0.134 8.647,3.573 8.782,8.292 z"
id="path133"
inkscape:connector-curvature="0"
style="fill:#5a3620" /></g></g><g
id="g135"><g
id="g137"><path
d="m 806.891,536.418 c 0.135,4.712 -3.575,8.644 -8.291,8.778 -4.714,0.135 -8.646,-3.579 -8.781,-8.291 -0.135,-4.718 3.579,-8.644 8.293,-8.779 4.716,-0.134 8.644,3.573 8.779,8.292 z"
id="path139"
inkscape:connector-curvature="0"
style="fill:#5a3620" /></g></g></g><g
id="g141"><path
d="m 831.225,475.208 c 0.201,-2.368 0.344,-4.799 0.352,-7.411 0,-0.924 0.031,-1.81 0.003,-2.778 -0.883,-30.924 -26.904,-55.418 -62.478,-55.716 l -5.561,0.163 -0.005,0 c -0.005,0.014 -19.18666,70.69971 61.71134,107.73071 0.624,-3.294 9.87079,-9.74237 10.28979,-12.41137 1.694,-10.822 -5.15313,-19.70834 -4.31213,-29.57734 z"
id="path143"
inkscape:connector-curvature="0"
style="fill:#87654a"
sodipodi:nodetypes="ccccccccc" /></g><g
id="g145"><g
id="g147"><g
id="g149"><g
id="g151"><path
d="m 807.344,471.221 c 0.151,5.28 -4.011,9.684 -9.294,9.835 -5.279,0.151 -9.686,-4.008 -9.837,-9.288 -0.151,-5.285 4.011,-9.687 9.291,-9.838 5.282,-0.151 9.689,4.006 9.84,9.291 z"
id="path153"
inkscape:connector-curvature="0"
style="fill:#5a3620" /></g></g></g><g
id="g155"><g
id="g157"><g
id="g159"><path
d="m 737.68,473.211 c 0.151,5.28 -4.01,9.684 -9.289,9.835 -5.284,0.151 -9.685,-4.008 -9.835,-9.288 -0.151,-5.285 4.005,-9.687 9.289,-9.837 5.279,-0.152 9.684,4.005 9.835,9.29 z"
id="path161"
inkscape:connector-curvature="0"
style="fill:#5a3620" /></g></g></g><g
id="g163"><g
id="g165"><path
d="m 735.112,470.41 c 0.055,1.939 -1.47,3.555 -3.41,3.61 -1.939,0.055 -3.558,-1.47 -3.613,-3.41 -0.055,-1.935 1.474,-3.552 3.413,-3.607 1.94,-0.055 3.555,1.472 3.61,3.407 z"
id="path167"
inkscape:connector-curvature="0"
style="fill:#ffffff" /></g></g><g
id="g169"><g
id="g171"><path
d="m 804.125,468.439 c 0.055,1.939 -1.472,3.555 -3.409,3.61 -1.94,0.055 -3.556,-1.47 -3.611,-3.41 -0.055,-1.935 1.471,-3.552 3.411,-3.607 1.937,-0.056 3.554,1.471 3.609,3.407 z"
id="path173"
inkscape:connector-curvature="0"
style="fill:#ffffff" /></g></g></g></g><path
d="m 761.738,405.962 c -50.342,1.438 -89.989,43.417 -88.55,93.759 1.438,50.342 43.417,89.987 93.758,88.549 50.343,-1.438 89.988,-43.415 88.55,-93.757 -1.438,-50.343 -43.415,-89.989 -93.758,-88.551 z m -4.396,163.807 c -40.192,1.148 -73.725,-31.172 -74.896,-72.19 -1.172,-41.017 30.461,-75.2 70.653,-76.348 40.191,-1.148 73.723,31.172 74.895,72.19 1.171,41.018 -30.462,75.2 -70.652,76.348 z"
id="path179"
inkscape:connector-curvature="0"
style="fill:#f1f2f2" /><g
id="g181" /></g></g></svg>

Before

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

View File

@@ -1,103 +0,0 @@
/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
(function() {
'use strict';
angular.module('SOGo.Common')
.config(configure)
/**
* @ngInject
*/
configure.$inject = ['$mdThemingProvider'];
function configure($mdThemingProvider) {
// Overwrite values to prevent flipping colors on login screen
$mdThemingProvider.definePalette('mailcow-blue', {
'50': 'E3F2FD',
'100': 'BBDEFB',
'200': '90CAF9',
'300': '64B5F6',
'400': '42A5F5',
'500': '2196F3',
'600': '1E88E5',
'700': '1976D2',
'800': '1565C0',
'900': '0D47A1',
'1000': '0D47A1',
'A100': '82B1FF',
'A200': '448AFF',
'A400': '2979ff',
'A700': '2962ff',
'contrastDefaultColor': 'dark',
'contrastLightColors': ['700', '800', '900'],
'contrastDarkColors': undefined
});
$mdThemingProvider.definePalette('sogo-green', {
'50': 'E3F2FD',
'100': 'BBDEFB',
'200': '90CAF9',
'300': '64B5F6',
'400': '42A5F5',
'500': '2196F3',
'600': '1E88E5',
'700': '1976D2',
'800': '1565C0',
'900': '0D47A1',
'1000': '0D47A1',
'A100': '82B1FF',
'A200': '448AFF',
'A400': '2979ff',
'A700': '2962ff',
'contrastDefaultColor': 'dark',
'contrastLightColors': ['700', '800', '900'],
'contrastDarkColors': undefined
});
$mdThemingProvider.definePalette('default', {
'50': 'E3F2FD',
'100': 'BBDEFB',
'200': '90CAF9',
'300': '64B5F6',
'400': '42A5F5',
'500': '2196F3',
'600': '1E88E5',
'700': '1976D2',
'800': '1565C0',
'900': '0D47A1',
'1000': '0D47A1',
'A100': '82B1FF',
'A200': '448AFF',
'A400': '2979ff',
'A700': '2962ff',
'contrastDefaultColor': 'dark',
'contrastLightColors': ['700', '800', '900'],
'contrastDarkColors': undefined
});
$mdThemingProvider.theme('default')
.primaryPalette('mailcow-blue', {
'default': '700', // top toolbar
'hue-1': '500',
'hue-2': '700', // sidebar toolbar
'hue-3': 'A200'
})
.accentPalette('mailcow-blue', {
'default': '800', // fab buttons
'hue-1': '50', // center list toolbar
'hue-2': '500',
'hue-3': 'A700'
})
.backgroundPalette('grey', {
'default': '50', // center list background
'hue-1': '100',
'hue-2': '200',
'hue-3': '300'
});
$mdThemingProvider.setDefaultTheme('default');
$mdThemingProvider.generateThemesOnDemand(false);
$mdThemingProvider.alwaysWatchTheme(true);
}
})();

View File

@@ -322,6 +322,65 @@ phpfpm_checks() {
return 1
}
ratelimit_checks() {
err_count=0
diff_c=0
THRESHOLD=1
RL_LOG_STATUS=$(redis-cli -h redis LRANGE RL_LOG 0 0 | jq .qid)
# Reduce error count by 2 after restarting an unhealthy container
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
while [ ${err_count} -lt ${THRESHOLD} ]; do
err_c_cur=${err_count}
RL_LOG_STATUS_PREV=${RL_LOG_STATUS}
RL_LOG_STATUS=$(redis-cli -h redis LRANGE RL_LOG 0 0 | jq .qid)
if [[ ${RL_LOG_STATUS_PREV} != ${RL_LOG_STATUS} ]]; then
err_count=$(( ${err_count} + 1 ))
fi
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
progress "Ratelimit" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
if [[ $? == 10 ]]; then
diff_c=0
sleep 1
else
diff_c=0
sleep $(( ( RANDOM % 30 ) + 10 ))
fi
done
return 1
}
ipv6nat_checks() {
err_count=0
diff_c=0
THRESHOLD=1
# Reduce error count by 2 after restarting an unhealthy container
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
while [ ${err_count} -lt ${THRESHOLD} ]; do
err_c_cur=${err_count}
IPV6NAT_CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"ipv6nat\")) | .id")
if [[ ! -z ${IPV6NAT_CONTAINER_ID} ]]; then
LATEST_STARTED="$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], StartedAt: .State.StartedAt}" | jq -rc "select( .name | tostring | contains(\"ipv6nat\") | not)" | jq -rc .StartedAt | xargs -n1 date +%s -d | sort | tail -n1)"
LATEST_IPV6NAT="$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], StartedAt: .State.StartedAt}" | jq -rc "select( .name | tostring | contains(\"ipv6nat\"))" | jq -rc .StartedAt | xargs -n1 date +%s -d | sort | tail -n1)"
DIFFERENCE_START_TIME=$(expr ${LATEST_IPV6NAT} - ${LATEST_STARTED} 2>/dev/null)
if [[ "${DIFFERENCE_START_TIME}" -lt 30 ]]; then
err_count=$(( ${err_count} + 1 ))
fi
fi
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
progress "IPv6 NAT" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
if [[ $? == 10 ]]; then
diff_c=0
sleep 1
else
diff_c=0
sleep 3600
fi
done
return 1
}
rspamd_checks() {
err_count=0
diff_c=0
@@ -448,6 +507,26 @@ done
) &
BACKGROUND_TASKS+=($!)
(
while true; do
if ! ratelimit_checks; then
log_msg "Ratelimit hit error limit"
echo ratelimit > /tmp/com_pipe
fi
done
) &
BACKGROUND_TASKS+=($!)
(
while true; do
if ! ipv6nat_checks; then
log_msg "IPv6 NAT warning: ipv6nat container was not started at least 30s after siblings (not an error)"
echo ipv6nat > /tmp/com_pipe
fi
done
) &
BACKGROUND_TASKS+=($!)
# Monitor watchdog agents, stop script when agents fails and wait for respawn by Docker (restart:always:n)
(
while true; do
@@ -482,7 +561,10 @@ while true; do
CONTAINER_ID=
HAS_INITDB=
read com_pipe_answer </tmp/com_pipe
if [[ ${com_pipe_answer} =~ .+-mailcow ]]; then
if [[ ${com_pipe_answer} == "ratelimit" ]]; then
log_msg "At least one ratelimit was applied"
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${com_pipe_answer}" "No further information available."
elif [[ ${com_pipe_answer} =~ .+-mailcow ]] || [[ ${com_pipe_answer} == "ipv6nat" ]]; then
kill -STOP ${BACKGROUND_TASKS[*]}
sleep 3
CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${com_pipe_answer}\")) | .id")
@@ -499,9 +581,11 @@ while true; do
else
log_msg "Sending restart command to ${CONTAINER_ID}..."
curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/restart
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${com_pipe_answer}"
if [[ ${com_pipe_answer} != "ipv6nat" ]]; then
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${com_pipe_answer}"
fi
log_msg "Wait for restarted container to settle and continue watching..."
sleep 30
sleep 35
fi
fi
kill -CONT ${BACKGROUND_TASKS[*]}