Merge branch 'master' into master

master
Markus Heberling 2019-01-01 14:20:22 +01:00 committed by GitHub
commit 9750ec5bec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 2305 additions and 645 deletions

2
.gitignore vendored
View File

@ -1,10 +1,12 @@
rebuild-images.sh
data/conf/sogo/sieve.creds
data/conf/dovecot/dovecot-master.passwd
data/conf/dovecot/dovecot-master.userdb
mailcow.conf
mailcow.conf_backup
data/conf/nginx/*.active
data/conf/postfix/sql
data/conf/postfix/allow_mailcow_local.regexp
data/conf/dovecot/sql
data/conf/nextcloud-*.bak
data/web/inc/vars.local.inc.php

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[*]}

View File

@ -24,7 +24,8 @@ server {
add_header X-Robots-Tag none;
add_header X-Download-Options noopen;
add_header X-Permitted-Cross-Domain-Policies none;
add_header X-Frame-Options "SAMEORIGIN";
#add_header X-Frame-Options "SAMEORIGIN";
add_header Referrer-Policy "no-referrer";
server_name NC_SUBD;

View File

@ -58,6 +58,11 @@ passdb {
result_failure = continue
result_internalfail = continue
}
passdb {
driver = passwd-file
args = /usr/local/etc/dovecot/dovecot-master.passwd
skip = authenticated
}
# Set doveadm_password=your-secret-password in data/conf/dovecot/extra.conf (create if missing)
service doveadm {
inet_listener {
@ -257,9 +262,14 @@ service lmtp {
listen = *,[::]
ssl_cert = </etc/ssl/mail/cert.pem
ssl_key = </etc/ssl/mail/key.pem
userdb {
driver = passwd-file
args = /usr/local/etc/dovecot/dovecot-master.userdb
}
userdb {
args = /usr/local/etc/dovecot/sql/dovecot-dict-sql-userdb.conf
driver = sql
skip = found
}
protocol imap {
imap_metadata = yes
@ -347,4 +357,4 @@ auth_cache_negative_ttl = 0
auth_cache_ttl = 30 s
auth_cache_size = 2 M
!include_try /usr/local/etc/dovecot/extra.conf
default_client_limit = 10240
default_client_limit = 10400

View File

@ -0,0 +1 @@
/^(.+)@mailcow.local/ OK

View File

@ -43,7 +43,9 @@ postscreen_pipelining_enable = no
proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf,
proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf,
proxy:mysql:/opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf,
proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf,
proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf,
proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf,
proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf,
proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf,
proxy:mysql:/opt/postfix/conf/sql/mysql_sender_bcc_maps.cf,
proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf,
@ -126,9 +128,11 @@ mydestination = localhost.localdomain, localhost
smtp_address_preference = ipv4
smtp_sender_dependent_authentication = yes
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf
smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf
smtp_sasl_security_options =
smtp_sasl_mechanism_filter = plain, login
smtp_tls_policy_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf
smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre
mail_name = Postcow
transport_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf
smtp_sasl_auth_soft_bounce = no

View File

@ -13,6 +13,7 @@ submission inet n - n - - smtpd
588 inet n - n - - smtpd
-o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
-o smtpd_tls_auth_only=no
-o smtpd_sender_restrictions=check_sasl_access,regexp:/opt/postfix/conf/allow_mailcow_local.regexp,reject_authenticated_sender_login_mismatch,permit_mynetworks,permit_sasl_authenticated,reject_unlisted_sender,reject_unknown_sender_domain
590 inet n - n - - smtpd
-o smtpd_client_restrictions=permit_mynetworks,reject
-o smtpd_tls_auth_only=no
@ -22,6 +23,8 @@ smtp_enforced_tls unix - - n - - smtp
-o smtp_tls_security_level=encrypt
-o syslog_name=enforced-tls-smtp
-o smtp_delivery_status_filter=pcre:/opt/postfix/conf/smtp_dsn_filter
smtp_via_transport_maps unix - - n - - smtp
-o smtp_sasl_password_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf
tlsproxy unix - - n - 0 tlsproxy
dnsblog unix - - n - 0 dnsblog

View File

@ -1,10 +1,26 @@
rules {
QUARANTINE {
backend = "http";
url = "http://nginx:9081/pipe.php";
selector = "is_reject";
formatter = "default";
meta_headers = true;
}
QUARANTINE {
backend = "http";
url = "http://nginx:9081/pipe.php";
selector = "is_reject";
formatter = "default";
meta_headers = true;
}
RLINFO {
backend = "http";
url = "http://nginx:9081/pipe_rl.php";
selector = "ratelimited";
formatter = "json";
}
}
custom_select {
ratelimited = <<EOD
return function(task)
local ratelimited = task:get_symbol("RATELIMITED")
if ratelimited then
return true
end
return
end
EOD;
}

View File

@ -0,0 +1,38 @@
<?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();
$redis->connect('redis-mailcow', 6379);
$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;

View File

@ -1,11 +1,12 @@
rates {
# Format: "1 / 1h" or "20 / 1m" etc. - global ratelimits are disabled by default
to = "100 / 1s";
to_ip = "100 / 1s";
to_ip_from = "100 / 1s";
to = "45 / 1m";
to_ip = "360 / 1m";
to_ip_from = "180 / 1m";
bounce_to = "100 / 1s";
bounce_to_ip = "100 / 1s";
}
whitelisted_rcpts = "postmaster,mailer-daemon";
max_rcpt = 5;
custom_keywords = "/etc/rspamd/lua/ratelimit.lua";
info_symbol = "RATELIMITED";

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY st0 "fill:#50BD37;">
]>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" 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">
<path style="&st0;" 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"/>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -11,10 +11,10 @@
SOGoDraftsFolderName = "Drafts";
SOGoJunkFolderName= "Junk";
SOGoMailDomain = "sogo.local";
SOGoEnableEMailAlarms = NO;
SOGoEnableEMailAlarms = YES;
SOGoFoldersSendEMailNotifications = YES;
SOGoForwardEnabled = YES;
SOGoUIAdditionalJSFiles = (js/theme-blue.js, js/custom-sogo.js);
SOGoUIAdditionalJSFiles = (js/custom-sogo.js);
// Multi-domain setup
// Domains are isolated, you can define visibility options here.

View File

@ -10,6 +10,7 @@ $tfa_data = get_tfa();
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#tab-access" aria-controls="tab-access" role="tab" data-toggle="tab"><?=$lang['admin']['access'];?></a></li>
<li role="presentation"><a href="#tab-config" aria-controls="tab-config" role="tab" data-toggle="tab"><?=$lang['admin']['configuration'];?></a></li>
<li role="presentation"><a href="#tab-routing" aria-controls="tab-config" role="tab" data-toggle="tab"><?=$lang['admin']['routing'];?></a></li>
<li role="presentation"><a href="#tab-sys-mails" aria-controls="tab-sys-mails" role="tab" data-toggle="tab"><?=$lang['admin']['sys_mails'];?></a></li>
<li role="presentation"><a href="#tab-mailq" aria-controls="tab-mailq" role="tab" data-toggle="tab"><?=$lang['admin']['queue_manager'];?></a></li>
</ul>
@ -178,6 +179,101 @@ $tfa_data = get_tfa();
</div>
</div>
<div role="tabpanel" class="tab-pane" id="tab-routing">
<div class="panel panel-default">
<div class="panel-heading"><?=$lang['admin']['relayhosts'];?></div>
<div class="panel-body">
<p style="margin-bottom:40px"><?=$lang['admin']['relayhosts_hint'];?></p>
<div class="table-responsive">
<table class="table table-striped table-condensed" id="relayhoststable"></table>
</div>
<div class="mass-actions-admin">
<div class="btn-group btn-group-sm">
<button type="button" id="toggle_multi_select_all" data-id="rlyhosts" class="btn btn-default"><?=$lang['mailbox']['toggle_all'];?></button>
<a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a data-action="edit_selected" data-id="rlyhosts" data-api-url='edit/relayhost' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li>
<li><a data-action="edit_selected" data-id="rlyhosts" data-api-url='edit/relayhost' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li>
<li role="separator" class="divider"></li>
<li><a data-action="delete_selected" data-id="rlyhosts" data-api-url='delete/relayhost' href="#"><?=$lang['admin']['remove'];?></a></li>
</ul>
</div>
</div>
<legend><?=$lang['admin']['add_relayhost'];?></legend>
<p class="help-block"><?=$lang['admin']['add_relayhost_hint'];?></p>
<div class="row">
<div class="col-md-6">
<form class="form" data-id="rlyhost" role="form" method="post">
<div class="form-group">
<label for="hostname"><?=$lang['admin']['host'];?></label>
<input class="form-control input-sm" name="hostname" required>
</div>
<div class="form-group">
<label for="username"><?=$lang['admin']['username'];?></label>
<input class="form-control input-sm" name="username">
</div>
<div class="form-group">
<label for="password"><?=$lang['admin']['password'];?></label>
<input class="form-control input-sm" name="password">
</div>
<button class="btn btn-default" data-action="add_item" data-id="rlyhost" data-api-url='add/relayhost' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add'];?></button>
</form>
</div>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><?=$lang['admin']['transport_maps'];?></div>
<div class="panel-body">
<p style="margin-bottom:40px"><?=$lang['admin']['transports_hint'];?></p>
<div class="table-responsive">
<table class="table table-striped table-condensed" id="transportstable"></table>
</div>
<div class="mass-actions-admin">
<div class="btn-group btn-group-sm">
<button type="button" id="toggle_multi_select_all" data-id="transports" class="btn btn-default"><?=$lang['mailbox']['toggle_all'];?></button>
<a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a data-action="edit_selected" data-id="transports" data-api-url='edit/transport' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li>
<li><a data-action="edit_selected" data-id="transports" data-api-url='edit/transport' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li>
<li role="separator" class="divider"></li>
<li><a data-action="delete_selected" data-id="transports" data-api-url='delete/transport' href="#"><?=$lang['admin']['remove'];?></a></li>
</ul>
</div>
</div>
<legend><?=$lang['admin']['add_transport'];?></legend>
<p class="help-block"><?=$lang['admin']['add_transports_hint'];?></p>
<div class="row">
<div class="col-md-6">
<form class="form" data-id="transport" role="form" method="post">
<div class="form-group">
<label for="destination"><?=$lang['admin']['destination'];?></label>
<input class="form-control input-sm" name="destination" placeholder='example.org, .example.org, *, box@example.org' required>
</div>
<div class="form-group">
<label for="nexthop"><?=$lang['admin']['nexthop'];?></label>
<input class="form-control input-sm" name="nexthop" placeholder='host:25, host, [host]:25, [0.0.0.0]:25' required>
</div>
<div class="form-group">
<label for="username"><?=$lang['admin']['username'];?></label>
<input class="form-control input-sm" name="username">
</div>
<div class="form-group">
<label for="password"><?=$lang['admin']['password'];?></label>
<input class="form-control" name="password">
</div>
<p class="help-block"><?=$lang['admin']['credentials_transport_warning'];?></p>
<button class="btn btn-default" data-action="add_item" data-id="transport" data-api-url='add/transport' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add'];?></button>
</form>
</div>
</div>
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="tab-config">
<div class="row">
<div id="sidebar-admin" class="col-sm-2 hidden-xs">
@ -185,7 +281,6 @@ $tfa_data = get_tfa();
<a href="#dkim" class="list-group-item"><?=$lang['admin']['dkim_keys'];?></a>
<a href="#fwdhosts" class="list-group-item"><?=$lang['admin']['forwarding_hosts'];?></a>
<a href="#f2bparams" class="list-group-item"><?=$lang['admin']['f2b_parameters'];?></a>
<a href="#relayhosts" class="list-group-item">Relayhosts</a>
<a href="#quarantine" class="list-group-item"><?=$lang['admin']['quarantine'];?></a>
<a href="#rsettings" class="list-group-item">Rspamd settings map</a>
<a href="#customize" class="list-group-item"><?=$lang['admin']['customize'];?></a>
@ -333,7 +428,7 @@ $tfa_data = get_tfa();
<input class="form-control input-sm" name="dkim_selector" value="dkim" required>
</div>
<div class="form-group">
<label for="private_key_file"><?=$lang['admin']['private_key'];?>:</label>
<label for="private_key_file"><?=$lang['admin']['private_key'];?>: (RSA PKCS#8)</label>
<textarea class="form-control input-sm" rows="10" name="private_key_file" id="private_key_file" required placeholder="-----BEGIN RSA KEY-----"></textarea>
</div>
<button class="btn btn-sm btn-default" data-action="add_item" data-id="dkim_import" data-api-url='add/dkim_import' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['import'];?></button>
@ -513,46 +608,6 @@ $tfa_data = get_tfa();
</div>
</div>
<span class="anchor" id="relayhosts"></span>
<div class="panel panel-default">
<div class="panel-heading">Relayhosts</div>
<div class="panel-body">
<p style="margin-bottom:40px"><?=$lang['admin']['relayhosts_hint'];?></p>
<div class="table-responsive">
<table class="table table-striped table-condensed" id="relayhoststable"></table>
</div>
<div class="mass-actions-admin">
<div class="btn-group btn-group-sm">
<button type="button" id="toggle_multi_select_all" data-id="rlyhosts" class="btn btn-default"><?=$lang['mailbox']['toggle_all'];?></button>
<a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a data-action="edit_selected" data-id="rlyhosts" data-api-url='edit/relayhost' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li>
<li><a data-action="edit_selected" data-id="rlyhosts" data-api-url='edit/relayhost' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li>
<li role="separator" class="divider"></li>
<li><a data-action="delete_selected" data-id="rlyhosts" data-api-url='delete/relayhost' href="#"><?=$lang['admin']['remove'];?></a></li>
</ul>
</div>
</div>
<legend><?=$lang['admin']['add_relayhost'];?></legend>
<p class="help-block"><?=$lang['admin']['add_relayhost_add_hint'];?></p>
<form class="form" data-id="rlyhost" role="form" method="post">
<div class="form-group">
<label for="hostname"><?=$lang['admin']['host'];?></label>
<input class="form-control" name="hostname" required>
</div>
<div class="form-group">
<label for="hostname"><?=$lang['admin']['username'];?></label>
<input class="form-control" name="username">
</div>
<div class="form-group">
<label for="hostname"><?=$lang['admin']['password'];?></label>
<input class="form-control" name="password">
</div>
<button class="btn btn-default" data-action="add_item" data-id="rlyhost" data-api-url='add/relayhost' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add'];?></button>
</form>
</div>
</div>
<span class="anchor" id="quarantine"></span>
<div class="panel panel-default">
<div class="panel-heading"><?=$lang['admin']['quarantine'];?></div>

View File

@ -9,7 +9,7 @@ table.footable>tbody>tr.footable-empty>td {
overflow: visible !important;
}
.table-responsive {
overflow: visible !important;
overflow: auto !important;
}
@media screen and (max-width: 767px) {
.table-responsive {

View File

@ -22,6 +22,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
<li role="presentation"><a href="#tab-watchdog-logs" aria-controls="tab-watchdog-logs" role="tab" data-toggle="tab">Watchdog</a></li>
<li role="presentation"><a href="#tab-acme-logs" aria-controls="tab-acme-logs" role="tab" data-toggle="tab">ACME</a></li>
<li role="presentation"><a href="#tab-api-logs" aria-controls="tab-api-logs" role="tab" data-toggle="tab">API</a></li>
<li role="presentation"><a href="#tab-api-rl" aria-controls="tab-api-rl" role="tab" data-toggle="tab">Ratelimits</a></li>
<li role="presentation"><span class="dropdown-desc"><?=$lang['debug']['external_logs'];?></span></li>
<li role="presentation"><a href="#tab-rspamd-history" aria-controls="tab-rspamd-history" role="tab" data-toggle="tab">Rspamd</a></li>
<li role="presentation"><span class="dropdown-desc"><?=$lang['debug']['static_logs'];?></span></li>
@ -272,6 +273,24 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
</div>
</div>
<div role="tabpanel" class="tab-pane" id="tab-api-rl">
<div class="panel panel-default">
<div class="panel-heading">Ratelimits <span class="badge badge-info table-lines"></span>
<div class="btn-group pull-right">
<button class="btn btn-xs btn-default add_log_lines" data-post-process="rllog" data-table="rl_log" data-log-url="ratelimited" data-nrows="100">+ 100</button>
<button class="btn btn-xs btn-default add_log_lines" data-post-process="rllog" data-table="rl_log" data-log-url="ratelimited" data-nrows="1000">+ 1000</button>
<button class="btn btn-xs btn-default refresh_table" data-draw="draw_rl_logs" data-table="rl_log"><?=$lang['admin']['refresh'];?></button>
</div>
</div>
<div class="panel-body">
<p class="help-block"><?=$lang['admin']['hash_remove_info'];?></p>
<div class="table-responsive">
<table class="table table-striped table-condensed" id="rl_log"></table>
</div>
</div>
</div>
</div>
</div> <!-- /tab-content -->
</div> <!-- /col-md-12 -->
</div> <!-- /row -->

View File

@ -271,7 +271,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="quota">Relayhost</label>
<label class="control-label col-sm-2" for="quota"><?=$lang['edit']['relayhost'];?></label>
<div class="col-sm-10">
<select data-live-search="true" name="relayhost" class="form-control">
<?php
@ -330,7 +330,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
<hr>
<form data-id="domratelimit" class="form-inline well" method="post">
<div class="form-group">
<label class="control-label">Ratelimit</label>
<label class="control-label"><?=$lang['acl']['ratelimit'];?></label>
<input name="rl_value" type="number" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" autocomplete="off" class="form-control" placeholder="disabled">
</div>
<div class="form-group">
@ -440,7 +440,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
<hr>
<form data-id="domratelimit" class="form-inline well" method="post">
<div class="form-group">
<label class="control-label">Ratelimit</label>
<label class="control-label"><?=$lang['acl']['ratelimit'];?></label>
<input name="rl_value" type="number" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" autocomplete="off" class="form-control" placeholder="disabled">
</div>
<div class="form-group">
@ -600,7 +600,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
<form data-id="mboxratelimit" class="form-inline well" method="post">
<div class="row">
<div class="col-sm-1">
<p class="help-block">Ratelimit</p>
<p class="help-block"><?=$lang['acl']['ratelimit'];?></p>
</div>
<div class="col-sm-10">
<div class="form-group">
@ -693,6 +693,59 @@ if (isset($_SESSION['mailcow_cc_role'])) {
<?php
}
}
elseif (isset($_GET['transport']) && is_numeric($_GET["transport"]) && !empty($_GET["transport"])) {
$transport = intval($_GET["transport"]);
$result = transport('details', $transport);
if (!empty($result)) {
?>
<h4><?=$lang['edit']['resource'];?></h4>
<form class="form-horizontal" role="form" method="post" data-id="edittransport">
<input type="hidden" value="0" name="active">
<div class="form-group">
<label class="control-label col-sm-2" for="destination"><?=$lang['add']['destination'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="destination" value="<?=htmlspecialchars($result['destination'], ENT_QUOTES, 'UTF-8');?>" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="nexthop"><?=$lang['edit']['nexthop'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="nexthop" value="<?=htmlspecialchars($result['nexthop'], ENT_QUOTES, 'UTF-8');?>" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="username"><?=$lang['add']['username'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="username" value="<?=htmlspecialchars($result['username'], ENT_QUOTES, 'UTF-8');?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password"><?=$lang['add']['password'];?></label>
<div class="col-sm-10">
<input type="password" data-hibp="true" class="form-control" name="password" value="<?=htmlspecialchars($result['password'], ENT_QUOTES, 'UTF-8');?>">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label><input type="checkbox" value="1" name="active" <?=($result['active_int']=="1") ? "checked" : null;?>> <?=$lang['edit']['active'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-success" data-action="edit_selected" data-id="edittransport" data-item="<?=htmlspecialchars($result['id']);?>" data-api-url='edit/transport' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button>
</div>
</div>
</form>
<?php
}
else {
?>
<div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
<?php
}
}
elseif (isset($_GET['resource']) && filter_var(html_entity_decode(rawurldecode($_GET["resource"])), FILTER_VALIDATE_EMAIL) && !empty($_GET["resource"])) {
$resource = html_entity_decode(rawurldecode($_GET["resource"]));
$result = mailbox('get', 'resource_details', $resource);

View File

@ -4,23 +4,49 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php';
error_reporting(0);
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") {
$relayhost_id = intval($_GET['relayhost_id']);
$transport_id = intval($_GET['transport_id']);
$transport_type = $_GET['transport_type'];
if (isset($_GET['mail_from']) && filter_var($_GET['mail_from'], FILTER_VALIDATE_EMAIL)) {
$mail_from = $_GET['mail_from'];
}
else {
$mail_from = "relay@example.org";
}
$relayhost_details = relayhost('details', $relayhost_id);
if (!empty($relayhost_details)) {
if ($transport_type == 'transport-map') {
$transport_details = transport('details', $transport_id);
$nexthop = $transport_details['nexthop'];
}
elseif ($transport_type == 'sender-dependent') {
$transport_details = relayhost('details', $transport_id);
$nexthop = $transport_details['hostname'];
}
if (!empty($transport_details)) {
// Remove [ and ]
$hostname_w_port = preg_replace('/\[|\]/', '', $relayhost_details['hostname']);
$hostname_w_port = preg_replace('/\[|\]/', '', $nexthop);
$skip_lookup_mx = strpos($nexthop, '[');
// Explode to hostname and port
list($hostname, $port) = explode(':', $hostname_w_port);
// Try to get MX if host is not [host]
if ($skip_lookup_mx === false) {
getmxrr($hostname, $mx_records, $mx_weight);
if (!empty($mx_records)) {
for ($i = 0; $i < count($mx_records); $i++) {
$mxs[$mx_records[$i]] = $mx_weight[$i];
}
asort ($mxs);
$records = array_keys($mxs);
echo 'Using first matched primary MX for "' . $hostname . '": ';
$hostname = $records[0];
echo $hostname . '<br>';
}
else {
echo 'No MX records for ' . $hostname . ' were found in DNS, skipping and using hostname as next-hop.<br>';
}
}
// Use port 25 if no port was given
$port = (empty($port)) ? 25 : $port;
$username = $relayhost_details['username'];
$password = $relayhost_details['password'];
$username = $transport_details['username'];
$password = $transport_details['password'];
$mail = new PHPMailer;
$mail->Timeout = 10;
@ -73,7 +99,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admi
$mail->send();
}
else {
echo "Unknown relayhost.";
echo "Unknown transport.";
}
}
else {

View File

@ -123,7 +123,7 @@ function dkim($_action, $_data = null) {
try {
$redis->hSet('DKIM_PUB_KEYS', $to_domain, $from_domain_dkim['pubkey']);
$redis->hSet('DKIM_SELECTORS', $to_domain, $from_domain_dkim['dkim_selector']);
$redis->hSet('DKIM_PRIV_KEYS', $from_domain_dkim['dkim_selector'] . '.' . $to_domain, trim($from_domain_dkim['privkey']));
$redis->hSet('DKIM_PRIV_KEYS', $from_domain_dkim['dkim_selector'] . '.' . $to_domain, base64_decode(trim($from_domain_dkim['privkey'])));
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
@ -307,4 +307,4 @@ function dkim($_action, $_data = null) {
}
break;
}
}
}

View File

@ -5,6 +5,9 @@ function domain_admin($_action, $_data = null) {
$_data_log = $_data;
!isset($_data_log['password']) ?: $_data_log['password'] = '*';
!isset($_data_log['password2']) ?: $_data_log['password2'] = '*';
!isset($_data_log['user_old_pass']) ?: $_data_log['user_old_pass'] = '*';
!isset($_data_log['user_new_pass']) ?: $_data_log['user_new_pass'] = '*';
!isset($_data_log['user_new_pass2']) ?: $_data_log['user_new_pass2'] = '*';
switch ($_action) {
case 'add':
$username = strtolower(trim($_data['username']));

View File

@ -1263,7 +1263,7 @@ function get_u2f_registrations($username) {
$sel->execute(array($username));
return $sel->fetchAll(PDO::FETCH_OBJ);
}
function get_logs($container, $lines = false) {
function get_logs($application, $lines = false) {
if ($lines === false) {
$lines = $GLOBALS['LOG_LINES'] - 1;
}
@ -1283,7 +1283,7 @@ function get_logs($container, $lines = false) {
return false;
}
// SQL
if ($container == "mailcow-ui") {
if ($application == "mailcow-ui") {
if (isset($from) && isset($to)) {
$stmt = $pdo->prepare("SELECT * FROM `logs` ORDER BY `id` DESC LIMIT :from, :to");
$stmt->execute(array(
@ -1304,7 +1304,7 @@ function get_logs($container, $lines = false) {
}
}
// Redis
if ($container == "dovecot-mailcow") {
if ($application == "dovecot-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('DOVECOT_MAILLOG', $from - 1, $to - 1);
}
@ -1318,7 +1318,7 @@ function get_logs($container, $lines = false) {
return $data_array;
}
}
if ($container == "postfix-mailcow") {
if ($application == "postfix-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('POSTFIX_MAILLOG', $from - 1, $to - 1);
}
@ -1332,7 +1332,7 @@ function get_logs($container, $lines = false) {
return $data_array;
}
}
if ($container == "sogo-mailcow") {
if ($application == "sogo-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('SOGO_LOG', $from - 1, $to - 1);
}
@ -1346,7 +1346,7 @@ function get_logs($container, $lines = false) {
return $data_array;
}
}
if ($container == "watchdog-mailcow") {
if ($application == "watchdog-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('WATCHDOG_LOG', $from - 1, $to - 1);
}
@ -1360,7 +1360,7 @@ function get_logs($container, $lines = false) {
return $data_array;
}
}
if ($container == "acme-mailcow") {
if ($application == "acme-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('ACME_LOG', $from - 1, $to - 1);
}
@ -1374,7 +1374,21 @@ function get_logs($container, $lines = false) {
return $data_array;
}
}
if ($container == "api-mailcow") {
if ($application == "ratelimited") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('RL_LOG', $from - 1, $to - 1);
}
else {
$data = $redis->lRange('RL_LOG', 0, $lines);
}
if ($data) {
foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true);
}
return $data_array;
}
}
if ($application == "api-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('API_LOG', $from - 1, $to - 1);
}
@ -1388,7 +1402,7 @@ function get_logs($container, $lines = false) {
return $data_array;
}
}
if ($container == "netfilter-mailcow") {
if ($application == "netfilter-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('NETFILTER_LOG', $from - 1, $to - 1);
}
@ -1402,7 +1416,7 @@ function get_logs($container, $lines = false) {
return $data_array;
}
}
if ($container == "autodiscover-mailcow") {
if ($application == "autodiscover-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('AUTODISCOVER_LOG', $from - 1, $to - 1);
}
@ -1416,7 +1430,7 @@ function get_logs($container, $lines = false) {
return $data_array;
}
}
if ($container == "rspamd-history") {
if ($application == "rspamd-history") {
$curl = curl_init();
curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock');
if (!is_numeric($lines)) {

View File

@ -192,5 +192,44 @@ function ratelimit($_action, $_scope, $_data = null) {
break;
}
break;
case 'delete':
$data['hash'] = $_data;
if ($_SESSION['mailcow_cc_role'] != 'admin' || !preg_match('/^RL[0-9A-Za-z=]+$/i', trim($data['hash']))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
try {
if ($redis->exists($data['hash'])) {
$redis->delete($data['hash']);
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
'msg' => 'hash_deleted'
);
return true;
}
else {
$_SESSION['return'][] = array(
'type' => 'warning',
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
'msg' => 'hash_not_found'
);
return false;
}
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
'msg' => array('redis_error', $e)
);
return false;
}
return false;
break;
}
}

View File

@ -1,174 +0,0 @@
<?php
function relayhost($_action, $_data = null) {
global $pdo;
global $lang;
$_data_log = $_data;
switch ($_action) {
case 'add':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$hostname = trim($_data['hostname']);
$username = str_replace(':', '\:', trim($_data['username']));
$password = str_replace(':', '\:', trim($_data['password']));
if (empty($hostname)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('invalid_host', htmlspecialchars($host))
);
return false;
}
try {
$stmt = $pdo->prepare("INSERT INTO `relayhosts` (`hostname`, `username` ,`password`, `active`)
VALUES (:hostname, :username, :password, :active)");
$stmt->execute(array(
':hostname' => $hostname,
':username' => $username,
':password' => str_replace(':', '\:', $password),
':active' => '1'
));
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('mysql_error', $e)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('relayhost_added', htmlspecialchars(implode(', ', $hosts)))
);
break;
case 'edit':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$ids = (array)$_data['id'];
foreach ($ids as $id) {
$is_now = relayhost('details', $id);
if (!empty($is_now)) {
$hostname = (!empty($_data['hostname'])) ? trim($_data['hostname']) : $is_now['hostname'];
$username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username'];
$password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password'];
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('relayhost_invalid', $id)
);
continue;
}
try {
$stmt = $pdo->prepare("UPDATE `relayhosts` SET
`hostname` = :hostname,
`username` = :username,
`password` = :password,
`active` = :active
WHERE `id` = :id");
$stmt->execute(array(
':id' => $id,
':hostname' => $hostname,
':username' => $username,
':password' => $password,
':active' => $active
));
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('mysql_error', $e)
);
continue;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('object_modified', htmlspecialchars(implode(', ', $hostnames)))
);
}
break;
case 'delete':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$ids = (array)$_data['id'];
foreach ($ids as $id) {
try {
$stmt = $pdo->prepare("DELETE FROM `relayhosts` WHERE `id`= :id");
$stmt->execute(array(':id' => $id));
$stmt = $pdo->prepare("UPDATE `domain` SET `relayhost` = '0' WHERE `relayhost`= :id");
$stmt->execute(array(':id' => $id));
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('mysql_error', $e)
);
continue;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('relayhost_removed', htmlspecialchars($id))
);
}
break;
case 'get':
if ($_SESSION['mailcow_cc_role'] != "admin") {
return false;
}
$relayhosts = array();
$stmt = $pdo->query("SELECT `id`, `hostname`, `username` FROM `relayhosts`");
$relayhosts = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $relayhosts;
break;
case 'details':
if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) {
return false;
}
$relayhostdata = array();
$stmt = $pdo->prepare("SELECT `id`,
`hostname`,
`username`,
`password`,
`active` AS `active_int`,
CONCAT(LEFT(`password`, 3), '...') AS `password_short`,
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
FROM `relayhosts`
WHERE `id` = :id");
$stmt->execute(array(':id' => $_data));
$relayhostdata = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($relayhostdata)) {
$stmt = $pdo->prepare("SELECT GROUP_CONCAT(`domain` SEPARATOR ', ') AS `used_by_domains` FROM `domain` WHERE `relayhost` = :id");
$stmt->execute(array(':id' => $_data));
$used_by_domains = $stmt->fetch(PDO::FETCH_ASSOC)['used_by_domains'];
$used_by_domains = (empty($used_by_domains)) ? '' : $used_by_domains;
$relayhostdata['used_by_domains'] = $used_by_domains;
}
return $relayhostdata;
break;
}
}

View File

@ -0,0 +1,449 @@
<?php
function relayhost($_action, $_data = null) {
global $pdo;
global $lang;
$_data_log = $_data;
switch ($_action) {
case 'add':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$hostname = trim($_data['hostname']);
$username = str_replace(':', '\:', trim($_data['username']));
$password = str_replace(':', '\:', trim($_data['password']));
if (empty($hostname)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('invalid_host', htmlspecialchars($host))
);
return false;
}
try {
$stmt = $pdo->prepare("INSERT INTO `relayhosts` (`hostname`, `username` ,`password`, `active`)
VALUES (:hostname, :username, :password, :active)");
$stmt->execute(array(
':hostname' => $hostname,
':username' => $username,
':password' => str_replace(':', '\:', $password),
':active' => '1'
));
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('mysql_error', $e)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('relayhost_added', htmlspecialchars(implode(', ', $hosts)))
);
break;
case 'edit':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$ids = (array)$_data['id'];
foreach ($ids as $id) {
$is_now = relayhost('details', $id);
if (!empty($is_now)) {
$hostname = (!empty($_data['hostname'])) ? trim($_data['hostname']) : $is_now['hostname'];
$username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username'];
$password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password'];
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('relayhost_invalid', $id)
);
continue;
}
try {
$stmt = $pdo->prepare("UPDATE `relayhosts` SET
`hostname` = :hostname,
`username` = :username,
`password` = :password,
`active` = :active
WHERE `id` = :id");
$stmt->execute(array(
':id' => $id,
':hostname' => $hostname,
':username' => $username,
':password' => $password,
':active' => $active
));
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('mysql_error', $e)
);
continue;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('object_modified', htmlspecialchars(implode(', ', $hostnames)))
);
}
break;
case 'delete':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$ids = (array)$_data['id'];
foreach ($ids as $id) {
try {
$stmt = $pdo->prepare("DELETE FROM `relayhosts` WHERE `id`= :id");
$stmt->execute(array(':id' => $id));
$stmt = $pdo->prepare("UPDATE `domain` SET `relayhost` = '0' WHERE `relayhost`= :id");
$stmt->execute(array(':id' => $id));
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('mysql_error', $e)
);
continue;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('relayhost_removed', htmlspecialchars($id))
);
}
break;
case 'get':
if ($_SESSION['mailcow_cc_role'] != "admin") {
return false;
}
$relayhosts = array();
$stmt = $pdo->query("SELECT `id`, `hostname`, `username` FROM `relayhosts`");
$relayhosts = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $relayhosts;
break;
case 'details':
if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) {
return false;
}
$relayhostdata = array();
$stmt = $pdo->prepare("SELECT `id`,
`hostname`,
`username`,
`password`,
`active` AS `active_int`,
CONCAT(LEFT(`password`, 3), '...') AS `password_short`,
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
FROM `relayhosts`
WHERE `id` = :id");
$stmt->execute(array(':id' => $_data));
$relayhostdata = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($relayhostdata)) {
$stmt = $pdo->prepare("SELECT GROUP_CONCAT(`domain` SEPARATOR ', ') AS `used_by_domains` FROM `domain` WHERE `relayhost` = :id");
$stmt->execute(array(':id' => $_data));
$used_by_domains = $stmt->fetch(PDO::FETCH_ASSOC)['used_by_domains'];
$used_by_domains = (empty($used_by_domains)) ? '' : $used_by_domains;
$relayhostdata['used_by_domains'] = $used_by_domains;
}
return $relayhostdata;
break;
}
}
function transport($_action, $_data = null) {
global $pdo;
global $lang;
$_data_log = $_data;
switch ($_action) {
case 'add':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$destination = trim($_data['destination']);
$nexthop = trim($_data['nexthop']);
preg_match('/\[(.+)\].*/', $nexthop, $next_hop_matches);
$next_hop_clean = (isset($next_hop_matches[1])) ? $next_hop_matches[1] : $nexthop;
$username = str_replace(':', '\:', trim($_data['username']));
$password = str_replace(':', '\:', trim($_data['password']));
// ".domain" is a valid destination, "..domain" is not
if (empty($destination) || (is_valid_domain_name(preg_replace('/^' . preg_quote('.', '/') . '/', '', $destination)) === false && $destination != '*')) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'invalid_destination'
);
return false;
}
if (empty($nexthop)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('invalid_nexthop')
);
return false;
}
$transports = transport('get');
if (!empty($transports)) {
foreach ($transports as $transport) {
$transport_data = transport('details', $transport['id']);
$existing_nh[] = $transport_data['nexthop'];
preg_match('/\[(.+)\].*/', $transport_data['nexthop'], $existing_clean_nh[]);
if (($transport_data['nexthop'] == $nexthop || $transport_data['nexthop'] == $next_hop_clean) && $transport_data['username'] != $username) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'invalid_nexthop_authenticated'
);
return false;
}
}
}
if (isset($next_hop_matches[1])) {
if (in_array($next_hop_clean, $existing_nh)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('next_hop_interferes', $next_hop_clean, $nexthop)
);
return false;
}
}
else {
foreach ($existing_clean_nh as $existing_clean_nh_each) {
if ($existing_clean_nh_each[1] == $nexthop) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('next_hop_interferes_any', $nexthop)
);
return false;
}
}
}
try {
$stmt = $pdo->prepare("INSERT INTO `transports` (`nexthop`, `destination`, `username` ,`password`, `active`)
VALUES (:nexthop, :destination, :username, :password, :active)");
$stmt->execute(array(
':nexthop' => $nexthop,
':destination' => $destination,
':username' => $username,
':password' => str_replace(':', '\:', $password),
':active' => '1'
));
$stmt = $pdo->prepare("UPDATE `transports` SET
`username` = :username,
`password` = :password
WHERE `nexthop` = :nexthop");
$stmt->execute(array(
':nexthop' => $nexthop,
':username' => $username,
':password' => $password
));
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('mysql_error', $e)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('relayhost_added', htmlspecialchars(implode(', ', $hosts)))
);
break;
case 'edit':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$ids = (array)$_data['id'];
foreach ($ids as $id) {
$is_now = transport('details', $id);
if (!empty($is_now)) {
$destination = (!empty($_data['destination'])) ? trim($_data['destination']) : $is_now['destination'];
$nexthop = (!empty($_data['nexthop'])) ? trim($_data['nexthop']) : $is_now['nexthop'];
$username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username'];
$password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password'];
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('relayhost_invalid', $id)
);
continue;
}
preg_match('/\[(.+)\].*/', $nexthop, $next_hop_matches);
$next_hop_clean = (isset($next_hop_matches[1])) ? $next_hop_matches[1] : $nexthop;
$transports = transport('get');
if (!empty($transports)) {
foreach ($transports as $transport) {
$transport_data = transport('details', $transport['id']);
if ($transport['id'] == $id) {
continue;
}
$existing_nh[] = $transport_data['nexthop'];
preg_match('/\[(.+)\].*/', $transport_data['nexthop'], $existing_clean_nh[]);
}
}
if (isset($next_hop_matches[1])) {
if (in_array($next_hop_clean, $existing_nh)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('next_hop_interferes', $next_hop_clean, $nexthop)
);
return false;
}
}
else {
foreach ($existing_clean_nh as $existing_clean_nh_each) {
if ($existing_clean_nh_each[1] == $nexthop) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('next_hop_interferes_any', $nexthop)
);
return false;
}
}
}
if (empty($username)) {
$password = '';
}
try {
$stmt = $pdo->prepare("UPDATE `transports` SET
`destination` = :destination,
`nexthop` = :nexthop,
`username` = :username,
`password` = :password,
`active` = :active
WHERE `id` = :id");
$stmt->execute(array(
':id' => $id,
':destination' => $destination,
':nexthop' => $nexthop,
':username' => $username,
':password' => $password,
':active' => $active
));
$stmt = $pdo->prepare("UPDATE `transports` SET
`username` = :username,
`password` = :password
WHERE `nexthop` = :nexthop");
$stmt->execute(array(
':nexthop' => $nexthop,
':username' => $username,
':password' => $password
));
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('mysql_error', $e)
);
continue;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('object_modified', htmlspecialchars(implode(', ', $hostnames)))
);
}
break;
case 'delete':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$ids = (array)$_data['id'];
foreach ($ids as $id) {
try {
$stmt = $pdo->prepare("DELETE FROM `transports` WHERE `id`= :id");
$stmt->execute(array(':id' => $id));
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('mysql_error', $e)
);
continue;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('relayhost_removed', htmlspecialchars($id))
);
}
break;
case 'get':
if ($_SESSION['mailcow_cc_role'] != "admin") {
return false;
}
$transports = array();
$stmt = $pdo->query("SELECT `id`, `destination`, `nexthop`, `username` FROM `transports`");
$transports = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $transports;
break;
case 'details':
if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) {
return false;
}
$transportdata = array();
$stmt = $pdo->prepare("SELECT `id`,
`destination`,
`nexthop`,
`username`,
`password`,
`active` AS `active_int`,
CONCAT(LEFT(`password`, 3), '...') AS `password_short`,
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
FROM `transports`
WHERE `id` = :id");
$stmt->execute(array(':id' => $_data));
$transportdata = $stmt->fetch(PDO::FETCH_ASSOC);
return $transportdata;
break;
}
}

View File

@ -3,7 +3,7 @@ function init_db_schema() {
try {
global $pdo;
$db_version = "14112018_0717";
$db_version = "15122018_0717";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@ -109,6 +109,26 @@ function init_db_schema() {
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"transports" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"destination" => "VARCHAR(255) NOT NULL",
"nexthop" => "VARCHAR(255) NOT NULL",
"username" => "VARCHAR(255) NOT NULL",
"password" => "VARCHAR(255) NOT NULL",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
),
"keys" => array(
"primary" => array(
"" => array("id")
),
"key" => array(
"destination" => array("destination"),
"nexthop" => array("nexthop"),
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"alias" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",

View File

@ -147,7 +147,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.dkim.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fwdhost.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailq.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.ratelimit.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.relayhost.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.transports.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.rsettings.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.tls_policy_maps.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fail2ban.inc.php';

View File

@ -5,10 +5,12 @@ jQuery(function($){
var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};
function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
function hashCode(t){for(var n=0,r=0;r<t.length;r++)n=t.charCodeAt(r)+((n<<5)-n);return n}
function intToRGB(t){var n=(16777215&t).toString(16).toUpperCase();return"00000".substring(0,6-n.length)+n}
$("#rspamd_preset_1").on('click', function(e) {
e.preventDefault();
$("form[data-id=rsetting]").find("#adminRspamdSettingsDesc").val(lang.rsettings_preset_1);
$("form[data-id=rsetting]").find("#adminRspamdSettingsContent").val('priority = 10;\nauthenticated = yes;\napply "default" {\n symbols_enabled = ["DKIM_SIGNED", "RATELIMIT_UPDATE", "RATELIMIT_CHECK", "DYN_RL_CHECK", "HISTORY_SAVE", "MILTER_HEADERS", "ARC_SIGNED"];\n}');
$("form[data-id=rsetting]").find("#adminRspamdSettingsContent").val('priority = 10;\nauthenticated = yes;\napply "default" {\n symbols_enabled = ["DKIM_SIGNED", "RATELIMITED", "RATELIMIT_UPDATE", "RATELIMIT_CHECK", "DYN_RL_CHECK", "HISTORY_SAVE", "MILTER_HEADERS", "ARC_SIGNED"];\n}');
});
$("#rspamd_preset_2").on('click', function(e) {
e.preventDefault();
@ -145,7 +147,7 @@ jQuery(function($){
{"name":"username","title":lang.username,"breakpoints":"xs sm"},
{"name":"used_by_domains","title":lang.in_use_by,"style":{"width":"110px"}, "type": "text","breakpoints":"xs sm"},
{"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"280px","width":"280px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"220px","width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
],
"rows": $.ajax({
dataType: 'json',
@ -163,6 +165,33 @@ jQuery(function($){
"sorting": {"enabled": true}
});
}
function draw_transport_maps() {
ft_relayhoststable = FooTable.init('#transportstable', {
"columns": [
{"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"},
{"name":"id","type":"text","title":"ID","style":{"width":"50px"}},
{"name":"destination","type":"text","title":lang.destination,"style":{"width":"250px"}},
{"name":"nexthop","type":"text","title":lang.nexthop,"style":{"width":"250px"}},
{"name":"username","title":lang.username,"breakpoints":"xs sm"},
{"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"220px","width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
],
"rows": $.ajax({
dataType: 'json',
url: '/api/v1/get/transport/all',
jsonp: false,
error: function () {
console.log('Cannot draw transports table');
},
success: function (data) {
return process_table_data(data, 'transportstable');
}
}),
"empty": lang.empty,
"paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
"sorting": {"enabled": true}
});
}
function draw_queue() {
ft_queuetable = FooTable.init('#queuetable', {
"columns": [
@ -205,12 +234,24 @@ jQuery(function($){
if (table == 'relayhoststable') {
$.each(data, function (i, item) {
item.action = '<div class="btn-group">' +
'<a href="#" data-toggle="modal" id="miau" data-target="#testRelayhostModal" data-relayhost-id="' + encodeURI(item.id) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-stats"></span> Test</a>' +
'<a href="#" data-toggle="modal" data-target="#testTransportModal" data-transport-id="' + encodeURI(item.id) + '" data-transport-type="sender-dependent" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-triangle-right"></span> Test</a>' +
'<a href="/edit/relayhost/' + encodeURI(item.id) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
'<a href="#" data-action="delete_selected" data-id="single-rlshost" data-api-url="delete/relayhost" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'<a href="#" data-action="delete_selected" data-id="single-rlyhost" data-api-url="delete/relayhost" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'</div>';
item.chkbox = '<input type="checkbox" data-id="rlyhosts" name="multi_select" value="' + item.id + '" />';
});
} else if (table == 'transportstable') {
$.each(data, function (i, item) {
if (item.username) {
item.username = '<span style="border-left:3px solid #' + intToRGB(hashCode(item.nexthop)) + ';padding-left:5px;">' + item.username + '</span>';
}
item.action = '<div class="btn-group">' +
'<a href="#" data-toggle="modal" data-target="#testTransportModal" data-transport-id="' + encodeURI(item.id) + '" data-transport-type="transport-map" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-triangle-right"></span> Test</a>' +
'<a href="/edit/transport/' + encodeURI(item.id) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
'<a href="#" data-action="delete_selected" data-id="single-transport" data-api-url="delete/transport" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'</div>';
item.chkbox = '<input type="checkbox" data-id="transports" name="multi_select" value="' + item.id + '" />';
});
} else if (table == 'queuetable') {
$.each(data, function (i, item) {
item.chkbox = '<input type="checkbox" data-id="mailqitems" name="multi_select" value="' + item.queue_id + '" />';
@ -264,6 +305,7 @@ jQuery(function($){
draw_admins();
draw_fwd_hosts();
draw_relayhosts();
draw_transport_maps();
draw_queue();
// Relayhost
$('#testRelayhostModal').on('show.bs.modal', function (e) {
@ -290,6 +332,32 @@ jQuery(function($){
}
});
})
// Transport
$('#testTransportModal').on('show.bs.modal', function (e) {
$('#test_transport_result').text("-");
button = $(e.relatedTarget)
if (button != null) {
$('#transport_id').val(button.data('transport-id'));
$('#transport_type').val(button.data('transport-type'));
}
})
$('#test_transport').on('click', function (e) {
e.preventDefault();
prev = $('#test_transport').text();
$(this).prop("disabled",true);
$(this).html('<span class="glyphicon glyphicon-refresh glyphicon-spin"></span> ');
$.ajax({
type: 'GET',
url: 'inc/ajax/transport_check.php',
dataType: 'text',
data: $('#test_transport_form').serialize(),
complete: function (data) {
$('#test_transport_result').html(data.responseText);
$('#test_transport').prop("disabled",false);
$('#test_transport').text(prev);
}
});
})
// DKIM private key modal
$('#showDKIMprivKey').on('show.bs.modal', function (e) {
$('#priv_key_pre').text("-");

View File

@ -8,6 +8,8 @@ jQuery(function($){
var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};
function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
function hashCode(t){for(var n=0,r=0;r<t.length;r++)n=t.charCodeAt(r)+((n<<5)-n);return n}
function intToRGB(t){var n=(16777215&t).toString(16).toUpperCase();return"00000".substring(0,6-n.length)+n}
$(".refresh_table").on('click', function(e) {
e.preventDefault();
var table_name = $(this).data('table');
@ -162,6 +164,48 @@ jQuery(function($){
}
});
}
function draw_rl_logs() {
ft_rl_logs = FooTable.init('#rl_log', {
"columns": [
{"name":"indicator","title":" ","style":{"width":"50px"}},
{"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.last_applied,"style":{"width":"170px"}},
{"name":"rl_name","title":lang.rate_name},
{"name":"from","title":lang.sender},
{"name":"rcpt","title":lang.recipients},
{"name":"user","title":lang.authed_user},
{"name":"message_id","title":"Msg ID","breakpoints": "all","style":{"word-break":"break-all"}},
{"name":"header_from","title":"Header From","breakpoints": "all","style":{"word-break":"break-all"}},
{"name":"header_subject","title":"Subject","breakpoints": "all","style":{"word-break":"break-all"}},
{"name":"rl_hash","title":"Hash","breakpoints": "all","style":{"word-break":"break-all"}},
{"name":"qid","title":"Rspamd QID","breakpoints": "all","style":{"word-break":"break-all"}},
{"name":"ip","title":"IP","breakpoints": "all","style":{"word-break":"break-all"}},
{"name":"action","title":lang.action,"breakpoints": "all","style":{"word-break":"break-all"}},
],
"rows": $.ajax({
dataType: 'json',
url: '/api/v1/get/logs/ratelimited',
jsonp: false,
error: function () {
console.log('Cannot draw rl log table');
},
success: function (data) {
return process_table_data(data, 'rllog');
}
}),
"empty": lang.empty,
"paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
"filtering": {"enabled": true,"delay": 1,"position": "left","connectors": false,"placeholder": lang.filter_table},
"sorting": {"enabled": true},
"on": {
"ready.ft.table": function(e, ft){
table_log_ready(ft, 'rl_logs');
},
"after.ft.paging": function(e, ft){
table_log_paging(ft, 'rl_logs');
}
}
});
}
function draw_ui_logs() {
ft_api_logs = FooTable.init('#ui_logs', {
"columns": [
@ -540,6 +584,19 @@ jQuery(function($){
item.method = '<span class="label label-warning">' + item.method + '</span>';
}
});
} else if (table == 'rllog') {
$.each(data, function (i, item) {
if (item.user == null) {
item.user = "none";
}
if (item.rl_hash == null) {
item.rl_hash = "err";
}
item.indicator = '<span style="border-right:6px solid #' + intToRGB(hashCode(item.rl_hash)) + ';padding-left:5px;">&nbsp;</span>';
if (item.rl_hash != 'err') {
item.action = '<a href="#" data-action="delete_selected" data-id="single-hash" data-api-url="delete/rlhash" data-item="' + encodeURI(item.rl_hash) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.reset_limit + '</a>';
}
});
}
return data
};
@ -575,6 +632,7 @@ jQuery(function($){
draw_watchdog_logs();
draw_acme_logs();
draw_api_logs();
draw_rl_logs();
draw_ui_logs();
draw_netfilter_logs();
draw_rspamd_history();

View File

@ -646,6 +646,67 @@ jQuery(function($){
}
});
}
function draw_transport_maps_table() {
ft_transport_maps_table = FooTable.init('#transport_maps_table', {
"columns": [
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"},
{"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}},
{"name":"dest","title":lang.tls_map_dest},
{"name":"parameters","title":lang.tls_map_parameters},
{"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":(role == "admin" ? lang.action : ""),"breakpoints":"xs sm"}
],
"empty": lang.empty,
"rows": $.ajax({
dataType: 'json',
url: '/api/v1/get/transport-map/all',
jsonp: false,
error: function () {
console.log('Cannot draw transport map table');
},
success: function (data) {
if (role == "admin") {
$.each(data, function (i, item) {
item.dest = escapeHtml(item.dest);
if (item.parameters == '') {
item.parameters = '<code>-</code>';
} else {
item.parameters = '<code>' + escapeHtml(item.parameters) + '</code>';
}
item.action = '<div class="btn-group">' +
'<a href="/edit/transport_map/' + item.id + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
'<a href="#" data-action="delete_selected" data-id="single-transport-map" data-api-url="delete/transport-map" data-item="' + item.id + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'</div>';
item.chkbox = '<input type="checkbox" data-id="transport-map" name="multi_select" value="' + item.id + '" />';
});
}
}
}),
"paging": {
"enabled": true,
"limit": 5,
"size": pagination_size
},
"filtering": {
"enabled": true,
"delay": 100,
"position": "left",
"connectors": false,
"placeholder": lang.filter_table
},
"sorting": {
"enabled": true
},
"on": {
"ready.ft.table": function(e, ft){
table_mailbox_ready(ft, 'transport_maps_table');
},
"after.ft.paging": function(e, ft){
paging_mailbox_after(ft, 'transport_maps_table');
}
}
});
}
function draw_alias_table() {
ft_alias_table = FooTable.init('#alias_table', {
"columns": [
@ -925,5 +986,6 @@ jQuery(function($){
draw_bcc_table();
draw_recipient_map_table();
draw_tls_policy_table();
draw_transport_maps_table();
});

View File

@ -102,6 +102,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
case "relayhost":
process_add_return(relayhost('add', $attr));
break;
case "transport":
process_add_return(transport('add', $attr));
break;
case "rsetting":
process_add_return(rsettings('add', $attr));
break;
@ -320,6 +323,33 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
}
break;
case "transport":
switch ($object) {
case "all":
$transports = transport('get');
if (!empty($transports)) {
foreach ($transports as $transport) {
if ($details = transport('details', $transport['id'])) {
$data[] = $details;
}
else {
continue;
}
}
process_get_return($data);
}
else {
echo '{}';
}
break;
default:
$data = transport('details', $object);
process_get_return($data);
break;
}
break;
case "rsetting":
switch ($object) {
case "all":
@ -387,6 +417,17 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
}
echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
break;
case "ratelimited":
// 0 is first record, so empty is fine
if (isset($extra)) {
$extra = preg_replace('/[^\d\-]/i', '', $extra);
$logs = get_logs('ratelimited', $extra);
}
else {
$logs = get_logs('ratelimited');
}
echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
break;
case "netfilter":
// 0 is first record, so empty is fine
if (isset($extra)) {
@ -979,6 +1020,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
case "relayhost":
process_delete_return(relayhost('delete', array('id' => $items)));
break;
case "transport":
process_delete_return(transport('delete', array('id' => $items)));
break;
case "rsetting":
process_delete_return(rsettings('delete', array('id' => $items)));
break;
@ -1043,6 +1087,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
case "admin":
process_delete_return(admin('delete', array('username' => $items)));
break;
case "rlhash":
echo ratelimit('delete', null, implode($items));
break;
}
break;
case "edit":
@ -1093,6 +1140,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
case "relayhost":
process_edit_return(relayhost('edit', array_merge(array('id' => $items), $attr)));
break;
case "transport":
process_edit_return(transport('edit', array_merge(array('id' => $items), $attr)));
break;
case "rsetting":
process_edit_return(rsettings('edit', array_merge(array('id' => $items), $attr)));
break;

View File

@ -2,7 +2,7 @@
/*
* Czech language file for mailcow
*
* Author: radek@fastlinux.eu (www.fastlinux.eu)
* Author: radek@fastlinux.eu (www.fastlinux.eu)
*
*/
@ -33,6 +33,11 @@ $lang['success']['verified_u2f_login'] = "U2F přihlášení ověřeno";
$lang['success']['verified_yotp_login'] = "Yubico OTP přihlášení ověřeno";
$lang['danger']['yotp_verification_failed'] = "Yubico OTP ověření selhalo: %s";
$lang['danger']['ip_list_empty'] = "Seznam povolených IP nesmí být prázdný";
$lang['danger']['invalid_destination'] = "Formát cíle je špatný";
$lang['danger']['invalid_nexthop'] = "Formát skoku (Next hop) je špatný";
$lang['danger']['invalid_nexthop_authenticated'] = "Skok (Next hop) již existuje s rozílným přihlašovacím údajem, nejdříve prosím aktualizujte existující přihlašovací údaje tohoto skoku.";
$lang['danger']['next_hop_interferes'] = "%s koliduje se skokem %s";
$lang['danger']['next_hop_interferes_any'] = "Existující skok koliduje s %s";
$lang['danger']['rspamd_ui_pw_length'] = "Heslo pro Rspamd UI musí být minimálně 6 znaků dlouhé";
$lang['success']['rspamd_ui_pw_set'] = "Heslo k Rspamd UI nastaveno";
$lang['success']['queue_command_success'] = "Příkaz pro frontu úspěšně dokončen";
@ -251,7 +256,7 @@ $lang['user']['create_syncjob'] = 'Vytvořit novou synchronizační úlohu';
$lang['start']['mailcow_apps_detail'] = 'Použijte mailcow aplikace pro přístup k vašim e-mailům, kalendáři, kontaktům a dalším funkcím.';
$lang['start']['mailcow_panel_detail'] = '<b>Administrátoři domén</b> mohou vytvářet, upravovat nebo mazat mailboxy a aliasy. Dále mohou upravovat parametry domény a zobrazovat další informace o jejich přidělených doménách.<br>
<b>Uživatelé<b> mohou vytvářet časově omezené aliasy (spam aliases), měnit jejich heslo a nastavovat spam filtr.';
<b>Uživatelé</b> mohou vytvářet časově omezené aliasy (spam aliases), měnit jejich heslo a nastavovat spam filtr.';
$lang['start']['imap_smtp_server_auth_info'] = 'Použijte prosím vaší celou e-mailovou adresu a způsob ověřování PLAIN.<br>
Vaše přihlašovací údaje budou zašifrovány na straně serveru.';
$lang['start']['help'] = 'Zobrazit/Skrýt panel nápovědy';
@ -331,7 +336,6 @@ Každý filtr bude zpracován v daném pořadí. Ani chyba při vykonávání sk
Prefilter Uživatelské skripty Postfilter <a href="https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/sieve_after" target="_blank">global sieve postfilter</a>';
$lang['info']['no_action'] = 'Není použitelná žádná akce';
$lang['edit']['syncjob'] = 'Upravit synchronizační úlohu';
$lang['edit']['client_id'] = 'ID klienta';
$lang['edit']['client_secret'] = 'Client secret';
@ -387,6 +391,7 @@ $lang['edit']['dont_check_sender_acl'] = "Disable sender check for domain %s (+
$lang['edit']['multiple_bookings'] = 'Vícenásobné rezervace';
$lang['edit']['kind'] = 'Druh';
$lang['edit']['resource'] = 'Zdroje';
$lang['edit']['relayhost'] = 'Přeposílání závislé na odesílateli';
$lang['acl']['spam_alias'] = 'Dočasné aliasy';
$lang['acl']['tls_policy'] = 'Pravidla TLS';
@ -400,14 +405,17 @@ $lang['acl']['quarantine'] = 'Karanténa';
$lang['acl']['login_as'] = 'Přihlásit jako uživatel poštovní schránky';
$lang['acl']['bcc_maps'] = 'BCC maps';
$lang['acl']['filters'] = 'Filtry';
$lang['acl']['ratelimit'] = 'Omezování provozu (Rate limit)';
$lang['acl']['ratelimit'] = 'Omezování provozu';
$lang['acl']['recipient_maps'] = 'Recipient maps';
$lang['acl']['prohibited'] = 'Zakázáno z důvodu ACL';
$lang['add']['generate'] = 'generovat';
$lang['add']['syncjob'] = 'Přidat synchronizační úlohu';
$lang['add']['syncjob_hint'] = 'Upozornění: Heslo bude uloženo v prostém textu!';
$lang['add']['hostname'] = 'Jméno hostitele (Hostname)';
$lang['add']['hostname'] = 'Jméno hostitele (Host)';
$lang['add']['destination'] = 'Cíl';
$lang['add']['nexthop'] = 'Další skok (Next hop)';
$lang['edit']['nexthop'] = 'Další skok (Next hop)';
$lang['add']['port'] = 'Port';
$lang['add']['username'] = 'Jméno uživatele';
$lang['add']['enc_method'] = 'Metoda šifrování';
@ -474,7 +482,6 @@ $lang['mailbox']['sync_jobs'] = 'Synchronizační úlohy';
$lang['mailbox']['inactive'] = 'Neaktivní';
$lang['edit']['validate_save'] = 'Ověřit a uložit';
$lang['login']['username'] = 'Jméno uživatele';
$lang['login']['password'] = 'Heslo';
$lang['login']['login'] = 'Přihlásit';
@ -582,24 +589,46 @@ $lang['admin']['no_record'] = 'Žádný záznam';
$lang['admin']['filter_table'] = 'Tabulka filtrů';
$lang['admin']['empty'] = 'Žádné výsledky';
$lang['admin']['time'] = 'Čas';
$lang['admin']['last_applied'] = 'Naposledy použité';
$lang['admin']['reset_limit'] = 'Odebrat hash';
$lang['admin']['hash_remove_info'] = 'Odebrání hashe omezování provozu (pokud stále existuje) vyresetuje kompletně jeho čítač.<br>
Každý hash je označen jedinečnou barvou.';
$lang['warning']['hash_not_found'] = 'Hash nenalezen';
$lang['success']['hash_deleted'] = 'Hash byl smazán';
$lang['admin']['authed_user'] = 'Přihlášený uživatel';
$lang['admin']['priority'] = 'Priorita';
$lang['admin']['message'] = 'Zpráva';
$lang['admin']['rate_name'] = 'Název (Rate name)';
$lang['admin']['refresh'] = 'Obnovit';
$lang['admin']['to_top'] = 'Zpět na začátek';
$lang['admin']['in_use_by'] = 'Používáno';
$lang['admin']['forwarding_hosts'] = 'Přesměrování klientů (Forwarding Hosts)';
$lang['admin']['forwarding_hosts_hint'] = 'Příchozí zprávy jsou bezpodmínečně akceptovány od všech zde uvedených klientů. Tito klienti nebudou kontrolováni proti DNSBL nebo podrobeni greylistingu. Spam obdržený od těchto klientů nebude nikdy odmítnut, ale příležitostně může být uložen do složky se spamem. Nejčastějším účelem je zadat poštovní servery, pro které jste nastavili pravidlo, které předává příchozí e-maily na váš poštovní server.';
$lang['admin']['forwarding_hosts_add_hint'] = 'Lze zadat IPv4/IPv6 adresy, sítě ve formátu CIDR, názvy klientů (které budou převedeny na IP adresy) nebo názvy domén (které budou převedeny na IP dotazováním se DNS na SPF záznam nebo pokud neexistuje tak na MX záznam).';
$lang['admin']['relayhosts_hint'] = 'Nastavte zde nadřazené SMTP servery (relayhosts), pro možnost je vybrat v okně pro nastavení domény.';
$lang['admin']['add_relayhost_add_hint'] = 'Upozornění: autentizační data budou uložena jako prostý text.';
$lang['admin']['relayhosts_hint'] = 'Nastavte zde přeposílání závislé na odesílateli (sender-dependent transports), pro možnost je vybrat v okně pro nastavení domény.<br>
Služba přeposílání je vždy "smtp:". Individuální uživatelská nastavení odchozích TLS politik jsou také povolena.';
$lang['admin']['transports_hint'] = 'Položky seznamu přeposílání (transport map) <b>přetěžují</b> položky seznamu přeposílání závislém na odesílateli (sender-dependent transport)</b>.<br>
Individuální uživatelská nastavení odchozích TLS politik jsou ignorována a lze je vynutit přes přetěžování odchozích TLS pravidel (TLS policy map overrides). Služba přeposílání je vždy "smtp:".<br>
Pro zjištění přihlašovacích údajů dalšího skoku "[host]:25" se Postfix <b>vždy</b> dotáže na "nexthop" před hledáním "[nexthop]:25". Toto chování znemožnuje použít "nexthop" a "[nexthop]:25" současně.';
$lang['admin']['add_relayhost_hint'] = 'Upozornění: přihlašovací údaje (pokud existují) budou uloženy jako prostý text.';
$lang['admin']['add_transports_hint'] = 'Upozornění: přihlašovací údaje budou uloženy jako prostý text.';
$lang['admin']['host'] = 'Klient (Host)';
$lang['admin']['source'] = 'Zdroj';
$lang['admin']['add_forwarding_host'] = 'Přidat přesměrování klientů (Forwarding Hosts)';
$lang['admin']['add_relayhost'] = 'Přidat nadřazený SMTP server (Relayhost)';
$lang['admin']['add_relayhost'] = 'Přidat přeposílání závislé na odesílateli (sender-dependent transport)';
$lang['admin']['add_transport'] = 'Přidat přeposílání (Transport)';
$lang['admin']['relayhosts'] = 'Přeposílání závislé na odesílateli (Sender-dependent transports)';
$lang['admin']['transport_maps'] = 'Přeposílání (Transport Maps)';
$lang['admin']['routing'] = 'Směrování';
$lang['admin']['credentials_transport_warning'] = '<b>Upozornění</b>: Přidání položky do seznamu přeposílání aktualizuje také přihlašovací údaje všech záznamů s odpovídajícím sloupcem (nexthop).';
$lang['admin']['destination'] = 'Cíl';
$lang['admin']['nexthop'] = 'Další skok (Next hop)';
$lang['success']['forwarding_host_removed'] = "Přesměrovaný klient %s byl odebrán";
$lang['success']['forwarding_host_added'] = "Přesměrovaný klient %s byl přidán";
$lang['success']['relayhost_removed'] = "Nadřazený SMTP server (Relayhost) %s byl odebrán";
$lang['success']['relayhost_added'] = "Nadřazený SMTP server (Relayhost) %s byl přidán";
$lang['success']['relayhost_removed'] = "Položka seznamu přeposílání %s byla odebrána";
$lang['success']['relayhost_added'] = "Položky seznamu přeposílání %s byla přidána";
$lang['diagnostics']['dns_records'] = 'DNS záznamy';
$lang['diagnostics']['dns_records_24hours'] = 'Upozornění: Změnám provedeným v systému DNS může trvat až 24 hodin, než se na této stránce správně zobrazí jejich aktuální stav. Tato stránka je určena pro snadné zjištění, jak nakonfigurovat DNS záznamy a zda jsou všechny vaše záznamy správně uloženy.';
$lang['diagnostics']['dns_records_name'] = 'Název';
@ -624,6 +653,9 @@ $lang['admin']['quarantine'] = "Karanténa";
$lang['admin']['quarantine_retention_size'] = "Počet zadržených zpráv na poštovní schránku<br />0 znamená <b>neaktivní</b>!";
$lang['admin']['quarantine_max_size'] = "Maximální velikost v MiB (větší prvky budou smazány)<br />0 <b>neznamená</b> neomezeno!";
$lang['admin']['quarantine_exclude_domains'] = "Vyloučené domény a doménové aliasy:";
$lang['admin']['quarantine_release_format'] = "Formát propuštěných položek:";
$lang['admin']['quarantine_release_format_raw'] = "Nezměněný originál";
$lang['admin']['quarantine_release_format_att'] = "Jako příloha";
$lang['admin']['ui_texts'] = "Úpravy UI textů";
$lang['admin']['help_text'] = "Přetíží text nápovědy pod přihlašovacím formulářem (HTML povoleno)";

View File

@ -29,6 +29,11 @@ $lang['success']['verified_u2f_login'] = "U2F Anmeldung verifiziert";
$lang['success']['verified_yotp_login'] = "Yubico OTP Anmeldung verifiziert";
$lang['danger']['yotp_verification_failed'] = "Yubico OTP Verifizierung fehlgeschlagen: %s";
$lang['danger']['ip_list_empty'] = "Liste erlaubter IPs darf nicht leer sein";
$lang['danger']['invalid_destination'] = "Ziel-Format ist ungültig";
$lang['danger']['invalid_nexthop'] = "Next Hop ist ungültig";
$lang['danger']['invalid_nexthop_authenticated'] = 'Dieser Next Hop existiert bereits mit abweichenden Authentifizierungsdaten. Die bestehenden Authentifizierungsdaten dieses "Next Hops" müssen vorab angepasst werden.';
$lang['danger']['next_hop_interferes'] = "%s verhindert das Hinzufügen von Next Hop %s";
$lang['danger']['next_hop_interferes_any'] = "Ein vorhandener Eintrag verhindert das Hinzufügen von Next Hop %s";
$lang['danger']['rspamd_ui_pw_length'] = "Rspamd UI Passwort muss mindestens 6 Zeichen lang sein";
$lang['success']['rspamd_ui_pw_set'] = "Rspamd UI Passwort wurde gesetzt";
$lang['success']['queue_command_success'] = "Queue-Aufgabe erfolgreich ausgeführt";
@ -65,7 +70,7 @@ $lang['success']['settings_map_added'] = "Regel wurde gespeichert";
$lang['danger']['settings_map_invalid'] = "Regel ID %s ist ungültig";
$lang['success']['settings_map_removed'] = "Regeln wurden entfernt: %s";
$lang['danger']['invalid_host'] = "Ungültiger Host: %s";
$lang['danger']['relayhost_invalid'] = "Relayhost %s ist ungültig";
$lang['danger']['relayhost_invalid'] = "Mapeintrag %s ist ungültig";
$lang['success']['saved_settings'] = "Regel wurde gespeichert";
$lang['danger']['dkim_domain_or_sel_invalid'] = 'DKIM-Domain oder Selektor nicht korrekt: %s';
@ -392,7 +397,10 @@ $lang['acl']['prohibited'] = 'Untersagt durch Richtlinie';
$lang['add']['generate'] = 'generieren';
$lang['add']['syncjob'] = 'Syncjob hinzufügen';
$lang['add']['syncjob_hint'] = 'Passwörter werden unverschlüsselt abgelegt!';
$lang['add']['hostname'] = 'Servername';
$lang['add']['hostname'] = 'Host';
$lang['add']['destination'] = 'Ziel';
$lang['add']['nexthop'] = 'Next Hop';
$lang['edit']['nexthop'] = 'Next Hop';
$lang['add']['port'] = 'Port';
$lang['add']['username'] = 'Benutzername';
$lang['add']['enc_method'] = 'Verschlüsselung';
@ -556,20 +564,46 @@ $lang['admin']['no_record'] = 'Kein Eintrag';
$lang['admin']['filter_table'] = 'Tabelle Filtern';
$lang['admin']['empty'] = 'Keine Einträge vorhanden';
$lang['admin']['time'] = 'Zeit';
$lang['admin']['last_applied'] = 'Zuletzt angewendet';
$lang['admin']['reset_limit'] = 'Hash entfernen';
$lang['admin']['hash_remove_info'] = 'Das Entfernen eines Ratelimit Hashes - sofern noch existent - bewirkt den Reset gezählter Nachrichten dieses Elements.<br>
Jeder Hash wird durch eine eindeutige Farbe gekennzeichnet.';
$lang['warning']['hash_not_found'] = 'Hash nicht gefunden';
$lang['success']['hash_deleted'] = 'Hash wurde gelöscht';
$lang['admin']['authed_user'] = 'Auth. Benutzer';
$lang['admin']['priority'] = 'Gewichtung';
$lang['admin']['refresh'] = 'Neu laden';
$lang['admin']['to_top'] = 'Nach oben';
$lang['admin']['in_use_by'] = 'Verwendet von';
$lang['admin']['rate_name'] = 'Rate name';
$lang['admin']['message'] = 'Nachricht';
$lang['admin']['forwarding_hosts'] = 'Weiterleitungs-Hosts';
$lang['admin']['forwarding_hosts_hint'] = 'Eingehende Nachrichten werden von den hier gelisteten Hosts bedingungslos akzeptiert. Diese Hosts werden dann nicht mit DNSBLs abgeglichen oder Greylisting unterworfen. Von ihnen empfangener Spam wird nie abgelehnt, optional kann er aber in den Spam-Ordner einsortiert werden. Die übliche Verwendung für diese Funktion ist, um Mailserver anzugeben, auf denen eine Weiterleitung zu Ihrem mailcow-Server eingerichtet wurde.';
$lang['admin']['forwarding_hosts_add_hint'] = 'Sie können entweder IPv4/IPv6-Adressen, Netzwerke in CIDR-Notation, Hostnamen (die zu IP-Adressen aufgelöst werden), oder Domainnamen (die zu IP-Adressen aufgelöst werden, indem ihr SPF-Record abgefragt wird oder, in dessen Abwesenheit, ihre MX-Records) angeben.';
$lang['admin']['relayhosts_hint'] = 'Erstellen Sie Relayhosts, um diese im Einstellungsdialog einer Domain auszuwählen.';
$lang['admin']['add_relayhost_add_hint'] = 'Bitte beachten Sie, dass Relayhost Anmeldedaten im Klartext gespeichert werden.';
$lang['admin']['relayhosts_hint'] = 'Erstellen Sie senderabhängige Transporte, um diese im Einstellungsdialog einer Domain auszuwählen.<br>
Der Transporttyp lautet immer "smtp:". Benutzereinstellungen bezüglich Verschlüsselungsrichtlinie werden beim Transport berücksichtigt.';
$lang['admin']['transports_hint'] = 'Transport Maps <b>überwiegen</b> senderabhängige Transport Maps und ignorieren die individuellen Einstellungen eines Benutzers bezüglich Verschlüsselungsrichtlinie, da der Absender bei Ermittlung der Transportregel nicht berücksichtigt wird.<br>
Der Transport erfolgt immer via "smtp:".<br>
Ein Eintrag in der TLS Policy Map kann eine Verschlüsselung erzwingen.<br>
Die Authentifizierung wird anhand des Host Parameters ermittelt, hierbei würde bei einem beispielhaften Next Hop "[host]:25" immer zuerst "host" abfragt und <b>erst im Anschluss</b> "[host]:25".<br>
Dieses Verhalten schließt die <b>gleichzeitige Verwendung</b> von Einträgen der Art "host" sowie "[host]:25" aus.';
$lang['admin']['add_relayhost_hint'] = 'Bitte beachten Sie, dass Anmeldedaten klartext gespeichert werden.<br>
Angelegte Transporte dieser Art sind <b>senderabhängig</b> und müssen erst einer Domain zugewiesen werden, bevor sie als Transport verwendet werden.<br>
Diese Einstellungen entsprechen demach <i>nicht</i> dem "relayhost" Parameter in Postfix.';
$lang['admin']['add_transports_hint'] = 'Bitte beachten Sie, dass Anmeldedaten klartext gespeichert werden.';
$lang['admin']['host'] = 'Host';
$lang['admin']['source'] = 'Quelle';
$lang['admin']['add_forwarding_host'] = 'Weiterleitungs-Host hinzufügen';
$lang['admin']['add_relayhost'] = 'Relayhost hinzufügen';
$lang['admin']['add_relayhost'] = 'Senderabhängigen Transport hinzufügen';
$lang['admin']['add_transport'] = 'Transport hinzufügen';
$lang['admin']['relayhosts'] = 'Senderabhängige Transport Maps';
$lang['admin']['transport_maps'] = 'Transport Maps';
$lang['admin']['routing'] = 'Routing';
$lang['admin']['credentials_transport_warning'] = '<b>Warnung</b>: Das Hinzufügen einer neuen Regel bewirkt die Aktualisierung der Authentifizierungsdaten aller vorhandenen Einträge mit identischem Host.';
$lang['admin']['destination'] = 'Ziel';
$lang['admin']['nexthop'] = 'Next Hop';
$lang['admin']['api_allow_from'] = "IP-Adressen für Zugriff";
$lang['admin']['api_key'] = "API-Key";
$lang['admin']['activate_api'] = "API aktivieren";
@ -586,8 +620,8 @@ $lang['admin']['quarantine_exclude_domains'] = "Domains und Alias-Domains aussch
$lang['success']['forwarding_host_removed'] = "Weiterleitungs-Host %s wurde entfernt";
$lang['success']['forwarding_host_added'] = "Weiterleitungs-Host %s wurde hinzugefügt";
$lang['success']['relayhost_removed'] = "Relayhost %s wurde entfernt";
$lang['success']['relayhost_added'] = "Relayhost %s wurde hinzugefügt";
$lang['success']['relayhost_removed'] = "Mapeintrag %s wurde entfernt";
$lang['success']['relayhost_added'] = "Mapeintrag %s wurde hinzugefügt";
$lang['diagnostics']['dns_records'] = 'DNS-Einträge';
$lang['diagnostics']['dns_records_24hours'] = 'Bitte beachten Sie, dass es bis zu 24 Stunden dauern kann, bis Änderungen an Ihren DNS-Einträgen als aktueller Status auf dieser Seite dargestellt werden. Diese Seite ist nur als Hilfsmittel gedacht, um die korrekten Werte für DNS-Einträge anzuzeigen und zu überprüfen, ob die Daten im DNS hinterlegt sind.';
$lang['diagnostics']['dns_records_name'] = 'Name';

View File

@ -30,6 +30,11 @@ $lang['success']['verified_u2f_login'] = "Verified U2F login";
$lang['success']['verified_yotp_login'] = "Verified Yubico OTP login";
$lang['danger']['yotp_verification_failed'] = "Yubico OTP verification failed: %s";
$lang['danger']['ip_list_empty'] = "List of allowed IPs cannot be empty";
$lang['danger']['invalid_destination'] = "Destination format is invalid";
$lang['danger']['invalid_nexthop'] = "Next hop format is invalid";
$lang['danger']['invalid_nexthop_authenticated'] = "Next hops exists with different credentials, please update the existing credentials for this next hop first.";
$lang['danger']['next_hop_interferes'] = "%s interferes with nexthop %s";
$lang['danger']['next_hop_interferes_any'] = "An existing next hop interferes with %s";
$lang['danger']['rspamd_ui_pw_length'] = "Rspamd UI password should be at least 6 chars long";
$lang['success']['rspamd_ui_pw_set'] = "Rspamd UI password successfully set";
$lang['success']['queue_command_success'] = "Queue command completed successfully";
@ -66,7 +71,7 @@ $lang['success']['settings_map_added'] = "Added settings map entry";
$lang['danger']['settings_map_invalid'] = "Settings map ID %s invalid";
$lang['success']['settings_map_removed'] = "Removed settings map ID %s";
$lang['danger']['invalid_host'] = "Invalid host specified: %s";
$lang['danger']['relayhost_invalid'] = "Relayhost %s is invalid";
$lang['danger']['relayhost_invalid'] = "Map entry %s is invalid";
$lang['success']['saved_settings'] = "Saved settings";
$lang['success']['db_init_complete'] = "Database initialization completed";
@ -384,6 +389,7 @@ $lang['edit']['dont_check_sender_acl'] = "Disable sender check for domain %s (+
$lang['edit']['multiple_bookings'] = 'Multiple bookings';
$lang['edit']['kind'] = 'Kind';
$lang['edit']['resource'] = 'Resource';
$lang['edit']['relayhost'] = 'Sender-dependent transports';
$lang['acl']['spam_alias'] = 'Temporary aliases';
$lang['acl']['tls_policy'] = 'TLS policy';
@ -405,7 +411,10 @@ $lang['acl']['prohibited'] = 'Prohibited by ACL';
$lang['add']['generate'] = 'generate';
$lang['add']['syncjob'] = 'Add sync job';
$lang['add']['syncjob_hint'] = 'Be aware that passwords need to be saved plain-text!';
$lang['add']['hostname'] = 'Hostname';
$lang['add']['hostname'] = 'Host';
$lang['add']['destination'] = 'Destination';
$lang['add']['nexthop'] = 'Next hop';
$lang['edit']['nexthop'] = 'Next hop';
$lang['add']['port'] = 'Port';
$lang['add']['username'] = 'Username';
$lang['add']['enc_method'] = 'Encryption method';
@ -580,24 +589,46 @@ $lang['admin']['no_record'] = 'No record';
$lang['admin']['filter_table'] = 'Filter table';
$lang['admin']['empty'] = 'No results';
$lang['admin']['time'] = 'Time';
$lang['admin']['last_applied'] = 'Last applied';
$lang['admin']['reset_limit'] = 'Remove hash';
$lang['admin']['hash_remove_info'] = 'Removing a ratelimit hash (if still existing) will reset its counter completely.<br>
Each hash is indicated by an individual color.';
$lang['warning']['hash_not_found'] = 'Hash not found';
$lang['success']['hash_deleted'] = 'Hash deleted';
$lang['admin']['authed_user'] = 'Auth. user';
$lang['admin']['priority'] = 'Priority';
$lang['admin']['message'] = 'Message';
$lang['admin']['rate_name'] = 'Rate name';
$lang['admin']['refresh'] = 'Refresh';
$lang['admin']['to_top'] = 'Back to top';
$lang['admin']['in_use_by'] = 'In use by';
$lang['admin']['forwarding_hosts'] = 'Forwarding Hosts';
$lang['admin']['forwarding_hosts_hint'] = 'Incoming messages are unconditionally accepted from any hosts listed here. These hosts are then not checked against DNSBLs or subjected to greylisting. Spam received from them is never rejected, but optionally it can be filed into the Junk folder. The most common use for this is to specify mail servers on which you have set up a rule that forwards incoming emails to your mailcow server.';
$lang['admin']['forwarding_hosts_add_hint'] = 'You can either specify IPv4/IPv6 addresses, networks in CIDR notation, host names (which will be resolved to IP addresses), or domain names (which will be resolved to IP addresses by querying SPF records or, in their absence, MX records).';
$lang['admin']['relayhosts_hint'] = 'Define relayhosts here to be able to select them in a domains configuration dialog.';
$lang['admin']['add_relayhost_add_hint'] = 'Please be aware that relayhost authentication data will be stored as plain text.';
$lang['admin']['relayhosts_hint'] = 'Define sender-dependent transports to be able to select them in a domains configuration dialog.<br>
The transport service is always "smtp:". A users individual outbound TLS policy setting is taken into account.';
$lang['admin']['transports_hint'] = 'A transport map entry <b>overrules</b> a sender-dependent transport map</b>.<br>
Outbound TLS policy settings per-user are ignored and can only be enfored by TLS policy map entries. The transport service is always "smtp:".<br>
To determine credentials for an exemplary next hop "[host]:25", Postfix <b>always</b> queries for "nexthop" before searching for "[nexthop]:25". This behavior makes it impossible to use "nexthop" and "[nexthop]:25" at the same time.';
$lang['admin']['add_relayhost_hint'] = 'Please be aware that authentication data, if any, will be stored as plain text.';
$lang['admin']['add_transports_hint'] = 'Please be aware that authentication data, if any, will be stored as plain text.';
$lang['admin']['host'] = 'Host';
$lang['admin']['source'] = 'Source';
$lang['admin']['add_forwarding_host'] = 'Add Forwarding Host';
$lang['admin']['add_relayhost'] = 'Add Relayhost';
$lang['admin']['add_forwarding_host'] = 'Add forwarding host';
$lang['admin']['add_relayhost'] = 'Add sender-dependent transport';
$lang['admin']['add_transport'] = 'Add transport';
$lang['admin']['relayhosts'] = 'Sender-dependent transports';
$lang['admin']['transport_maps'] = 'Transport Maps';
$lang['admin']['routing'] = 'Routing';
$lang['admin']['credentials_transport_warning'] = '<b>Warning</b>: Adding a new transport map entry will update the credentials for all entries with a matching nexthop column.';
$lang['admin']['destination'] = 'Destination';
$lang['admin']['nexthop'] = 'Next hop';
$lang['success']['forwarding_host_removed'] = "Forwarding host %s has been removed";
$lang['success']['forwarding_host_added'] = "Forwarding host %s has been added";
$lang['success']['relayhost_removed'] = "Relayhost %s has been removed";
$lang['success']['relayhost_added'] = "Relayhost %s has been added";
$lang['success']['relayhost_removed'] = "Map entry %s has been removed";
$lang['success']['relayhost_added'] = "Map entry %s has been added";
$lang['diagnostics']['dns_records'] = 'DNS Records';
$lang['diagnostics']['dns_records_24hours'] = 'Please note that changes made to DNS may take up to 24 hours to correctly have their current state reflected on this page. It is intended as a way for you to easily see how to configure your DNS records and to check whether all your records are correctly stored in DNS.';
$lang['diagnostics']['dns_records_name'] = 'Name';

View File

@ -411,8 +411,10 @@ $lang['edit']['delete1'] = 'Verwijder van oorsprong wanneer voltooid';
$lang['edit']['delete2'] = 'Verwijder berichten die zich niet in de oorsprong bevinden';
$lang['add']['custom_params'] = 'Aangepaste parameters';
$lang['add']['subscribeall'] = 'Abonneer op alle mappen';
$lang['add']['timeout1'] = 'Time-out voor verbinding met externe host';
$lang['add']['timeout2'] = 'Time-out voor verbinding met lokale host';
$lang['add']['timeout1'] = 'Time-out voor verbinding met externe hosts';
$lang['add']['timeout2'] = 'Time-out voor verbinding met lokale hosts';
$lang['edit']['timeout1'] = 'Time-out voor verbinding met externe hosts';
$lang['edit']['timeout2'] = 'Time-out voor verbinding met lokale hosts';
$lang['add']['domain_matches_hostname'] = 'Domein %s komt overeen met hostname';
$lang['add']['domain'] = 'Domein';
@ -521,7 +523,7 @@ $lang['admin']['dkim_add_key'] = 'Voeg DKIM-sleutel toe';
$lang['admin']['dkim_keys'] = 'DKIM-sleutels';
$lang['admin']['dkim_private_key'] = 'Privésleutel';
$lang['admin']['dkim_domains_wo_keys'] = "Selecteer domeinen met ontbrekende sleutels";
$lang['admin']['dkim_domains_selector'] = "Onderdeel";
$lang['admin']['dkim_domains_selector'] = "Noemer";
$lang['admin']['add'] = 'Toevoegen';
$lang['add']['add_domain_restart'] = 'Voeg domein toe en herstart SOGo';
$lang['add']['add_domain_only'] = 'Voeg enkel domein toe';
@ -553,7 +555,7 @@ $lang['admin']['flush_queue'] = 'Leeg wachtrij';
$lang['admin']['delete_queue'] = 'Verwijder alles';
$lang['admin']['queue_deliver_mail'] = 'Lever af';
$lang['admin']['queue_hold_mail'] = 'Houd vast';
$lang['admin']['queue_unhold_mail'] = 'Vrijgeven';
$lang['admin']['queue_unhold_mail'] = 'Geef vrij';
$lang['admin']['username'] = 'Gebruikersnaam';
$lang['admin']['edit'] = 'Wijzig';
$lang['admin']['remove'] = 'Verwijder';
@ -608,6 +610,9 @@ $lang['admin']['quarantine'] = "Quarantaine";
$lang['admin']['quarantine_retention_size'] = "Maximale retenties per postvak<br />Gebruik 0 om deze functionaliteit <b>uit te zetten</b>.";
$lang['admin']['quarantine_max_size'] = "Maximale grootte in MiB (mail die de limiet overschrijdt zal worden verwijderd)<br />0 betekent <b>niet</b> onbeperkt!";
$lang['admin']['quarantine_exclude_domains'] = "Sluit domeinen en aliasdomeinen uit:";
$lang['admin']['quarantine_release_format'] = "Vrijgegeven items worden verstuurd als:";
$lang['admin']['quarantine_release_format_raw'] = "Origineel";
$lang['admin']['quarantine_release_format_att'] = "Bijlage";
$lang['admin']['ui_texts'] = "UI-labels en teksten";
$lang['admin']['help_text'] = "Pas hulpteksten onder inlogvenster aan (HTML toegestaan)";
@ -648,7 +653,7 @@ $lang['danger']['imagick_exception'] = "Error: Er is een probleem opgetreden met
$lang['quarantine']['quarantine'] = "Quarantaine";
$lang['quarantine']['learn_spam_delete'] = "Onthoud als spam en verwijder";
$lang['quarantine']['qinfo'] = 'Het quarantainesysteem slaat geweigerde e-mail op, terwijl het voor de afzender lijkt alsof deze <em>niet</em> ontvangen is.<br>"' . $lang['quarantine']['learn_spam_delete'] . '" traint het systeem om soortgelijke e-mails in de toekomst direct als spam te markeren.<br>Wees er van bewust dat wanneer er meerdere berichten worden onderzocht, dit mogelijk enige tijd kan duren.';
$lang['quarantine']['release'] = "Vrijgeven";
$lang['quarantine']['release'] = "Geef vrij";
$lang['quarantine']['empty'] = 'Geen resultaten';
$lang['quarantine']['toggle_all'] = 'Selecteer alles';
$lang['quarantine']['quick_actions'] = 'Handelingen';

View File

@ -151,17 +151,18 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
</div>
</div>
</div><!-- add admin modal -->
<!-- test relayhost modal -->
<div class="modal fade" id="testRelayhostModal" tabindex="-1" role="dialog" aria-hidden="true">
<!-- test transport modal -->
<div class="modal fade" id="testTransportModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span></button>
<h3 class="modal-title"><span class="glyphicon glyphicon-stats"></span> Relayhost</h3>
<h3 class="modal-title"><span class="glyphicon glyphicon-stats"></span> Transport</h3>
</div>
<div class="modal-body">
<form class="form-horizontal" data-cached-form="true" id="test_relayhost_form" role="form" method="post">
<input type="hidden" class="form-control" name="relayhost_id" id="relayhost_id">
<form class="form-horizontal" data-cached-form="true" id="test_transport_form" role="form" method="post">
<input type="hidden" class="form-control" name="transport_id" id="transport_id">
<input type="hidden" class="form-control" name="transport_type" id="transport_type">
<div class="form-group">
<label class="control-label col-sm-2" for="mail_from"><?=$lang['admin']['relay_from'];?></label>
<div class="col-sm-10">
@ -170,16 +171,16 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-default" id="test_relayhost" href="#"><?=$lang['admin']['relay_run'];?></button>
<button class="btn btn-default" id="test_transport" href="#"><?=$lang['admin']['relay_run'];?></button>
</div>
</div>
</form>
<hr>
<div id="test_relayhost_result" style="font-size:10pt">-</div>
<div id="test_transport_result" style="font-size:10pt">-</div>
</div>
</div>
</div>
</div><!-- test relayhost modal -->
</div><!-- test transport modal -->
<!-- priv key modal -->
<div class="modal fade" id="showDKIMprivKey" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">

View File

@ -131,7 +131,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
</div>
<hr>
<div class="form-group">
<label class="control-label col-sm-2" for="rl_frame">Ratelimit</label>
<label class="control-label col-sm-2" for="rl_frame"><?=$lang['acl']['ratelimit'];?></label>
<div class="col-sm-7">
<input name="rl_value" type="number" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" class="form-control" placeholder="disabled">
</div>
@ -328,7 +328,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
</div>
<hr>
<div class="form-group">
<label class="control-label col-sm-2" for="rl_frame">Ratelimit</label>
<label class="control-label col-sm-2" for="rl_frame"><?=$lang['acl']['ratelimit'];?></label>
<div class="col-sm-7">
<input name="rl_value" type="number" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" class="form-control" placeholder="disabled">
</div>

View File

@ -40,7 +40,7 @@ services:
- mysql
redis-mailcow:
image: redis:4-alpine
image: redis:5-alpine
volumes:
- redis-vol-1:/data/
restart: always
@ -72,7 +72,7 @@ services:
- clamd
rspamd-mailcow:
image: mailcow/rspamd:1.32
image: mailcow/rspamd:1.33
build: ./data/Dockerfiles/rspamd
stop_grace_period: 30s
depends_on:
@ -95,7 +95,7 @@ services:
- rspamd
php-fpm-mailcow:
image: mailcow/phpfpm:1.26
image: mailcow/phpfpm:1.29
build: ./data/Dockerfiles/phpfpm
command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
depends_on:
@ -138,7 +138,7 @@ services:
- phpfpm
sogo-mailcow:
image: mailcow/sogo:1.44
image: mailcow/sogo:1.49
build: ./data/Dockerfiles/sogo
environment:
- DBNAME=${DBNAME}
@ -163,7 +163,7 @@ services:
- sogo
dovecot-mailcow:
image: mailcow/dovecot:1.48
image: mailcow/dovecot:1.52
build: ./data/Dockerfiles/dovecot
cap_add:
- NET_BIND_SERVICE
@ -208,7 +208,7 @@ services:
- dovecot
postfix-mailcow:
image: mailcow/postfix:1.27
image: mailcow/postfix:1.29
build: ./data/Dockerfiles/postfix
volumes:
- ./data/conf/postfix:/opt/postfix/conf
@ -344,7 +344,7 @@ services:
- /lib/modules:/lib/modules:ro
watchdog-mailcow:
image: mailcow/watchdog:1.29
image: mailcow/watchdog:1.32
# Debug
#command: /watchdog.sh
build: ./data/Dockerfiles/watchdog
@ -393,6 +393,22 @@ services:
- dockerapi
ipv6nat:
depends_on:
- unbound-mailcow
- mysql-mailcow
- redis-mailcow
- clamd-mailcow
- rspamd-mailcow
- php-fpm-mailcow
- sogo-mailcow
- dovecot-mailcow
- postfix-mailcow
- memcached-mailcow
- nginx-mailcow
- acme-mailcow
- netfilter-mailcow
- watchdog-mailcow
- dockerapi-mailcow
image: robbertkl/ipv6nat
restart: always
privileged: true

View File

@ -159,7 +159,7 @@ USE_WATCHDOG=n
LOG_LINES=9999
# Internal IPv4 /24 subnet, format n.n.n. (expands to n.n.n.0/24)
# Internal IPv4 /24 subnet, format n.n.n (expands to n.n.n.0/24)
IPV4_NETWORK=172.22.1

View File

@ -32,7 +32,11 @@ if [[ ${NC_PURGE} == "y" ]]; then
docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e \
"$(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "SELECT GROUP_CONCAT('DROP TABLE ', TABLE_SCHEMA, '.', TABLE_NAME SEPARATOR ';') FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'nc_%' AND TABLE_SCHEMA = '${DBNAME}';" -BN)"
docker exec -it $(docker ps -f name=redis-mailcow -q) /bin/sh -c 'redis-cli KEYS "*nextcloud*" | xargs redis-cli DEL'
docker exec -it $(docker ps -f name=redis-mailcow -q) /bin/sh -c ' cat <<EOF | redis-cli
SELECT 10
FLUSHDB
EOF
'
if [ -d ./data/web/nextcloud/config ]; then
mv ./data/web/nextcloud/config/ ./data/conf/nextcloud-config-folder-$(date +%s).bak
fi
@ -87,6 +91,7 @@ elif [[ ${NC_INSTALL} == "y" ]]; then
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ config:system:set redis host --value=redis --type=string; \
/web/nextcloud/occ config:system:set redis port --value=6379 --type=integer; \
/web/nextcloud/occ config:system:set redis timeout --value=0.0 --type=integer; \
/web/nextcloud/occ config:system:set redis dbindex --value=10 --type=integer; \
/web/nextcloud/occ config:system:set memcache.locking --value='\OC\Memcache\Redis' --type=string; \
/web/nextcloud/occ config:system:set memcache.local --value='\OC\Memcache\Redis' --type=string; \
/web/nextcloud/occ config:system:set trusted_domains 1 --value=${MAILCOW_HOSTNAME}; \

View File

@ -263,6 +263,8 @@ docker-compose down
# Silently fixing remote url from andryyy to mailcow
git remote set-url origin https://github.com/mailcow/mailcow-dockerized
echo -e "\e[32mCommitting current status...\e[0m"
[[ -z "$(git config user.name)" ]] && git config user.name moo
[[ -z "$(git config user.email)" ]] && git config user.email moo@cow.moo
git update-index --assume-unchanged data/conf/rspamd/override.d/worker-controller-password.inc
git add -u
git commit -am "Before update on ${DATE}" > /dev/null