Page MenuHomeWMGMC Issues

No OneTemporary

diff --git a/data/Dockerfiles/rspamd/Dockerfile b/data/Dockerfiles/rspamd/Dockerfile
index cb5c7d40..32e0fe30 100644
--- a/data/Dockerfiles/rspamd/Dockerfile
+++ b/data/Dockerfiles/rspamd/Dockerfile
@@ -1,27 +1,28 @@
FROM debian:stretch-slim
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive
ENV LC_ALL C
RUN apt-get update && apt-get install -y \
ca-certificates \
gnupg2 \
apt-transport-https \
- && apt-key adv --fetch-keys https://rspamd.com/apt-stable/gpg.key \
- && echo "deb https://rspamd.com/apt-stable/ stretch main" > /etc/apt/sources.list.d/rspamd.list \
+ && apt-key adv --fetch-keys https://rspamd.com/apt/gpg.key \
+ && echo "deb https://rspamd.com/apt/ stretch main" > /etc/apt/sources.list.d/rspamd.list \
&& apt-get update && apt-get install -y rspamd \
&& rm -rf /var/lib/apt/lists/* \
&& echo '.include $LOCAL_CONFDIR/local.d/rspamd.conf.local' > /etc/rspamd/rspamd.conf.local \
&& apt-get autoremove --purge \
&& apt-get clean \
&& mkdir -p /run/rspamd \
&& chown _rspamd:_rspamd /run/rspamd
COPY settings.conf /etc/rspamd/modules.d/settings.conf
COPY ratelimit.lua /usr/share/rspamd/lua/ratelimit.lua
+COPY lua_util.lua /usr/share/rspamd/lib/lua_util.lua
COPY docker-entrypoint.sh /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD /usr/bin/rspamd -f -u _rspamd -g _rspamd
diff --git a/data/Dockerfiles/rspamd/lua_util.lua b/data/Dockerfiles/rspamd/lua_util.lua
new file mode 100644
index 00000000..a9abd901
--- /dev/null
+++ b/data/Dockerfiles/rspamd/lua_util.lua
@@ -0,0 +1,152 @@
+local exports = {}
+local lpeg = require 'lpeg'
+
+local split_grammar = {}
+local function rspamd_str_split(s, sep)
+ local gr = split_grammar[sep]
+
+ if not gr then
+ local _sep = lpeg.P(sep)
+ local elem = lpeg.C((1 - _sep)^0)
+ local p = lpeg.Ct(elem * (_sep * elem)^0)
+ gr = p
+ split_grammar[sep] = gr
+ end
+
+ return gr:match(s)
+end
+
+exports.rspamd_str_split = rspamd_str_split
+
+local space = lpeg.S' \t\n\v\f\r'
+local nospace = 1 - space
+local ptrim = space^0 * lpeg.C((space^0 * nospace^1)^0)
+local match = lpeg.match
+exports.rspamd_str_trim = function(s)
+ return match(ptrim, s)
+end
+
+-- Robert Jay Gould http://lua-users.org/wiki/SimpleRound
+exports.round = function(num, numDecimalPlaces)
+ local mult = 10^(numDecimalPlaces or 0)
+ return math.floor(num * mult) / mult
+end
+
+exports.template = function(tmpl, keys)
+ local var_lit = lpeg.P { lpeg.R("az") + lpeg.R("AZ") + lpeg.R("09") + "_" }
+ local var = lpeg.P { (lpeg.P("$") / "") * ((var_lit^1) / keys) }
+ local var_braced = lpeg.P { (lpeg.P("${") / "") * ((var_lit^1) / keys) * (lpeg.P("}") / "") }
+
+ local template_grammar = lpeg.Cs((var + var_braced + 1)^0)
+
+ return lpeg.match(template_grammar, tmpl)
+end
+
+exports.remove_email_aliases = function(email_addr)
+ local function check_gmail_user(addr)
+ -- Remove all points
+ local no_dots_user = string.gsub(addr.user, '%.', '')
+ local cap, pluses = string.match(no_dots_user, '^([^%+][^%+]*)(%+.*)$')
+ if cap then
+ return cap, rspamd_str_split(pluses, '+'), nil
+ elseif no_dots_user ~= addr.user then
+ return no_dots_user,{},nil
+ end
+
+ return nil
+ end
+
+ local function check_address(addr)
+ if addr.user then
+ local cap, pluses = string.match(addr.user, '^([^%+][^%+]*)(%+.*)$')
+ if cap then
+ return cap, rspamd_str_split(pluses, '+'), nil
+ end
+ end
+
+ return nil
+ end
+
+ local function set_addr(addr, new_user, new_domain)
+ if new_user then
+ addr.user = new_user
+ end
+ if new_domain then
+ addr.domain = new_domain
+ end
+
+ if addr.domain then
+ addr.addr = string.format('%s@%s', addr.user, addr.domain)
+ else
+ addr.addr = string.format('%s@', addr.user)
+ end
+
+ if addr.name and #addr.name > 0 then
+ addr.raw = string.format('"%s" <%s>', addr.name, addr.addr)
+ else
+ addr.raw = string.format('<%s>', addr.addr)
+ end
+ end
+
+ local function check_gmail(addr)
+ local nu, tags, nd = check_gmail_user(addr)
+
+ if nu then
+ return nu, tags, nd
+ end
+
+ return nil
+ end
+
+ local function check_googlemail(addr)
+ local nd = 'gmail.com'
+ local nu, tags = check_gmail_user(addr)
+
+ if nu then
+ return nu, tags, nd
+ end
+
+ return nil, nil, nd
+ end
+
+ local specific_domains = {
+ ['gmail.com'] = check_gmail,
+ ['googlemail.com'] = check_googlemail,
+ }
+
+ if email_addr then
+ if email_addr.domain and specific_domains[email_addr.domain] then
+ local nu, tags, nd = specific_domains[email_addr.domain](email_addr)
+ if nu or nd then
+ set_addr(email_addr, nu, nd)
+
+ return nu, tags
+ end
+ else
+ local nu, tags, nd = check_address(email_addr)
+ if nu or nd then
+ set_addr(email_addr, nu, nd)
+
+ return nu, tags
+ end
+ end
+
+ return nil
+ end
+end
+
+exports.is_rspamc_or_controller = function(task)
+ local ua = task:get_request_header('User-Agent') or ''
+ local pwd = task:get_request_header('Password')
+ local is_rspamc = false
+ if tostring(ua) == 'rspamc' or pwd then is_rspamc = true end
+
+ return is_rspamc
+end
+
+local unpack_function = table.unpack or unpack
+exports.unpack = function(t)
+ return unpack_function(t)
+end
+
+return exports
diff --git a/data/Dockerfiles/rspamd/ratelimit.lua b/data/Dockerfiles/rspamd/ratelimit.lua
index d9e8f42a..e25ea42d 100644
--- a/data/Dockerfiles/rspamd/ratelimit.lua
+++ b/data/Dockerfiles/rspamd/ratelimit.lua
@@ -1,717 +1,723 @@
--[[
-Copyright (c) 2011-2015, Vsevolod Stakhov <vsevolod@highsecure.ru>
+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
--- A plugin that implements ratelimits using redis or kvstorage server
+-- A plugin that implements ratelimits using redis
-local E = {}
-
--- Default settings for limits, 1-st member is burst, second is rate and the third is numeric type
-local settings = {
-}
+local E, settings = {}, {}
+local N = 'ratelimit'
-- Senders that are considered as bounce
local bounce_senders = {'postmaster', 'mailer-daemon', '', 'null', 'fetchmail-daemon', 'mdaemon'}
-- Do not check ratelimits for these recipients
local whitelisted_rcpts = {'postmaster', 'mailer-daemon'}
local whitelisted_ip
local whitelisted_user
local max_rcpt = 5
local redis_params
local ratelimit_symbol
-- Do not delay mail after 1 day
-local max_delay = 24 * 3600
local use_ip_score = false
-local rl_prefix = 'rl'
+local rl_prefix = 'RL'
local ip_score_lower_bound = 10
local ip_score_ham_multiplier = 1.1
local ip_score_spam_divisor = 1.1
+local limits_hash
local message_func = function(_, limit_type)
return string.format('Ratelimit "%s" exceeded', limit_type)
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 user_keywords = {'user'}
+local redis_script_sha
+local redis_script = [[local bucket
+local limited = false
+local buckets = {}
+local queue_id = table.remove(ARGV)
+local now = table.remove(ARGV)
+
+local argi = 0
+for i = 1, #KEYS do
+ local key = KEYS[i]
+ local period = tonumber(ARGV[argi+1])
+ local limit = tonumber(ARGV[argi+2])
+ if not buckets[key] then
+ buckets[key] = {
+ max_period = period,
+ limits = { {period, limit} },
+ }
+ else
+ table.insert(buckets[key].limits, {period, limit})
+ if period > buckets[key].max_period then
+ buckets[key].max_period = period
+ end
+ end
+ argi = argi + 2
+end
+
+for k, v in pairs(buckets) do
+ local maxp = v.max_period
+ redis.call('ZREMRANGEBYSCORE', k, '-inf', now - maxp)
+ for _, lim in ipairs(v.limits) do
+ local period = lim[1]
+ local limit = lim[2]
+ local rate
+ if period == maxp then
+ rate = redis.call('ZCARD', k)
+ else
+ rate = redis.call('ZCOUNT', k, now - period, '+inf')
+ end
+ if rate and rate >= limit then
+ limited = true
+ bucket = k
+ end
+ end
+ redis.call('EXPIRE', k, maxp)
+ if limited then break end
+end
+
+if not limited then
+ for k in pairs(buckets) do
+ redis.call('ZADD', k, now, queue_id)
+ end
+end
+
+return {limited, bucket}]]
+
+local redis_script_symbol = [[local limited = false
+local buckets, results = {}, {}
+local queue_id = table.remove(ARGV)
+local now = table.remove(ARGV)
+
+local argi = 0
+for i = 1, #KEYS do
+ local key = KEYS[i]
+ local period = tonumber(ARGV[argi+1])
+ local limit = tonumber(ARGV[argi+2])
+ if not buckets[key] then
+ buckets[key] = {
+ max_period = period,
+ limits = { {period, limit} },
+ }
+ else
+ table.insert(buckets[key].limits, {period, limit})
+ if period > buckets[key].max_period then
+ buckets[key].max_period = period
+ end
+ end
+ argi = argi + 2
+end
+
+for k, v in pairs(buckets) do
+ local maxp = v.max_period
+ redis.call('ZREMRANGEBYSCORE', k, '-inf', now - maxp)
+ for _, lim in ipairs(v.limits) do
+ local period = lim[1]
+ local limit = lim[2]
+ local rate
+ if period == maxp then
+ rate = redis.call('ZCARD', k)
+ else
+ rate = redis.call('ZCOUNT', k, now - period, '+inf')
+ end
+ if rate then
+ local mult = 2 * math.tanh(rate / (limit * 2))
+ if mult >= 0.5 then
+ table.insert(results, {k, tostring(mult)})
+ end
+ end
+ end
+ redis.call('ZADD', k, now, queue_id)
+ redis.call('EXPIRE', k, maxp)
+end
+
+return results]]
+
+local function load_scripts(cfg, ev_base)
+ local function rl_script_cb(err, data)
+ if err then
+ rspamd_logger.errx(cfg, 'Script loading failed: ' .. err)
+ elseif type(data) == 'string' then
+ redis_script_sha = data
+ end
+ end
+ local script
+ if ratelimit_symbol then
+ script = redis_script_symbol
+ else
+ script = redis_script
+ end
+ lua_redis.redis_make_request_taskless(
+ ev_base,
+ cfg,
+ redis_params,
+ nil, -- key
+ true, -- is write
+ rl_script_cb, --callback
+ 'SCRIPT', -- command
+ {'LOAD', script}
+ )
+end
+
local limit_parser
-local function parse_string_limit(lim)
+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[1] / t[2], t[1]
+ return t[2], t[1]
end
- rspamd_logger.errx(rspamd_config, 'bad limit: %s', lim)
-
- return nil
-end
-
---- Parse atime and bucket of limit
-local function parse_limits(data)
- local function parse_limit_elt(str)
- local elts = rspamd_str_split(str, ':')
- if not elts or #elts < 2 then
- return {0, 0, 0}
- else
- local atime = tonumber(elts[1])
- local bucket = tonumber(elts[2])
- local ctime = atime
-
- if elts[3] then
- ctime = tonumber(elts[3])
- end
-
- if not ctime then
- ctime = atime
- end
-
- return {atime,bucket,ctime}
- end
+ if not no_error then
+ rspamd_logger.errx(rspamd_config, 'bad limit: %s', lim)
end
- return fun.iter(data):map(function(e)
- if type(e) == 'string' then
- return parse_limit_elt(e)
- else
- return {0, 0, 0}
- end
- end):totable()
+ return nil
end
local function resize_element(x_score, x_total, element)
local x_ip_score
if not x_total then x_total = 0 end
if x_total < ip_score_lower_bound or x_total <= 0 then
x_score = 1
else
x_score = x_score / x_total
end
if x_score > 0 then
x_ip_score = x_score / ip_score_spam_divisor
element = element * rspamd_util.tanh(2.718281 * x_ip_score)
elseif x_score < 0 then
x_ip_score = ((1 + (x_score * -1)) * ip_score_ham_multiplier)
element = element * x_ip_score
end
return element
end
--- Check whether this addr is bounce
local function check_bounce(from)
return fun.any(function(b) return b == from end, bounce_senders)
end
local custom_keywords = {}
local keywords = {
['ip'] = {
['get_value'] = function(task)
local ip = task:get_ip()
if ip and ip:is_valid() then return 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 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 from[1]['addr']
+ 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()
return '%s' -- 'to' is special
end,
},
}
local function dynamic_rate_key(task, rtype)
local key_t = {rl_prefix, rtype}
local key_keywords = rspamd_str_split(rtype, '_')
local have_to, have_user = false, false
for _, v in ipairs(key_keywords) do
if (custom_keywords[v] and type(custom_keywords[v]['condition']) == 'function') then
if not custom_keywords[v]['condition']() then return nil end
end
local ret
if custom_keywords[v] and type(custom_keywords[v]['get_value']) == 'function' then
ret = custom_keywords[v]['get_value'](task)
elseif keywords[v] and type(keywords[v]['get_value']) == 'function' then
ret = keywords[v]['get_value'](task)
end
if not ret then return nil end
for _, uk in ipairs(user_keywords) do
if v == uk then have_user = true end
if have_user then break end
end
if v == 'to' then have_to = true end
if type(ret) ~= 'string' then ret = tostring(ret) end
table.insert(key_t, ret)
end
if (not have_user) and task:get_user() then
return nil
end
if not have_to then
return table.concat(key_t, ":")
else
local rate_keys = {}
local rcpts = task:get_recipients(0)
if not ((rcpts or E)[1] or E).addr then
return nil
end
local key_s = table.concat(key_t, ":")
local total_rcpt = 0
for _, r in ipairs(rcpts) do
if r['addr'] and total_rcpt < max_rcpt then
- local key_f = string.format(key_s, r['addr'])
+ local key_f = string.format(key_s, string.lower(r['addr']))
table.insert(rate_keys, key_f)
total_rcpt = total_rcpt + 1
end
end
return rate_keys
end
end
---- Check specific limit inside redis
-local function check_limits(task, args)
-
- local key = fun.foldl(function(acc, k) return acc .. k[2] end, '', args)
- local ret
- --- Called when value is got from server
- local function rate_get_cb(err, data)
+local function process_buckets(task, buckets)
+ if not buckets then return end
+ local function rl_redis_cb(err, data)
if err then
- rspamd_logger.infox(task, 'got error while getting limit: %1', err)
+ rspamd_logger.infox(task, 'got error while setting limit: %1', err)
end
if not data then return end
- local ntime = rspamd_util.get_time()
- local asn_score,total_asn,
- country_score,total_country,
- ipnet_score,total_ipnet,
- ip_score, total_ip
- if use_ip_score then
- asn_score,total_asn,
- country_score,total_country,
- ipnet_score,total_ipnet,
- ip_score, total_ip = task:get_mempool():get_variable('ip_score',
- 'double,double,double,double,double,double,double,double')
+ if data[1] == 1 then
+ rspamd_logger.infox(task,
+ 'ratelimit "%s" exceeded',
+ data[2])
+ task:set_pre_result('soft reject',
+ message_func(task, data[2]))
end
-
- fun.each(function(elt, limit, rtype)
- local bucket = elt[2]
- local rate = limit[2]
- local threshold = limit[1]
- local atime = elt[1]
- local ctime = elt[3]
-
- if atime == 0 then return end
-
- if use_ip_score then
- local key_keywords = rspamd_str_split(rtype, '_')
- local has_asn, has_ip = false, false
- for _, v in ipairs(key_keywords) do
- if v == "asn" then has_asn = true end
- if v == "ip" then has_ip = true end
- if has_ip and has_asn then break end
- end
- if has_asn and not has_ip then
- bucket = resize_element(asn_score, total_asn, bucket)
- rate = resize_element(asn_score, total_asn, rate)
- elseif has_ip then
- if total_ip and total_ip > ip_score_lower_bound then
- bucket = resize_element(ip_score, total_ip, bucket)
- rate = resize_element(ip_score, total_ip, rate)
- elseif total_ipnet and total_ipnet > ip_score_lower_bound then
- bucket = resize_element(ipnet_score, total_ipnet, bucket)
- rate = resize_element(ipnet_score, total_ipnet, rate)
- elseif total_asn and total_asn > ip_score_lower_bound then
- bucket = resize_element(asn_score, total_asn, bucket)
- rate = resize_element(asn_score, total_asn, rate)
- elseif total_country and total_country > ip_score_lower_bound then
- bucket = resize_element(country_score, total_country, bucket)
- rate = resize_element(country_score, total_country, rate)
- else
- bucket = resize_element(ip_score, total_ip, bucket)
- rate = resize_element(ip_score, total_ip, rate)
- end
- end
- end
-
- if atime - ctime > max_delay then
- rspamd_logger.infox(task, 'limit is too old: %1 seconds; ignore it',
- atime - ctime)
- else
- bucket = bucket - rate * (ntime - atime);
- if bucket > 0 then
- if ratelimit_symbol then
- local mult = 2 * rspamd_util.tanh(bucket / (threshold * 2))
-
- if mult > 0.5 then
- task:insert_result(ratelimit_symbol, mult,
- rtype .. ':' .. string.format('%.2f', mult))
- end
- else
- if bucket > threshold then
- rspamd_logger.infox(task,
- 'ratelimit "%s" exceeded: %s elements with %s limit',
- rtype, bucket, threshold)
- task:set_pre_result('soft reject',
- message_func(task, rtype, bucket, threshold))
- end
- end
- end
- end
- end, fun.zip(parse_limits(data), fun.map(function(a) return a[1] end, args),
- fun.map(function(a) return rspamd_str_split(a[2], ":")[2] end, args)))
end
-
- ret = rspamd_redis_make_request(task,
- redis_params, -- connect params
- key, -- hash key
- false, -- is write
- rate_get_cb, --callback
- 'mget', -- command
- fun.totable(fun.map(function(l) return l[2] end, args)) -- arguments
- )
- if not ret then
- rspamd_logger.errx(task, 'got error connecting to redis')
- end
-end
-
---- Set specific limit inside redis
-local function set_limits(task, args)
- local key = fun.foldl(function(acc, k) return acc .. k[2] end, '', args)
- local ret, upstream
-
- local function rate_set_cb(err)
- if err then
- rspamd_logger.infox(task, 'got error %s when setting ratelimit record on server %s',
- err, upstream:get_addr())
- end
- end
- local function rate_get_cb(err, data)
+ local function rl_symbol_redis_cb(err, data)
if err then
rspamd_logger.infox(task, 'got error while setting limit: %1', err)
end
if not data then return end
- local ntime = rspamd_util.get_time()
- local values = {}
- fun.each(function(elt, limit)
- local bucket = elt[2]
- local rate = limit[1][2]
- local atime = elt[1]
- local ctime = elt[3]
-
- if atime - ctime > max_delay then
- rspamd_logger.infox(task, 'limit is too old: %1 seconds; start it over',
- atime - ctime)
- bucket = 1
- ctime = ntime
- else
- if bucket > 0 then
- bucket = bucket - rate * (ntime - atime) + 1;
- if bucket < 0 then
- bucket = 1
- end
+ for i, b in ipairs(data) do
+ task:insert_result(ratelimit_symbol, b[2], string.format('%s:%s:%s', i, b[1], b[2]))
+ end
+ end
+ local redis_cb = rl_redis_cb
+ if ratelimit_symbol then redis_cb = rl_symbol_redis_cb end
+ local args = {redis_script_sha, #buckets}
+ for _, bucket in ipairs(buckets) do
+ table.insert(args, bucket[2])
+ end
+ for _, bucket in ipairs(buckets) do
+ if use_ip_score then
+ local asn_score,total_asn,
+ country_score,total_country,
+ ipnet_score,total_ipnet,
+ ip_score, total_ip = task:get_mempool():get_variable('ip_score',
+ 'double,double,double,double,double,double,double,double')
+ local key_keywords = rspamd_str_split(bucket[2], '_')
+ local has_asn, has_ip = false, false
+ for _, v in ipairs(key_keywords) do
+ if v == "asn" then has_asn = true end
+ if v == "ip" then has_ip = true end
+ if has_ip and has_asn then break end
+ end
+ if has_asn and not has_ip then
+ bucket[1][2] = resize_element(asn_score, total_asn, bucket[1][2])
+ elseif has_ip then
+ if total_ip and total_ip > ip_score_lower_bound then
+ bucket[1][2] = resize_element(ip_score, total_ip, bucket[1][2])
+ elseif total_ipnet and total_ipnet > ip_score_lower_bound then
+ bucket[1][2] = resize_element(ipnet_score, total_ipnet, bucket[1][2])
+ elseif total_asn and total_asn > ip_score_lower_bound then
+ bucket[1][2] = resize_element(asn_score, total_asn, bucket[1][2])
+ elseif total_country and total_country > ip_score_lower_bound then
+ bucket[1][2] = resize_element(country_score, total_country, bucket[1][2])
else
- bucket = 1
+ bucket[1][2] = resize_element(ip_score, total_ip, bucket[1][2])
end
end
-
- if ctime == 0 then ctime = ntime end
-
- local lstr = string.format('%.3f:%.3f:%.3f', ntime, bucket, ctime)
- table.insert(values, {limit[2], max_delay, lstr})
- end, fun.zip(parse_limits(data), fun.iter(args)))
-
- if #values > 0 then
- local conn
- ret,conn,upstream = rspamd_redis_make_request(task,
- redis_params, -- connect params
- key, -- hash key
- true, -- is write
- rate_set_cb, --callback
- 'setex', -- command
- values[1] -- arguments
- )
-
- if conn then
- fun.each(function(v)
- conn:add_cmd('setex', v)
- end, fun.drop_n(1, values))
- else
- rspamd_logger.errx(task, 'got error while connecting to redis')
- end
end
+ table.insert(args, bucket[1][1])
+ table.insert(args, bucket[1][2])
end
-
- local _
- ret,_,upstream = rspamd_redis_make_request(task,
+ table.insert(args, rspamd_util.get_time())
+ table.insert(args, task:get_queue_id() or task:get_uid())
+ local ret = rspamd_redis_make_request(task,
redis_params, -- connect params
- key, -- hash key
- false, -- is write
- rate_get_cb, --callback
- 'mget', -- command
- fun.totable(fun.map(function(l) return l[2] end, args)) -- arguments
+ nil, -- hash key
+ true, -- is write
+ redis_cb, --callback
+ 'evalsha', -- command
+ args -- arguments
)
if not ret then
rspamd_logger.errx(task, 'got error connecting to redis')
end
end
---- Check or update ratelimit
-local function rate_test_set(task, func)
+local function ratelimit_cb(task)
+ if rspamd_lua_utils.is_rspamc_or_controller(task) then return end
local args = {}
-- Get initial task data
local ip = task:get_from_ip()
if ip and ip:is_valid() and whitelisted_ip then
if 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) table.insert(rcpts_user, r['user']) end, rcpts)
- if fun.any(function(r)
- fun.any(function(w) return r == w end, whitelisted_rcpts) end,
+ fun.each(function(r)
+ fun.each(function(type) table.insert(rcpts_user, r[type]) end, {'user', 'addr'})
+ end, rcpts)
+ if fun.any(
+ function(r)
+ if fun.any(function(w) return r == w end, whitelisted_rcpts) then return true end
+ end,
rcpts_user) then
rspamd_logger.infox(task, 'skip ratelimit for whitelisted recipient')
return
end
end
-- Get user (authuser)
if whitelisted_user then
local auser = task:get_user()
if whitelisted_user:get_key(auser) then
rspamd_logger.infox(task, 'skip ratelimit for whitelisted user')
return
end
end
+ local redis_keys = {}
+ local redis_keys_rev = {}
+ local function collect_redis_keys()
+ local function collect_cb(err, data)
+ if err then
+ rspamd_logger.errx(task, 'redis error: %1', err)
+ else
+ for i, d in ipairs(data) do
+ if type(d) == 'string' then
+ local plim, size = parse_string_limit(d)
+ if plim then
+ table.insert(args, {{plim, size}, redis_keys_rev[i]})
+ end
+ end
+ end
+ return process_buckets(task, args)
+ end
+ end
+ local params, method
+ if limits_hash then
+ params = {limits_hash, rspamd_lua_utils.unpack(redis_keys)}
+ method = 'HMGET'
+ else
+ method = 'MGET'
+ params = redis_keys
+ end
+ local requested_keys = rspamd_redis_make_request(task,
+ redis_params, -- connect params
+ nil, -- hash key
+ true, -- is write
+ collect_cb, --callback
+ method, -- command
+ params -- arguments
+ )
+ if not requested_keys then
+ rspamd_logger.errx(task, 'got error connecting to redis')
+ return process_buckets(task, args)
+ end
+ end
+
local rate_key
for k in pairs(settings) do
rate_key = dynamic_rate_key(task, k)
if rate_key then
if type(rate_key) == 'table' then
for _, rk in ipairs(rate_key) do
- if type(settings[k]) == 'table' then
- table.insert(args, {settings[k], rk})
- elseif type(settings[k]) == 'string' and
+ if type(settings[k]) == 'string' and
(custom_keywords[settings[k]] and type(custom_keywords[settings[k]]['get_limit']) == 'function') then
local res = custom_keywords[settings[k]]['get_limit'](task)
- if type(res) == 'table' then
- table.insert(args, {res, rate_key})
- elseif type(res) == 'string' then
- local plim, size = parse_string_limit(res)
+ if type(res) == 'string' then res = {res} end
+ for _, r in ipairs(res) do
+ local plim, size = parse_string_limit(r, true)
if plim then
- table.insert(args, {{size, plim, 1}, rate_key})
+ table.insert(args, {{plim, size}, rk})
+ else
+ local rkey = string.match(settings[k], 'redis:(.*)')
+ if rkey then
+ table.insert(redis_keys, rkey)
+ redis_keys_rev[#redis_keys] = rk
+ else
+ rspamd_logger.infox(task, "Don't know what to do with limit: %1", settings[k])
+ end
end
end
end
end
else
- if type(settings[k]) == 'table' then
- table.insert(args, {settings[k], rate_key})
- elseif type(settings[k]) == 'string' and
- (custom_keywords[settings[k]] and type(custom_keywords[settings[k]]['get_limit']) == 'function') then
+ if type(settings[k]) == 'string' and
+ (custom_keywords[settings[k]] and type(custom_keywords[settings[k]]['get_limit']) == 'function') then
local res = custom_keywords[settings[k]]['get_limit'](task)
- if type(res) == 'table' then
- table.insert(args, {res, rate_key})
- elseif type(res) == 'string' then
- local plim, size = parse_string_limit(res)
+ if type(res) == 'string' then res = {res} end
+ for _, r in ipairs(res) do
+ local plim, size = parse_string_limit(r, true)
if plim then
- table.insert(args, {{size, plim, 1}, rate_key})
+ table.insert(args, {{plim, size}, rate_key})
+ else
+ local rkey = string.match(r, 'redis:(.*)')
+ if rkey then
+ table.insert(redis_keys, rkey)
+ redis_keys_rev[#redis_keys] = rate_key
+ else
+ rspamd_logger.infox(task, "Don't know what to do with limit: %1", settings[k])
+ end
end
end
+ elseif type(settings[k]) == 'table' then
+ for _, rl in ipairs(settings[k]) do
+ table.insert(args, {{rl[1], rl[2]}, rate_key})
+ end
+ elseif type(settings[k]) == 'string' then
+ local rkey = string.match(settings[k], 'redis:(.*)')
+ if rkey then
+ table.insert(redis_keys, rkey)
+ redis_keys_rev[#redis_keys] = rate_key
+ else
+ rspamd_logger.infox(task, "Don't know what to do with limit: %1", settings[k])
+ end
end
end
end
end
- if #args > 0 then
- func(task, args)
- end
-end
-
---- Check limit
-local function rate_test(task)
- if rspamd_lua_utils.is_rspamc_or_controller(task) then return end
- rate_test_set(task, check_limits)
-end
---- Update limit
-local function rate_set(task)
- local action = task:get_metric_action('default')
-
- if action ~= 'soft reject' then
- if rspamd_lua_utils.is_rspamc_or_controller(task) then return end
- rate_test_set(task, set_limits)
- end
-end
-
-
---- Parse a single limit description
-local function parse_limit(str)
- local params = rspamd_str_split(str, ':')
-
- local function set_limit(limit, burst, rate)
- limit[1] = tonumber(burst)
- limit[2] = tonumber(rate)
- end
-
- if #params ~= 3 then
- rspamd_logger.errx(rspamd_config, 'invalid limit definition: ' .. str)
- return
- end
-
- local key_keywords = rspamd_str_split(params[1], '_')
- for _, k in ipairs(key_keywords) do
- if (custom_keywords[k] and type(custom_keywords[k]['get_value']) == 'function') or
- (keywords[k] and type(keywords[k]['get_value']) == 'function') then
- set_limit(settings[params[1]], params[2], params[3])
- else
- rspamd_logger.errx(rspamd_config, 'invalid limit type: ' .. params[1])
- end
+ if redis_keys[1] then
+ return collect_redis_keys()
+ else
+ return process_buckets(task, args)
end
end
-local opts = rspamd_config:get_all_opt('ratelimit')
+local opts = rspamd_config:get_all_opt(N)
if opts then
- local rates = opts['limit']
- if rates and type(rates) == 'table' then
- fun.each(parse_limit, rates)
- elseif rates and type(rates) == 'string' then
- parse_limit(rates)
+ 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)
if type(lim) == 'table' then
- settings[t] = lim
+ settings[t] = {}
+ fun.each(function(l)
+ local plim, size = parse_string_limit(l)
+ if plim then
+ table.insert(settings[t], {plim, size})
+ end
+ end, lim)
elseif type(lim) == 'string' then
local plim, size = parse_string_limit(lim)
if plim then
- settings[t] = {size, plim, 1}
+ settings[t] = { {plim, size} }
end
end
end, opts['rates'])
end
if opts['dynamic_rates'] and type(opts['dynamic_rates']) == 'table' then
fun.each(function(t, lim)
if type(lim) == 'string' then
settings[t] = lim
end
end, opts['dynamic_rates'])
end
local enabled_limits = fun.totable(fun.map(function(t)
return t
- end, fun.filter(function(_, lim)
- return type(lim) == 'string' or
- (type(lim) == 'table' and type(lim[1]) == 'number' and lim[1] > 0)
- or (type(lim) == 'table' and (lim[3]))
- end, settings)))
+ end, settings))
rspamd_logger.infox(rspamd_config, 'enabled rate buckets: [%1]', table.concat(enabled_limits, ','))
if opts['whitelisted_rcpts'] and type(opts['whitelisted_rcpts']) == 'string' then
whitelisted_rcpts = rspamd_str_split(opts['whitelisted_rcpts'], ',')
elseif type(opts['whitelisted_rcpts']) == 'table' then
whitelisted_rcpts = opts['whitelisted_rcpts']
end
if opts['whitelisted_ip'] then
whitelisted_ip = rspamd_map_add('ratelimit', 'whitelisted_ip', 'radix',
'Ratelimit whitelist ip map')
end
if opts['whitelisted_user'] then
whitelisted_user = rspamd_map_add('ratelimit', 'whitelisted_user', 'set',
'Ratelimit whitelist user map')
end
if opts['symbol'] then
-- We want symbol instead of pre-result
ratelimit_symbol = opts['symbol']
end
if opts['max_rcpt'] then
max_rcpt = tonumber(opts['max_rcpt'])
end
- if opts['max_delay'] then
- max_rcpt = tonumber(opts['max_delay'])
- end
-
if opts['use_ip_score'] then
use_ip_score = true
local ip_score_opts = rspamd_config:get_all_opt('ip_score')
if ip_score_opts and ip_score_opts['lower_bound'] then
ip_score_lower_bound = ip_score_opts['lower_bound']
end
end
if opts['custom_keywords'] then
custom_keywords = dofile(opts['custom_keywords'])
end
if opts['user_keywords'] then
user_keywords = opts['user_keywords']
end
if opts['message_func'] then
message_func = assert(load(opts['message_func']))()
end
+ if opts['limits_hash'] then
+ limits_hash = opts['limits_hash']
+ end
+
redis_params = rspamd_parse_redis_server('ratelimit')
if not redis_params then
rspamd_logger.infox(rspamd_config, 'no servers are specified, disabling module')
else
- if not ratelimit_symbol and not use_ip_score then
- rspamd_config:register_symbol({
- name = 'RATELIMIT_CHECK',
- callback = rate_test,
- type = 'prefilter',
- priority = 4,
- })
- else
- local symbol
- if not ratelimit_symbol then
- symbol = 'RATELIMIT_CHECK'
- else
- symbol = ratelimit_symbol
- end
- local id = rspamd_config:register_symbol({
- name = symbol,
- callback = rate_test,
- })
- if use_ip_score then
- rspamd_config:register_dependency(id, 'IP_SCORE')
- end
+ local s = {
+ type = 'prefilter,nostat',
+ name = 'RATELIMIT_CHECK',
+ priority = 4,
+ callback = ratelimit_cb,
+ }
+ if use_ip_score then
+ s.type = 'normal'
+ end
+ if ratelimit_symbol then
+ s.name = ratelimit_symbol
+ end
+ local id = rspamd_config:register_symbol(s)
+ if use_ip_score then
+ rspamd_config:register_dependency(id, 'IP_SCORE')
end
- rspamd_config:register_symbol({
- name = 'RATELIMIT_SET',
- type = 'postfilter',
- priority = 5,
- callback = rate_set,
- })
for _, v in pairs(custom_keywords) do
if type(v) == 'table' and type(v['init']) == 'function' then
v['init']()
end
end
end
end
-
-
+rspamd_config:add_on_load(function(cfg, ev_base, worker)
+ load_scripts(cfg, ev_base)
+end)
diff --git a/data/conf/rspamd/custom/ratelimit.lua b/data/conf/rspamd/custom/ratelimit.lua
index ed6b1bd6..a7beac49 100644
--- a/data/conf/rspamd/custom/ratelimit.lua
+++ b/data/conf/rspamd/custom/ratelimit.lua
@@ -1,66 +1,25 @@
local custom_keywords = {
['customrl'] = {},
}
+
function custom_keywords.customrl.get_value(task)
local rspamd_logger = require "rspamd_logger"
- local rspamd_redis = require "rspamd_redis"
- local rspamd_regexp = require "rspamd_regexp"
- local re = rspamd_regexp.create('/^\\s*$/i')
- local envfrom = task:get_from(1)
- local env_from_addr = envfrom[1].addr:lower() -- get smtp from addr in lower case
- local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case
-
- local function rlo(object) -- get ratelimited object
- local rlobj = string.format('%s', object)
-
- local rl_ret, rl_obj = rspamd_redis.make_request_sync({host="172.22.1.249:6379", cmd='HGET', args={'RL_OBJECT', rlobj}, timeout=2.0})
-
- if rl_ret and rl_obj then
- return rl_obj
- else
- return false
- end
- end
-
- rl_addr = rlo(env_from_addr)
- rl_domain = rlo(env_from_domain)
- if type(rl_addr) == 'string' and not re:match(rl_addr) then
- rspamd_logger.infox(rspamd_config, "returning ratelimit object for %s", env_from_addr)
- return rl_addr
- elseif type(rl_domain) == 'string' and not re:match(rl_domain) then
- rspamd_logger.infox(rspamd_config, "returning ratelimit object for %s", env_from_domain)
- return rl_domain
+ if task:has_symbol('DYN_RL') then
+ rspamd_logger.infox(rspamd_config, "task has a dynamic ratelimit symbol, processing...")
+ return "check"
+ else
+ rspamd_logger.infox(rspamd_config, "task has no dynamic ratelimit symbol, skipping...")
+ return
end
end
function custom_keywords.customrl.get_limit(task)
local rspamd_logger = require "rspamd_logger"
- local rspamd_redis = require "rspamd_redis"
- local rspamd_regexp = require "rspamd_regexp"
- local re = rspamd_regexp.create('/^\\s*$/i')
- local envfrom = task:get_from(1)
- local env_from_addr = envfrom[1].addr:lower() -- get smtp from addr in lower case
- local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case
-
- local function rlv(object) -- get ratelimited object
- local rlobj = string.format('%s', object)
-
- local rl_ret, rl_value = rspamd_redis.make_request_sync({host="172.22.1.249:6379", cmd='HGET', args={'RL_VALUE', rlobj}, timeout=2.0})
-
- if rl_ret and rl_value then
- return rl_value
- else
- return false
- end
- end
-
- rl_addr = rlv(env_from_addr)
- rl_domain = rlv(env_from_domain)
- if type(rl_addr) == 'string' and not re:match(rl_addr) then
- rspamd_logger.infox(rspamd_config, "returning ratelimit %s for %s", rl_addr, env_from_addr)
- return rl_addr
- elseif type(rl_domain) == 'string' and not re:match(rl_domain) then
- rspamd_logger.infox(rspamd_config, "returning ratelimit %s for %s", rl_domain, env_from_domain)
- return rl_domain
+ local dyn_rl_symbol = task:get_symbol("DYN_RL")
+ if dyn_rl_symbol then
+ local rl_value = dyn_rl_symbol[1].options[1]
+ rspamd_logger.infox(rspamd_config, "dynamic ratelimit symbol has option %s, returning...", rl_value)
+ return rl_value
end
end
-return custom_keywords
+-- returning custom keywords
+return custom_keywords
\ No newline at end of file
diff --git a/data/conf/rspamd/lua/rspamd.local.lua b/data/conf/rspamd/lua/rspamd.local.lua
index 75f52892..62383707 100644
--- a/data/conf/rspamd/lua/rspamd.local.lua
+++ b/data/conf/rspamd/lua/rspamd.local.lua
@@ -1,53 +1,110 @@
rspamd_config.MAILCOW_AUTH = {
callback = function(task)
local uname = task:get_user()
if uname then
return 1
end
end
}
rspamd_config:register_symbol({
name = 'TAG_MOO',
type = 'postfilter',
callback = function(task)
local util = require("rspamd_util")
local rspamd_logger = require "rspamd_logger"
local tagged_rcpt = task:get_symbol("TAGGED_RCPT")
local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN")
if tagged_rcpt and mailcow_domain then
local tag = tagged_rcpt[1].options[1]
rspamd_logger.infox("found tag: %s", tag)
local action = task:get_metric_action('default')
rspamd_logger.infox("metric action now: %s", action)
if action ~= 'no action' and action ~= 'greylist' then
rspamd_logger.infox("skipping tag handler for action: %s", action)
task:set_metric_action('default', action)
return true
end
local wants_subject_tag = task:get_symbol("RCPT_WANTS_SUBJECT_TAG")
if wants_subject_tag then
rspamd_logger.infox("user wants subject modified for tagged mail")
local sbj = task:get_header('Subject')
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
task:set_milter_reply({
remove_headers = {['Subject'] = 1},
add_headers = {['Subject'] = new_sbj}
})
else
rspamd_logger.infox("Add X-Moo-Tag header")
task:set_milter_reply({
add_headers = {['X-Moo-Tag'] = 'YES'}
})
end
end
end,
priority = 11
})
+rspamd_config:register_symbol({
+ name = 'DYN_RL_CHECK',
+ type = 'prefilter',
+ callback = function(task)
+ local util = require("rspamd_util")
+ local redis_params = rspamd_parse_redis_server('dyn_rl')
+ local rspamd_logger = require "rspamd_logger"
+ local envfrom = task:get_from(1)
+ local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case
+ local env_from_addr = envfrom[1].addr:lower() -- get smtp from addr in lower case
+
+ local function redis_cb_user(err, data)
+
+ if err or type(data) ~= 'string' then
+ rspamd_logger.infox(rspamd_config, "dynamic ratelimit request for user %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying dynamic ratelimit for domain...", env_from_addr, data, err)
+
+ local function redis_key_cb_domain(err, data)
+ if err or type(data) ~= 'string' then
+ rspamd_logger.infox(rspamd_config, "dynamic ratelimit request for domain %s returned invalid or empty data (\"%s\") or error (\"%s\")", env_from_domain, data, err)
+ else
+ rspamd_logger.infox(rspamd_config, "found dynamic ratelimit in redis for domain %s with value %s", env_from_domain, data)
+ task:insert_result('DYN_RL', 0.0, data)
+ end
+ end
+ local redis_ret_domain = rspamd_redis_make_request(task,
+ redis_params, -- connect params
+ env_from_domain, -- hash key
+ false, -- is write
+ redis_key_cb_domain, --callback
+ 'HGET', -- command
+ {'RL_VALUE', env_from_domain} -- arguments
+ )
+ if not redis_ret_domain then
+ rspamd_logger.infox(rspamd_config, "cannot make request to load ratelimit for domain")
+ end
+ else
+ rspamd_logger.infox(rspamd_config, "found dynamic ratelimit in redis for user %s with value %s", env_from_addr, data)
+ task:insert_result('DYN_RL', 0.0, data)
+ end
+
+ end
+
+ local redis_ret_user = rspamd_redis_make_request(task,
+ redis_params, -- connect params
+ env_from_addr, -- hash key
+ false, -- is write
+ redis_cb_user, --callback
+ 'HGET', -- command
+ {'RL_VALUE', env_from_addr} -- arguments
+ )
+ if not redis_ret_user then
+ rspamd_logger.infox(rspamd_config, "cannot make request to load ratelimit for user")
+ end
+ return true
+ end,
+ priority = 20
+})
\ No newline at end of file
diff --git a/data/web/edit.php b/data/web/edit.php
index b40f1ae0..4511ab94 100644
--- a/data/web/edit.php
+++ b/data/web/edit.php
@@ -1,721 +1,717 @@
<?php
require_once("inc/prerequisites.inc.php");
$AuthUsers = array("admin", "domainadmin", "user");
if (!isset($_SESSION['mailcow_cc_role']) OR !in_array($_SESSION['mailcow_cc_role'], $AuthUsers)) {
header('Location: /');
exit();
}
require_once("inc/header.inc.php");
?>
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><?=$lang['edit']['title'];?></h3>
</div>
<div class="panel-body">
<?php
if (isset($_SESSION['mailcow_cc_role'])) {
if ($_SESSION['mailcow_cc_role'] == "admin" || $_SESSION['mailcow_cc_role'] == "domainadmin") {
if (isset($_GET["alias"]) &&
!empty($_GET["alias"])) {
$alias = $_GET["alias"];
$result = mailbox('get', 'alias_details', $alias);
if (!empty($result)) {
?>
<h4><?=$lang['edit']['alias'];?></h4>
<br />
<form class="form-horizontal" data-id="editalias" role="form" method="post">
<input type="hidden" value="0" name="active">
<div class="form-group">
<label class="control-label col-sm-2" for="goto"><?=$lang['edit']['target_address'];?></label>
<div class="col-sm-10">
<textarea class="form-control" autocapitalize="none" autocorrect="off" rows="10" id="goto" name="goto"><?=htmlspecialchars($result['goto']) ?></textarea>
</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" <?php if (isset($result['active_int']) && $result['active_int']=="1") { echo "checked"; }; ?>> <?=$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" id="edit_selected" data-id="editalias" data-item="<?=$alias;?>" data-api-url='edit/alias' 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['domainadmin']) &&
ctype_alnum(str_replace(array('_', '.', '-'), '', $_GET["domainadmin"])) &&
!empty($_GET["domainadmin"]) &&
$_GET["domainadmin"] != 'admin' &&
$_SESSION['mailcow_cc_role'] == "admin") {
$domain_admin = $_GET["domainadmin"];
$result = domain_admin('details', $domain_admin);
if (!empty($result)) {
?>
<h4><?=$lang['edit']['domain_admin'];?></h4>
<br />
<form class="form-horizontal" data-id="editdomainadmin" role="form" method="post">
<input type="hidden" value="0" name="active">
<div class="form-group">
<label class="control-label col-sm-2" for="username_new"><?=$lang['edit']['username'];?></label>
<div class="col-sm-10">
<input class="form-control" type="text" name="username_new" value="<?=htmlspecialchars($domain_admin);?>" />
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="domains"><?=$lang['edit']['domains'];?></label>
<div class="col-sm-10">
<select id="domains" name="domains" multiple required>
<?php
foreach ($result['selected_domains'] as $domain):
?>
<option selected><?=htmlspecialchars($domain);?></option>
<?php
endforeach;
foreach ($result['unselected_domains'] as $domain):
?>
<option><?=htmlspecialchars($domain);?></option>
<?php
endforeach;
?>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password"><?=$lang['edit']['password'];?></label>
<div class="col-sm-10">
<input type="password" class="form-control" name="password" id="password" placeholder="">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password2"><?=$lang['edit']['password_repeat'];?></label>
<div class="col-sm-10">
<input type="password" class="form-control" name="password2" id="password2">
</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" <?php if (isset($result['active_int']) && $result['active_int']=="1") { echo "checked"; }; ?>> <?=$lang['edit']['active'];?></label>
</div>
</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="disable_tfa"> <?=$lang['tfa']['disable_tfa'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-success" id="edit_selected" data-id="editdomainadmin" data-item="<?=$domain_admin;?>" data-api-url='edit/domain-admin' 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['domain']) &&
is_valid_domain_name($_GET["domain"]) &&
!empty($_GET["domain"])) {
$domain = $_GET["domain"];
$result = mailbox('get', 'domain_details', $domain);
$rl = mailbox('get', 'domain_ratelimit', $domain);
$rlyhosts = relayhost('get');
if (!empty($result)) {
?>
<h4><?=$lang['edit']['domain'];?></h4>
<form data-id="editdomain" class="form-horizontal" role="form" method="post">
<input type="hidden" value="0" name="active">
<input type="hidden" value="0" name="backupmx">
<input type="hidden" value="0" name="relay_all_recipients">
<div class="form-group">
<label class="control-label col-sm-2" for="description"><?=$lang['edit']['description'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="description" id="description" value="<?=htmlspecialchars($result['description']);?>">
</div>
</div>
<?php
if ($_SESSION['mailcow_cc_role'] == "admin") {
?>
<div class="form-group">
<label class="control-label col-sm-2" for="aliases"><?=$lang['edit']['max_aliases'];?></label>
<div class="col-sm-10">
<input type="number" class="form-control" name="aliases" id="aliases" value="<?=intval($result['max_num_aliases_for_domain']);?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="mailboxes"><?=$lang['edit']['max_mailboxes'];?></label>
<div class="col-sm-10">
<input type="number" class="form-control" name="mailboxes" id="mailboxes" value="<?=intval($result['max_num_mboxes_for_domain']);?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="maxquota"><?=$lang['edit']['max_quota'];?></label>
<div class="col-sm-10">
<input type="number" class="form-control" name="maxquota" id="maxquota" value="<?=intval($result['max_quota_for_mbox'] / 1048576);?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="quota"><?=$lang['edit']['domain_quota'];?></label>
<div class="col-sm-10">
<input type="number" class="form-control" name="quota" id="quota" value="<?=intval($result['max_quota_for_domain'] / 1048576);?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="quota">Relayhost</label>
<div class="col-sm-10">
<select name="relayhost" id="relayhost" class="form-control">
<?php
foreach ($rlyhosts as $rlyhost) {
?>
<option value="<?=$rlyhost['id'];?>" <?=($result['relayhost'] == $rlyhost['id']) ? 'selected' : null;?>>ID <?=$rlyhost['id'];?>: <?=$rlyhost['hostname'];?> (<?=$rlyhost['username'];?>)</option>
<?php
}
?>
<option value="" <?=($result['relayhost'] == "0") ? 'selected' : null;?>>None</option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2"><?=$lang['edit']['backup_mx_options'];?></label>
<div class="col-sm-10">
<div class="checkbox">
<label><input type="checkbox" value="1" name="backupmx" <?=(isset($result['backupmx_int']) && $result['backupmx_int']=="1") ? "checked" : null;?>> <?=$lang['edit']['relay_domain'];?></label>
<br />
<label><input type="checkbox" value="1" name="relay_all_recipients" <?=(isset($result['relay_all_recipients_int']) && $result['relay_all_recipients_int']=="1") ? "checked" : null;?>> <?=$lang['edit']['relay_all'];?></label>
<p><?=$lang['edit']['relay_all_info'];?></p>
</div>
</div>
</div>
<?php
}
?>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label><input type="checkbox" value="1" name="active" <?=(isset($result['active_int']) && $result['active_int']=="1") ? "checked" : null;?> <?=($_SESSION['mailcow_cc_role'] == "admin") ? null : "disabled";?>> <?=$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" id="edit_selected" data-id="editdomain" data-item="<?=$domain;?>" data-api-url='edit/domain' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
</div>
</div>
</form>
<?php
if (!empty($dkim = dkim('details', $domain))) {
?>
<hr>
<div class="row">
<div class="col-xs-2">
<p>Domain: <strong><?=htmlspecialchars($result['domain_name']);?></strong> (<?=$dkim['dkim_selector'];?>._domainkey)</p>
</div>
<div class="col-xs-10">
<pre><?=$dkim['dkim_txt'];?></pre>
</div>
</div>
<?php
}
?>
<hr>
- <!--
<form data-id="domratelimit" class="form-inline well" method="post">
<div class="form-group">
<label class="control-label">Ratelimit</label>
<input name="rl_value" id="rl_value" type="number" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" class="form-control" placeholder="disabled">
</div>
<div class="form-group">
<select name="rl_frame" id="rl_frame" class="form-control">
<option value="s" <?=(isset($rl['frame']) && $rl['frame'] == 's') ? 'selected' : null;?>>msgs / second</option>
<option value="m" <?=(isset($rl['frame']) && $rl['frame'] == 'm') ? 'selected' : null;?>>msgs / minute</option>
<option value="h" <?=(isset($rl['frame']) && $rl['frame'] == 'h') ? 'selected' : null;?>>msgs / hour</option>
</select>
</div>
<div class="form-group">
<button class="btn btn-default" id="edit_selected" data-id="domratelimit" data-item="<?=$domain;?>" data-api-url='edit/domain-ratelimit' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
</div>
</form>
<hr>
- -->
<div class="row">
<div class="col-sm-6">
<h4><?=$lang['user']['spamfilter_wl'];?></h4>
<p><?=$lang['user']['spamfilter_wl_desc'];?></p>
<div class="table-responsive">
<table class="table table-striped table-condensed" id="wl_policy_domain_table"></table>
</div>
<div class="mass-actions-user">
<div class="btn-group">
<a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_wl_domain" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
<a class="btn btn-sm btn-danger" id="delete_selected" data-id="policy_wl_domain" data-api-url='delete/domain-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li>
</ul>
</div>
</div>
<form class="form-inline" data-id="add_wl_policy_domain">
<div class="input-group">
<input type="text" class="form-control" name="object_from" id="object_from" placeholder="*@example.org" required>
<span class="input-group-btn">
<button class="btn btn-default" id="add_item" data-id="add_wl_policy_domain" data-api-url='add/domain-policy' data-api-attr='{"domain":"<?= $domain; ?>","object_list":"wl"}' href="#"><?=$lang['user']['spamfilter_table_add'];?></button>
</span>
</div>
</form>
</div>
<div class="col-sm-6">
<h4><?=$lang['user']['spamfilter_bl'];?></h4>
<p><?=$lang['user']['spamfilter_bl_desc'];?></p>
<div class="table-responsive">
<table class="table table-striped table-condensed" id="bl_policy_domain_table"></table>
</div>
<div class="mass-actions-user">
<div class="btn-group">
<a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_bl_domain" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
<a class="btn btn-sm btn-danger" id="delete_selected" data-id="policy_bl_domain" data-api-url='delete/domain-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li>
</ul>
</div>
</div>
<form class="form-inline" data-id="add_bl_policy_domain">
<div class="input-group">
<input type="text" class="form-control" name="object_from" id="object_from" placeholder="*@example.org" required>
<span class="input-group-btn">
<button class="btn btn-default" id="add_item" data-id="add_bl_policy_domain" data-api-url='add/domain-policy' data-api-attr='{"domain":"<?= $domain; ?>","object_list":"bl"}' href="#"><?=$lang['user']['spamfilter_table_add'];?></button>
</span>
</div>
</form>
</div>
</div>
<?php
}
else {
?>
<div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
<?php
}
}
elseif (isset($_GET['aliasdomain']) &&
is_valid_domain_name($_GET["aliasdomain"]) &&
!empty($_GET["aliasdomain"])) {
$alias_domain = $_GET["aliasdomain"];
$result = mailbox('get', 'alias_domain_details', $alias_domain);
- // $rl = mailbox('get', 'domain_ratelimit', $alias_domain);
+ $rl = mailbox('get', 'domain_ratelimit', $alias_domain);
if (!empty($result)) {
?>
<h4><?=$lang['edit']['edit_alias_domain'];?></h4>
<form class="form-horizontal" data-id="editaliasdomain" role="form" method="post">
<input type="hidden" value="0" name="active">
<div class="form-group">
<label class="control-label col-sm-2" for="target_domain"><?=$lang['edit']['target_domain'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="target_domain" id="target_domain" value="<?=htmlspecialchars($result['target_domain']);?>">
</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" <?=(isset($result['active_int']) && $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" id="edit_selected" data-id="editaliasdomain" data-item="<?=$alias_domain;?>" data-api-url='edit/alias-domain' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button>
</div>
</div>
</form>
- <!--
<hr>
<form data-id="domratelimit" class="form-inline well" method="post">
<div class="form-group">
<label class="control-label">Ratelimit</label>
<input name="rl_value" id="rl_value" type="number" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" class="form-control" placeholder="disabled">
</div>
<div class="form-group">
<select name="rl_frame" id="rl_frame" class="form-control">
<option value="s" <?=(isset($rl['frame']) && $rl['frame'] == 's') ? 'selected' : null;?>>msgs / second</option>
<option value="m" <?=(isset($rl['frame']) && $rl['frame'] == 'm') ? 'selected' : null;?>>msgs / minute</option>
<option value="h" <?=(isset($rl['frame']) && $rl['frame'] == 'h') ? 'selected' : null;?>>msgs / hour</option>
</select>
</div>
<div class="form-group">
<button class="btn btn-default" id="edit_selected" data-id="domratelimit" data-item="<?=$alias_domain;?>" data-api-url='edit/domain-ratelimit' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
</div>
</form>
- -->
<?php
if (!empty($dkim = dkim('details', $alias_domain))) {
?>
<hr>
<div class="row">
<div class="col-xs-2">
<p>Domain: <strong><?=htmlspecialchars($result['alias_domain']);?></strong> (<?=$dkim['dkim_selector'];?>._domainkey)</p>
</div>
<div class="col-xs-10">
<pre><?=$dkim['dkim_txt'];?></pre>
</div>
</div>
<?php
}
}
else {
?>
<div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
<?php
}
}
elseif (isset($_GET['mailbox']) && filter_var($_GET["mailbox"], FILTER_VALIDATE_EMAIL) && !empty($_GET["mailbox"])) {
$mailbox = $_GET["mailbox"];
$result = mailbox('get', 'mailbox_details', $mailbox);
if (!empty($result)) {
?>
<h4><?=$lang['edit']['mailbox'];?></h4>
<form class="form-horizontal" data-id="editmailbox" role="form" method="post">
<input type="hidden" value="0" name="sender_acl">
<input type="hidden" value="0" name="active">
<div class="form-group">
<label class="control-label col-sm-2" for="name"><?=$lang['edit']['full_name'];?>:</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="name" id="name" value="<?=htmlspecialchars($result['name'], ENT_QUOTES, 'UTF-8');?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="quota"><?=$lang['edit']['quota_mb'];?>:
<br /><span id="quotaBadge" class="badge">max. <?=intval($result['max_new_quota'] / 1048576)?> MiB</span>
</label>
<div class="col-sm-10">
<input type="number" name="quota" id="quota" id="destroyable" style="width:100%" min="1" max="<?=intval($result['max_new_quota'] / 1048576);?>" value="<?=intval($result['quota']) / 1048576;?>" class="form-control">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="sender_acl"><?=$lang['edit']['sender_acl'];?>:</label>
<div class="col-sm-10">
<select data-width="100%" style="width:100%" id="sender_acl" name="sender_acl" size="10" multiple>
<?php
$sender_acl_handles = mailbox('get', 'sender_acl_handles', $mailbox);
foreach ($sender_acl_handles['sender_acl_domains']['ro'] as $domain):
?>
<option data-subtext="Admin" value="<?=htmlspecialchars($domain);?>" disabled selected><?=htmlspecialchars(sprintf($lang['edit']['dont_check_sender_acl'], $domain));?></option>
<?php
endforeach;
foreach ($sender_acl_handles['sender_acl_addresses']['ro'] as $domain):
?>
<option data-subtext="Admin" disabled selected><?=htmlspecialchars($alias);?></option>
<?php
endforeach;
foreach ($sender_acl_handles['fixed_sender_aliases'] as $alias):
?>
<option data-subtext="Alias" disabled selected><?=htmlspecialchars($alias);?></option>
<?php
endforeach;
foreach ($sender_acl_handles['sender_acl_domains']['rw'] as $domain):
?>
<option value="<?=htmlspecialchars($domain);?>" selected><?=htmlspecialchars(sprintf($lang['edit']['dont_check_sender_acl'], $domain));?></option>
<?php
endforeach;
foreach ($sender_acl_handles['sender_acl_domains']['selectable'] as $domain):
?>
<option value="<?=htmlspecialchars($domain);?>"><?=htmlspecialchars(sprintf($lang['edit']['dont_check_sender_acl'], $domain));?></option>
<?php
endforeach;
foreach ($sender_acl_handles['sender_acl_addresses']['rw'] as $address):
?>
<option selected><?=htmlspecialchars($address);?></option>
<?php
endforeach;
foreach ($sender_acl_handles['sender_acl_addresses']['selectable'] as $address):
?>
<option><?=htmlspecialchars($address);?></option>
<?php
endforeach;
?>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password"><?=$lang['edit']['password'];?></label>
<div class="col-sm-10">
<input type="password" class="form-control" name="password" id="password" placeholder="<?=$lang['edit']['unchanged_if_empty'];?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password2"><?=$lang['edit']['password_repeat'];?></label>
<div class="col-sm-10">
<input type="password" class="form-control" name="password2" id="password2">
</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" id="edit_selected" data-id="editmailbox" data-item="<?=htmlspecialchars($result['username']);?>" data-api-url='edit/mailbox' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button>
</div>
</div>
</form>
<?php
}
}
elseif (isset($_GET['relayhost']) && is_numeric($_GET["relayhost"]) && !empty($_GET["relayhost"])) {
$relayhost = intval($_GET["relayhost"]);
$result = relayhost('details', $relayhost);
if (!empty($result)) {
?>
<h4><?=$lang['edit']['resource'];?></h4>
<form class="form-horizontal" role="form" method="post" data-id="editrelayhost">
<input type="hidden" value="0" name="active">
<div class="form-group">
<label class="control-label col-sm-2" for="hostname"><?=$lang['add']['hostname'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="hostname" id="hostname" value="<?=htmlspecialchars($result['hostname'], 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" id="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" class="form-control" name="password" id="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" id="edit_selected" data-id="editrelayhost" data-item="<?=htmlspecialchars($result['id']);?>" data-api-url='edit/relayhost' 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($_GET["resource"], FILTER_VALIDATE_EMAIL) && !empty($_GET["resource"])) {
$resource = $_GET["resource"];
$result = mailbox('get', 'resource_details', $resource);
if (!empty($result)) {
?>
<h4><?=$lang['edit']['resource'];?></h4>
<form class="form-horizontal" role="form" method="post" data-id="editresource">
<input type="hidden" value="0" name="active">
<input type="hidden" value="0" name="multiple_bookings">
<div class="form-group">
<label class="control-label col-sm-2" for="description"><?=$lang['add']['description'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="description" id="description" value="<?=htmlspecialchars($result['description'], ENT_QUOTES, 'UTF-8');?>" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="domain"><?=$lang['edit']['kind'];?>:</label>
<div class="col-sm-10">
<select name="kind" id="kind" title="<?=$lang['edit']['select'];?>" required>
<option value="location" <?=($result['kind'] == "location") ? "selected" : null;?>>Location</option>
<option value="group" <?=($result['kind'] == "group") ? "selected" : null;?>>Group</option>
<option value="thing" <?=($result['kind'] == "thing") ? "selected" : null;?>>Thing</option>
</select>
</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">
<div class="checkbox">
<label><input type="checkbox" value="1" name="multiple_bookings" <?=($result['multiple_bookings_int']=="1") ? "checked" : null;?>> <?=$lang['edit']['multiple_bookings'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-success" id="edit_selected" data-id="editresource" data-item="<?=htmlspecialchars($result['name']);?>" data-api-url='edit/resource' 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
}
}
}
if ($_SESSION['mailcow_cc_role'] == "admin" || $_SESSION['mailcow_cc_role'] == "domainadmin" || $_SESSION['mailcow_cc_role'] == "user") {
if (isset($_GET['syncjob']) &&
is_numeric($_GET['syncjob'])) {
$id = $_GET["syncjob"];
$result = mailbox('get', 'syncjob_details', $id);
if (!empty($result)) {
?>
<h4><?=$lang['edit']['syncjob'];?></h4>
<form class="form-horizontal" data-id="editsyncjob" role="form" method="post">
<input type="hidden" value="0" name="delete2duplicates">
<input type="hidden" value="0" name="delete1">
<input type="hidden" value="0" name="active">
<div class="form-group">
<label class="control-label col-sm-2" for="host1"><?=$lang['edit']['hostname'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="host1" id="host1" value="<?=htmlspecialchars($result['host1'], ENT_QUOTES, 'UTF-8');?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="port1">Port</label>
<div class="col-sm-10">
<input type="number" class="form-control" name="port1" id="port1" min="1" max="65535" value="<?=htmlspecialchars($result['port1'], ENT_QUOTES, 'UTF-8');?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="user1"><?=$lang['edit']['username'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="user1" id="user1" value="<?=htmlspecialchars($result['user1'], ENT_QUOTES, 'UTF-8');?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password1"><?=$lang['edit']['password'];?></label>
<div class="col-sm-10">
<input type="password" class="form-control" name="password1" id="password1" value="<?=htmlspecialchars($result['password1'], ENT_QUOTES, 'UTF-8');?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="enc1"><?=$lang['edit']['encryption'];?>:</label>
<div class="col-sm-10">
<select id="enc1" name="enc1">
<option <?=($result['enc1'] == "TLS") ? "selected" : null;?>>TLS</option>
<option <?=($result['enc1'] == "SSL") ? "selected" : null;?>>SSL</option>
<option <?=($result['enc1'] == "PLAIN") ? "selected" : null;?>>PLAIN</option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="mins_interval"><?=$lang['edit']['mins_interval'];?></label>
<div class="col-sm-10">
<input type="number" class="form-control" name="mins_interval" min="10" max="3600" value="<?=htmlspecialchars($result['mins_interval'], ENT_QUOTES, 'UTF-8');?>" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="subfolder2"><?=$lang['edit']['subfolder2'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="subfolder2" id="subfolder2" value="<?=htmlspecialchars($result['subfolder2'], ENT_QUOTES, 'UTF-8');?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="maxage"><?=$lang['edit']['maxage'];?></label>
<div class="col-sm-10">
<input type="number" class="form-control" name="maxage" id="maxage" value="<?=htmlspecialchars($result['maxage'], ENT_QUOTES, 'UTF-8');?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="exclude"><?=$lang['edit']['exclude'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="exclude" id="exclude" value="<?=htmlspecialchars($result['exclude'], 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="delete2duplicates" <?=($result['delete2duplicates']=="1") ? "checked" : "";?>> <?=$lang['edit']['delete2duplicates'];?></label>
</div>
</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="delete1" <?=($result['delete1']=="1") ? "checked" : "";?>> <?=$lang['edit']['delete1'];?></label>
</div>
</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" : "";?>> <?=$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" id="edit_selected" data-id="editsyncjob" data-item="<?=htmlspecialchars($result['id']);?>" data-api-url='edit/syncjob' 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
}
}
}
}
else {
?>
<div class="alert alert-danger" role="alert"><?=$lang['danger']['access_denied'];?></div>
<?php
}
?>
</div>
</div>
</div>
</div>
<a href="<?=$_SESSION['return_to'];?>">&#8592; <?=$lang['edit']['previous'];?></a>
</div> <!-- /container -->
<script type='text/javascript'>
<?php
$lang_user = json_encode($lang['user']);
echo "var lang = ". $lang_user . ";\n";
echo "var table_for_domain = '". ((isset($domain)) ? $domain : null) . "';\n";
echo "var csrf_token = '". $_SESSION['CSRF']['TOKEN'] . "';\n";
echo "var pagination_size = '". $PAGINATION_SIZE . "';\n";
?>
</script>
<script src="js/footable.min.js"></script>
<script src="js/edit.js"></script>
<?php
require_once("inc/footer.inc.php");
?>
diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php
index 7769e0d3..e6261172 100644
--- a/data/web/inc/functions.mailbox.inc.php
+++ b/data/web/inc/functions.mailbox.inc.php
@@ -1,3273 +1,3271 @@
<?php
function mailbox($_action, $_type, $_data = null) {
global $pdo;
global $redis;
global $lang;
switch ($_action) {
case 'add':
switch ($_type) {
case 'time_limited_alias':
if (!isset($_SESSION['acl']['spam_alias']) || $_SESSION['acl']['spam_alias'] != "1" ) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if (isset($_data['username']) && filter_var($_data['username'], FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data['username'])) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
else {
$username = $_data['username'];
}
}
else {
$username = $_SESSION['mailcow_cc_username'];
}
if (!is_numeric($_data["validity"]) || $_data["validity"] > 672) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['validity_missing'])
);
return false;
}
try {
$stmt = $pdo->prepare("SELECT `domain` FROM `mailbox` WHERE `username` = :username");
$stmt->execute(array(':username' => $_SESSION['mailcow_cc_username']));
$domain = $stmt->fetch(PDO::FETCH_ASSOC)['domain'];
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
$validity = strtotime("+".$_data["validity"]." hour");
$letters = 'abcefghijklmnopqrstuvwxyz1234567890';
$random_name = substr(str_shuffle($letters), 0, 24);
try {
$stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `goto`, `validity`) VALUES
(:address, :goto, :validity)");
$stmt->execute(array(
':address' => $random_name . '@' . $domain,
':goto' => $username,
':validity' => $validity
));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['mailbox_modified'], htmlspecialchars($usernames))
);
break;
case 'syncjob':
if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if (isset($_data['username']) && filter_var($_data['username'], FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data['username'])) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
else {
$username = $_data['username'];
}
}
elseif ($_SESSION['mailcow_cc_role'] == "user") {
$username = $_SESSION['mailcow_cc_username'];
}
else {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'No user defined'
);
return false;
}
$active = intval($_data['active']);
$delete2duplicates = intval($_data['delete2duplicates']);
$delete1 = intval($_data['delete1']);
$port1 = $_data['port1'];
$host1 = strtolower($_data['host1']);
$password1 = $_data['password1'];
$exclude = $_data['exclude'];
$maxage = $_data['maxage'];
$subfolder2 = $_data['subfolder2'];
$user1 = $_data['user1'];
$mins_interval = $_data['mins_interval'];
$enc1 = $_data['enc1'];
if (empty($subfolder2)) {
$subfolder2 = "";
}
if (!isset($maxage) || !filter_var($maxage, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32767)))) {
$maxage = "0";
}
if (!filter_var($port1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if (!filter_var($mins_interval, FILTER_VALIDATE_INT, array('options' => array('min_range' => 10, 'max_range' => 3600)))) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if (!is_valid_domain_name($host1)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if ($enc1 != "TLS" && $enc1 != "SSL" && $enc1 != "PLAIN") {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if (@preg_match("/" . $exclude . "/", null) === false) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
try {
$stmt = $pdo->prepare("SELECT '1' FROM `imapsync`
WHERE `user2` = :user2 AND `user1` = :user1 AND `host1` = :host1");
$stmt->execute(array(':user1' => $user1, ':user2' => $username, ':host1' => $host1));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
if ($num_results != 0) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['object_exists'], htmlspecialchars($host1 . ' / ' . $user1))
);
return false;
}
try {
$stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `delete1`, `maxage`, `subfolder2`, `host1`, `authmech1`, `user1`, `password1`, `mins_interval`, `port1`, `enc1`, `delete2duplicates`, `active`)
VALUES (:user2, :exclude, :maxage, :delete1, :subfolder2, :host1, :authmech1, :user1, :password1, :mins_interval, :port1, :enc1, :delete2duplicates, :active)");
$stmt->execute(array(
':user2' => $username,
':exclude' => $exclude,
':maxage' => $maxage,
':delete1' => $delete1,
':subfolder2' => $subfolder2,
':host1' => $host1,
':authmech1' => 'PLAIN',
':user1' => $user1,
':password1' => $password1,
':mins_interval' => $mins_interval,
':port1' => $port1,
':enc1' => $enc1,
':delete2duplicates' => $delete2duplicates,
':active' => $active,
));
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['mailbox_modified'], $username)
);
return true;
break;
case 'domain':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
$domain = idn_to_ascii(strtolower(trim($_data['domain'])));
$description = $_data['description'];
$aliases = $_data['aliases'];
$mailboxes = $_data['mailboxes'];
$maxquota = $_data['maxquota'];
$quota = $_data['quota'];
if ($maxquota > $quota) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['mailbox_quota_exceeds_domain_quota'])
);
return false;
}
if ($maxquota == "0" || empty($maxquota)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['maxquota_empty'])
);
return false;
}
$active = intval($_data['active']);
$relay_all_recipients = intval($_data['relay_all_recipients']);
$backupmx = intval($_data['backupmx']);
($relay_all_recipients == 1) ? $backupmx = '1' : null;
if (!is_valid_domain_name($domain)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['domain_invalid'])
);
return false;
}
foreach (array($quota, $maxquota, $mailboxes, $aliases) as $data) {
if (!is_numeric($data)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['object_is_not_numeric'], htmlspecialchars($data))
);
return false;
}
}
try {
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE `domain` = :domain");
$stmt->execute(array(':domain' => $domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain`
WHERE `alias_domain` = :domain");
$stmt->execute(array(':domain' => $domain));
$num_results = $num_results + count($stmt->fetchAll(PDO::FETCH_ASSOC));
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
if ($num_results != 0) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['domain_exists'], htmlspecialchars($domain))
);
return false;
}
try {
$stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `maxquota`, `quota`, `backupmx`, `active`, `relay_all_recipients`)
VALUES (:domain, :description, :aliases, :mailboxes, :maxquota, :quota, :backupmx, :active, :relay_all_recipients)");
$stmt->execute(array(
':domain' => $domain,
':description' => $description,
':aliases' => $aliases,
':mailboxes' => $mailboxes,
':maxquota' => $maxquota,
':quota' => $quota,
':backupmx' => $backupmx,
':active' => $active,
':relay_all_recipients' => $relay_all_recipients
));
try {
$redis->hSet('DOMAIN_MAP', $domain, 1);
}
catch (RedisException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Redis: '.$e
);
return false;
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['domain_added'], htmlspecialchars($domain))
);
}
catch (PDOException $e) {
mailbox('delete', 'domain', array('domain' => $domain));
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
break;
case 'alias':
$addresses = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['address']));
$gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['goto']));
$active = intval($_data['active']);
if (empty($addresses[0])) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['alias_empty'])
);
return false;
}
if (empty($gotos[0])) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['goto_empty'])
);
return false;
}
foreach ($gotos as &$goto) {
if (empty($goto)) {
continue;
}
$goto_domain = idn_to_ascii(substr(strstr($goto, '@'), 1));
$goto_local_part = strstr($goto, '@', true);
$goto = $goto_local_part.'@'.$goto_domain;
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox`
WHERE `kind` REGEXP 'location|thing|group'
AND `username`= :goto");
$stmt->execute(array(':goto' => $goto));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['goto_invalid'])
);
return false;
}
if (!filter_var($goto, FILTER_VALIDATE_EMAIL) === true) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['goto_invalid'])
);
return false;
}
}
$gotos = array_filter($gotos);
$goto = implode(",", $gotos);
foreach ($addresses as $address) {
if (empty($address)) {
continue;
}
if (in_array($address, $gotos)) {
continue;
}
$stmt = $pdo->prepare("SELECT `address` FROM `alias`
WHERE `address`= :address");
$stmt->execute(array(':address' => $address));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['is_alias_or_mailbox'], htmlspecialchars($address))
);
return false;
}
$domain = idn_to_ascii(substr(strstr($address, '@'), 1));
$local_part = strstr($address, '@', true);
$address = $local_part.'@'.$domain;
$domaindata = mailbox('get', 'domain_details', $domain);
if (is_array($domaindata) && $domaindata['aliases_left'] == "0") {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['max_alias_exceeded'])
);
return false;
}
try {
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)");
$stmt->execute(array(':domain1' => $domain, ':domain2' => $domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results == 0) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['domain_not_found'], htmlspecialchars($domain))
);
return false;
}
$stmt = $pdo->prepare("SELECT `address` FROM `alias`
WHERE `address`= :address");
$stmt->execute(array(':address' => $address));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['is_alias_or_mailbox'], htmlspecialchars($address))
);
return false;
}
$stmt = $pdo->prepare("SELECT `address` FROM `spamalias`
WHERE `address`= :address");
$stmt->execute(array(':address' => $address));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['is_spam_alias'], htmlspecialchars($address))
);
return false;
}
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
if ((!filter_var($address, FILTER_VALIDATE_EMAIL) === true) && !empty($local_part)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['alias_invalid'])
);
return false;
}
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
try {
$stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `goto`, `domain`, `active`)
VALUES (:address, :goto, :domain, :active)");
if (!filter_var($address, FILTER_VALIDATE_EMAIL) === true) {
$stmt->execute(array(
':address' => '@'.$domain,
':goto' => $goto,
':domain' => $domain,
':active' => $active
));
}
else {
$stmt->execute(array(
':address' => $address,
':goto' => $goto,
':domain' => $domain,
':active' => $active
));
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['alias_added'])
);
}
catch (PDOException $e) {
mailbox('delete', 'alias', array('address' => $address));
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['alias_added'])
);
break;
case 'alias_domain':
$active = intval($_data['active']);
$alias_domains = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['alias_domain']));
$target_domain = idn_to_ascii(strtolower(trim($_data['target_domain'])));
if (!is_valid_domain_name($target_domain)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['target_domain_invalid'])
);
return false;
}
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $target_domain)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
foreach ($alias_domains as $alias_domain) {
$alias_domain = idn_to_ascii(strtolower(trim($alias_domain)));
if (!is_valid_domain_name($alias_domain)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['alias_domain_invalid'])
);
return false;
}
if ($alias_domain == $target_domain) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['aliasd_targetd_identical'])
);
return false;
}
try {
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE `domain`= :target_domain");
$stmt->execute(array(':target_domain' => $target_domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results == 0) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['targetd_not_found'])
);
return false;
}
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `alias_domain`= :alias_domain
UNION
SELECT `alias_domain` FROM `alias_domain` WHERE `alias_domain`= :alias_domain_in_domain");
$stmt->execute(array(':alias_domain' => $alias_domain, ':alias_domain_in_domain' => $alias_domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['aliasd_exists'])
);
return false;
}
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
try {
$stmt = $pdo->prepare("INSERT INTO `alias_domain` (`alias_domain`, `target_domain`, `active`)
VALUES (:alias_domain, :target_domain, :active)");
$stmt->execute(array(
':alias_domain' => $alias_domain,
':target_domain' => $target_domain,
':active' => $active
));
}
catch (PDOException $e) {
mailbox('delete', 'alias_domain', array('alias_domain' => $alias_domain));
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['aliasd_added'], htmlspecialchars(implode(', ', $alias_domains)))
);
break;
case 'mailbox':
$local_part = strtolower(trim($_data['local_part']));
$domain = idn_to_ascii(strtolower(trim($_data['domain'])));
$username = $local_part . '@' . $domain;
if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['mailbox_invalid'])
);
return false;
}
if (empty($_data['local_part'])) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['mailbox_invalid'])
);
return false;
}
$password = $_data['password'];
$password2 = $_data['password2'];
$name = $_data['name'];
$quota_m = filter_var($_data['quota'], FILTER_SANITIZE_NUMBER_FLOAT);
if (empty($name)) {
$name = $local_part;
}
$active = intval($_data['active']);
$quota_b = ($quota_m * 1048576);
$maildir = $domain."/".$local_part."/";
if (!is_valid_domain_name($domain)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['domain_invalid'])
);
return false;
}
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
try {
$stmt = $pdo->prepare("SELECT `mailboxes`, `maxquota`, `quota` FROM `domain`
WHERE `domain` = :domain");
$stmt->execute(array(':domain' => $domain));
$DomainData = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT
COUNT(*) as count,
COALESCE(ROUND(SUM(`quota`)/1048576), 0) as `quota`
FROM `mailbox`
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `domain` = :domain");
$stmt->execute(array(':domain' => $domain));
$MailboxData = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT `local_part` FROM `mailbox` WHERE `local_part` = :local_part and `domain`= :domain");
$stmt->execute(array(':local_part' => $local_part, ':domain' => $domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['object_exists'], htmlspecialchars($username))
);
return false;
}
$stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE address= :username");
$stmt->execute(array(':username' => $username));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['is_alias'], htmlspecialchars($username))
);
return false;
}
$stmt = $pdo->prepare("SELECT `address` FROM `spamalias` WHERE `address`= :username");
$stmt->execute(array(':username' => $username));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['is_spam_alias'], htmlspecialchars($username))
);
return false;
}
$stmt = $pdo->prepare("SELECT `domain` FROM `domain` WHERE `domain`= :domain");
$stmt->execute(array(':domain' => $domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results == 0) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['domain_not_found'], htmlspecialchars($domain))
);
return false;
}
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
if (!is_numeric($quota_m) || $quota_m == "0") {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['quota_not_0_not_numeric'])
);
return false;
}
if (!empty($password) && !empty($password2)) {
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['password_complexity'])
);
return false;
}
if ($password != $password2) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['password_mismatch'])
);
return false;
}
$password_hashed = hash_password($password);
}
else {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['password_empty'])
);
return false;
}
if ($MailboxData['count'] >= $DomainData['mailboxes']) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['max_mailbox_exceeded'], $MailboxData['count'], $DomainData['mailboxes'])
);
return false;
}
if ($quota_m > $DomainData['maxquota']) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['mailbox_quota_exceeded'], $DomainData['maxquota'])
);
return false;
}
if (($MailboxData['quota'] + $quota_m) > $DomainData['quota']) {
$quota_left_m = ($DomainData['quota'] - $MailboxData['quota']);
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['mailbox_quota_left_exceeded'], $quota_left_m)
);
return false;
}
try {
$stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `maildir`, `quota`, `local_part`, `domain`, `active`)
VALUES (:username, :password_hashed, :name, :maildir, :quota_b, :local_part, :domain, :active)");
$stmt->execute(array(
':username' => $username,
':password_hashed' => $password_hashed,
':name' => $name,
':maildir' => $maildir,
':quota_b' => $quota_b,
':local_part' => $local_part,
':domain' => $domain,
':active' => $active
));
$stmt = $pdo->prepare("INSERT INTO `quota2` (`username`, `bytes`, `messages`)
VALUES (:username, '0', '0')");
$stmt->execute(array(':username' => $username));
$stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `goto`, `domain`, `active`)
VALUES (:username1, :username2, :domain, :active)");
$stmt->execute(array(
':username1' => $username,
':username2' => $username,
':domain' => $domain,
':active' => $active
));
$stmt = $pdo->prepare("INSERT INTO `user_acl` (`username`) VALUES (:username)");
$stmt->execute(array(
':username' => $username
));
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['mailbox_added'], htmlspecialchars($username))
);
}
catch (PDOException $e) {
mailbox('delete', 'mailbox', array('username' => $username));
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
break;
case 'resource':
$domain = idn_to_ascii(strtolower(trim($_data['domain'])));
$description = $_data['description'];
$local_part = preg_replace('/[^\da-z]/i', '', preg_quote($description, '/'));
$name = $local_part . '@' . $domain;
$kind = $_data['kind'];
$active = intval($_data['active']);
$multiple_bookings = intval($_data['multiple_bookings']);
if (!filter_var($name, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['resource_invalid'])
);
return false;
}
if (empty($description)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['description_invalid'])
);
return false;
}
if ($kind != 'location' && $kind != 'group' && $kind != 'thing') {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['resource_invalid'])
);
return false;
}
if (!is_valid_domain_name($domain)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['domain_invalid'])
);
return false;
}
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
try {
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :name");
$stmt->execute(array(':name' => $name));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['object_exists'], htmlspecialchars($name))
);
return false;
}
$stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE address= :name");
$stmt->execute(array(':name' => $name));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['is_alias'], htmlspecialchars($name))
);
return false;
}
$stmt = $pdo->prepare("SELECT `address` FROM `spamalias` WHERE `address`= :name");
$stmt->execute(array(':name' => $name));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['is_spam_alias'], htmlspecialchars($name))
);
return false;
}
$stmt = $pdo->prepare("SELECT `domain` FROM `domain` WHERE `domain`= :domain");
$stmt->execute(array(':domain' => $domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results == 0) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['domain_not_found'], htmlspecialchars($domain))
);
return false;
}
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
try {
$stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `maildir`, `quota`, `local_part`, `domain`, `active`, `multiple_bookings`, `kind`)
VALUES (:name, 'RESOURCE', :description, 'RESOURCE', 0, :local_part, :domain, :active, :multiple_bookings, :kind)");
$stmt->execute(array(
':name' => $name,
':description' => $description,
':local_part' => $local_part,
':domain' => $domain,
':active' => $active,
':kind' => $kind,
':multiple_bookings' => $multiple_bookings
));
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['resource_added'], htmlspecialchars($name))
);
}
catch (PDOException $e) {
mailbox('delete', 'resource', array('name' => $name));
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
break;
}
break;
case 'edit':
switch ($_type) {
case 'alias_domain':
$alias_domains = (array)$_data['alias_domain'];
foreach ($alias_domains as $alias_domain) {
$alias_domain = idn_to_ascii(strtolower(trim($alias_domain)));
$is_now = mailbox('get', 'alias_domain_details', $alias_domain);
if (!empty($is_now)) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
$target_domain = (!empty($_data['target_domain'])) ? idn_to_ascii(strtolower(trim($_data['target_domain']))) : $is_now['target_domain'];
}
else {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['alias_domain_invalid'])
);
return false;
}
if (!is_valid_domain_name($target_domain)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['target_domain_invalid'])
);
return false;
}
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $target_domain)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if (empty(mailbox('get', 'domain_details', $target_domain)) || !empty(mailbox('get', 'alias_domain_details', $target_domain))) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['target_domain_invalid'])
);
return false;
}
try {
$stmt = $pdo->prepare("UPDATE `alias_domain` SET
`target_domain` = :target_domain,
`active` = :active
WHERE `alias_domain` = :alias_domain");
$stmt->execute(array(
':alias_domain' => $alias_domain,
':target_domain' => $target_domain,
':active' => $active
));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['aliasd_modified'], htmlspecialchars(implode(', ', $alias_domains)))
);
break;
case 'tls_policy':
if (!is_array($_data['username'])) {
$usernames = array();
$usernames[] = $_data['username'];
}
else {
$usernames = $_data['username'];
}
if (!isset($_SESSION['acl']['tls_policy']) || $_SESSION['acl']['tls_policy'] != "1" ) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
foreach ($usernames as $username) {
if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
$is_now = mailbox('get', 'tls_policy', $username);
if (!empty($is_now)) {
$tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in'];
$tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : $is_now['tls_enforce_out'];
}
else {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
try {
$stmt = $pdo->prepare("UPDATE `mailbox` SET `tls_enforce_out` = :tls_out, `tls_enforce_in` = :tls_in WHERE `username` = :username");
$stmt->execute(array(
':tls_out' => $tls_enforce_out,
':tls_in' => $tls_enforce_in,
':username' => $username
));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['mailbox_modified'], implode(', ', $usernames))
);
break;
case 'spam_score':
if (!is_array($_data['username'])) {
$usernames = array();
$usernames[] = $_data['username'];
}
else {
$usernames = $_data['username'];
}
if (!isset($_SESSION['acl']['spam_score']) || $_SESSION['acl']['spam_score'] != "1" ) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
foreach ($usernames as $username) {
$lowspamlevel = explode(',', $_data['spam_score'])[0];
$highspamlevel = explode(',', $_data['spam_score'])[1];
if (!is_numeric($lowspamlevel) || !is_numeric($highspamlevel)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
try {
$stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username
AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option`, `value`)
VALUES (:username, 'highspamlevel', :highspamlevel)");
$stmt->execute(array(
':username' => $username,
':highspamlevel' => $highspamlevel
));
$stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option`, `value`)
VALUES (:username, 'lowspamlevel', :lowspamlevel)");
$stmt->execute(array(
':username' => $username,
':lowspamlevel' => $lowspamlevel
));
}
catch (PDOException $e) {
$stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username
AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')");
$stmt->execute(array(
':username' => $username
));
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['mailbox_modified'], implode(', ', $usernames))
);
break;
case 'time_limited_alias':
if (!isset($_SESSION['acl']['spam_alias']) || $_SESSION['acl']['spam_alias'] != "1" ) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if (!is_array($_data['address'])) {
$addresses = array();
$addresses[] = $_data['address'];
}
else {
$addresses = $_data['address'];
}
foreach ($addresses as $address) {
try {
$stmt = $pdo->prepare("SELECT `goto` FROM `spamalias` WHERE `address` = :address");
$stmt->execute(array(':address' => $address));
$goto = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $goto)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
try {
$stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = (`validity` + 3600) WHERE
`address` = :address AND
`validity` >= :validity");
$stmt->execute(array(
':address' => $address,
':validity' => time()
));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['mailbox_modified'], htmlspecialchars(implode(', ', $usernames)))
);
break;
case 'delimiter_action':
if (!is_array($_data['username'])) {
$usernames = array();
$usernames[] = $_data['username'];
}
else {
$usernames = $_data['username'];
}
if (!isset($_SESSION['acl']['delimiter_action']) || $_SESSION['acl']['delimiter_action'] != "1" ) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
foreach ($usernames as $username) {
if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if (isset($_data['tagged_mail_handler']) && $_data['tagged_mail_handler'] == "subject") {
try {
$redis->hSet('RCPT_WANTS_SUBJECT_TAG', $username, 1);
}
catch (RedisException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Redis: '.$e
);
return false;
}
}
else {
try {
$redis->hDel('RCPT_WANTS_SUBJECT_TAG', $username);
}
catch (RedisException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Redis: '.$e
);
return false;
}
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['mailbox_modified'], implode(', ', $usernames))
);
break;
case 'domain_ratelimit':
$rl_value = intval($_data['rl_value']);
$rl_frame = $_data['rl_frame'];
if (!in_array($rl_frame, array('s', 'm', 'h'))) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Ratelimit time frame is incorrect'
);
return false;
}
if (!is_array($_data['domain'])) {
$domains = array();
$domains[] = $_data['domain'];
}
else {
$domains = $_data['domain'];
}
foreach ($domains as $domain) {
if (!is_valid_domain_name($domain) || !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if (empty($rl_value)) {
try {
- $redis->hDel('RL_OBJECT', $domain);
$redis->hDel('RL_VALUE', $domain);
}
catch (RedisException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Redis: '.$e
);
return false;
}
}
else {
try {
- $redis->hSet('RL_OBJECT', $domain, '1');
$redis->hSet('RL_VALUE', $domain, $rl_value . ' / 1' . $rl_frame);
}
catch (RedisException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Redis: '.$e
);
return false;
}
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['domain_modified'], implode(', ', $domains))
);
break;
case 'syncjob':
if (!is_array($_data['id'])) {
$ids = array();
$ids[] = $_data['id'];
}
else {
$ids = $_data['id'];
}
if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
foreach ($ids as $id) {
$is_now = mailbox('get', 'syncjob_details', $id);
if (!empty($is_now)) {
$username = $is_now['user2'];
$user1 = (!empty($_data['user1'])) ? $_data['user1'] : $is_now['user1'];
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
$delete2duplicates = (isset($_data['delete2duplicates'])) ? intval($_data['delete2duplicates']) : $is_now['delete2duplicates'];
$delete1 = (isset($_data['delete1'])) ? intval($_data['delete1']) : $is_now['delete1'];
$port1 = (!empty($_data['port1'])) ? $_data['port1'] : $is_now['port1'];
$password1 = (!empty($_data['password1'])) ? $_data['password1'] : $is_now['password1'];
$host1 = (!empty($_data['host1'])) ? $_data['host1'] : $is_now['host1'];
$subfolder2 = (!empty($_data['subfolder2'])) ? $_data['subfolder2'] : $is_now['subfolder2'];
$enc1 = (!empty($_data['enc1'])) ? $_data['enc1'] : $is_now['enc1'];
$mins_interval = (!empty($_data['mins_interval'])) ? $_data['mins_interval'] : $is_now['mins_interval'];
$exclude = (!empty($_data['exclude'])) ? $_data['exclude'] : $is_now['exclude'];
$maxage = (!empty($_data['maxage'])) ? $_data['maxage'] : $is_now['maxage'];
}
else {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if (empty($subfolder2)) {
$subfolder2 = "";
}
if (!isset($maxage) || !filter_var($maxage, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32767)))) {
$maxage = "0";
}
if (!filter_var($port1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if (!filter_var($mins_interval, FILTER_VALIDATE_INT, array('options' => array('min_range' => 10, 'max_range' => 3600)))) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if (!is_valid_domain_name($host1)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if ($enc1 != "TLS" && $enc1 != "SSL" && $enc1 != "PLAIN") {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if (@preg_match("/" . $exclude . "/", null) === false) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
try {
$stmt = $pdo->prepare("UPDATE `imapsync` SET `delete1` = :delete1, `maxage` = :maxage, `subfolder2` = :subfolder2, `exclude` = :exclude, `host1` = :host1, `user1` = :user1, `password1` = :password1, `mins_interval` = :mins_interval, `port1` = :port1, `enc1` = :enc1, `delete2duplicates` = :delete2duplicates, `active` = :active
WHERE `id` = :id");
$stmt->execute(array(
':delete1' => $delete1,
':id' => $id,
':exclude' => $exclude,
':maxage' => $maxage,
':subfolder2' => $subfolder2,
':host1' => $host1,
':user1' => $user1,
':password1' => $password1,
':mins_interval' => $mins_interval,
':port1' => $port1,
':enc1' => $enc1,
':delete2duplicates' => $delete2duplicates,
':active' => $active,
));
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['mailbox_modified'], $username)
);
return true;
break;
case 'alias':
if (!is_array($_data['address'])) {
$addresses = array();
$addresses[] = $_data['address'];
}
else {
$addresses = $_data['address'];
}
foreach ($addresses as $address) {
$is_now = mailbox('get', 'alias_details', $address);
if (!empty($is_now)) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
$goto = (!empty($_data['goto'])) ? $_data['goto'] : $is_now['goto'];
}
else {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['alias_invalid'])
);
return false;
}
$gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['goto']));
foreach ($gotos as &$goto) {
if (empty($goto)) {
continue;
}
if (!filter_var($goto, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' =>sprintf($lang['danger']['goto_invalid'])
);
return false;
}
if ($goto == $address) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['alias_goto_identical'])
);
return false;
}
}
$gotos = array_filter($gotos);
$goto = implode(",", $gotos);
$domain = idn_to_ascii(substr(strstr($address, '@'), 1));
$local_part = strstr($address, '@', true);
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if ((!filter_var($address, FILTER_VALIDATE_EMAIL) === true) && !empty($local_part)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['alias_invalid'])
);
return false;
}
try {
if (!empty($goto)) {
$stmt = $pdo->prepare("UPDATE `alias` SET
`goto` = :goto,
`active`= :active
WHERE `address` = :address");
$stmt->execute(array(
':goto' => $goto,
':active' => $active,
':address' => $address
));
}
else {
$stmt = $pdo->prepare("UPDATE `alias` SET
`active`= :active
WHERE `address` = :address");
$stmt->execute(array(
':active' => $active,
':address' => $address
));
}
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['alias_modified'], htmlspecialchars(implode(', ', $addresses)))
);
break;
case 'domain':
if (!is_array($_data['domain'])) {
$domains = array();
$domains[] = $_data['domain'];
}
else {
$domains = $_data['domain'];
}
foreach ($domains as $domain) {
$domain = idn_to_ascii($domain);
if (!is_valid_domain_name($domain)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['domain_invalid'])
);
return false;
}
if ($_SESSION['mailcow_cc_role'] == "domainadmin" &&
hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$description = $_data['description'];
$active = intval($_data['active']);
try {
$stmt = $pdo->prepare("UPDATE `domain` SET
`description` = :description
WHERE `domain` = :domain");
$stmt->execute(array(
':description' => $description,
':domain' => $domain
));
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['domain_modified'], htmlspecialchars($domain))
);
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
elseif ($_SESSION['mailcow_cc_role'] == "admin") {
$is_now = mailbox('get', 'domain_details', $domain);
if (!empty($is_now)) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
$backupmx = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : $is_now['backupmx_int'];
$relay_all_recipients = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : $is_now['relay_all_recipients_int'];
$relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : $is_now['relayhost'];
$aliases = (!empty($_data['aliases'])) ? $_data['aliases'] : $is_now['max_num_aliases_for_domain'];
$mailboxes = (!empty($_data['mailboxes'])) ? $_data['mailboxes'] : $is_now['max_num_mboxes_for_domain'];
$maxquota = (!empty($_data['maxquota'])) ? $_data['maxquota'] : ($is_now['max_quota_for_mbox'] / 1048576);
$quota = (!empty($_data['quota'])) ? $_data['quota'] : ($is_now['max_quota_for_domain'] / 1048576);
$description = (!empty($_data['description'])) ? $_data['description'] : $is_now['description'];
($relay_all_recipients == '1') ? $backupmx = '1' : null;
}
else {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['domain_invalid'])
);
return false;
}
try {
// todo: should be using api here
$stmt = $pdo->prepare("SELECT
COUNT(*) AS count,
MAX(COALESCE(ROUND(`quota`/1048576), 0)) AS `biggest_mailbox`,
COALESCE(ROUND(SUM(`quota`)/1048576), 0) AS `quota_all`
FROM `mailbox`
WHERE `kind` NOT REGEXP 'location|thing|group'
AND domain = :domain");
$stmt->execute(array(':domain' => $domain));
$MailboxData = $stmt->fetch(PDO::FETCH_ASSOC);
// todo: should be using api here
$stmt = $pdo->prepare("SELECT COUNT(*) AS `count` FROM `alias`
WHERE domain = :domain
AND address NOT IN (
SELECT `username` FROM `mailbox`
)");
$stmt->execute(array(':domain' => $domain));
$AliasData = $stmt->fetch(PDO::FETCH_ASSOC);
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
if ($maxquota > $quota) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['mailbox_quota_exceeds_domain_quota'])
);
return false;
}
if ($maxquota == "0" || empty($maxquota)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['maxquota_empty'])
);
return false;
}
if ($MailboxData['biggest_mailbox'] > $maxquota) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['max_quota_in_use'], $MailboxData['biggest_mailbox'])
);
return false;
}
if ($MailboxData['quota_all'] > $quota) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['domain_quota_m_in_use'], $MailboxData['quota_all'])
);
return false;
}
if ($MailboxData['count'] > $mailboxes) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['mailboxes_in_use'], $MailboxData['count'])
);
return false;
}
if ($AliasData['count'] > $aliases) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['aliases_in_use'], $AliasData['count'])
);
return false;
}
try {
$stmt = $pdo->prepare("UPDATE `domain` SET
`relay_all_recipients` = :relay_all_recipients,
`backupmx` = :backupmx,
`active` = :active,
`quota` = :quota,
`maxquota` = :maxquota,
`relayhost` = :relayhost,
`mailboxes` = :mailboxes,
`aliases` = :aliases,
`description` = :description
WHERE `domain` = :domain");
$stmt->execute(array(
':relay_all_recipients' => $relay_all_recipients,
':backupmx' => $backupmx,
':active' => $active,
':quota' => $quota,
':maxquota' => $maxquota,
':relayhost' => $relayhost,
':mailboxes' => $mailboxes,
':aliases' => $aliases,
':description' => $description,
':domain' => $domain
));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['domain_modified'], htmlspecialchars(implode(', ', $domains)))
);
break;
case 'mailbox':
if (!is_array($_data['username'])) {
$usernames = array();
$usernames[] = $_data['username'];
}
else {
$usernames = $_data['username'];
}
foreach ($usernames as $username) {
if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['username_invalid'])
);
return false;
}
$is_now = mailbox('get', 'mailbox_details', $username);
if (!empty($is_now)) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
$name = (!empty($_data['name'])) ? $_data['name'] : $is_now['name'];
$domain = $is_now['domain'];
$quota_m = (!empty($_data['quota'])) ? $_data['quota'] : ($is_now['quota'] / 1048576);
$quota_b = $quota_m * 1048576;
$password = (!empty($_data['password'])) ? $_data['password'] : null;
$password2 = (!empty($_data['password2'])) ? $_data['password2'] : null;
}
else {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
try {
$stmt = $pdo->prepare("SELECT `quota`, `maxquota`
FROM `domain`
WHERE `domain` = :domain");
$stmt->execute(array(':domain' => $domain));
$DomainData = $stmt->fetch(PDO::FETCH_ASSOC);
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if (!is_numeric($quota_m) || $quota_m == "0") {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['quota_not_0_not_numeric'], htmlspecialchars($quota_m))
);
return false;
}
if ($quota_m > $DomainData['maxquota']) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['mailbox_quota_exceeded'], $DomainData['maxquota'])
);
return false;
}
if (((($is_now['quota_used'] / 1048576) - $quota_m) + $quota_m) > $DomainData['quota']) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['mailbox_quota_left_exceeded'], ($is_now['max_new_quota'] / 1048576))
);
return false;
}
if (isset($_data['sender_acl'])) {
// Get sender_acl items set by admin
$sender_acl_admin = array_merge(
mailbox('get', 'sender_acl_handles', $username)['sender_acl_domains']['ro'],
mailbox('get', 'sender_acl_handles', $username)['sender_acl_addresses']['ro']
);
// Get sender_acl items from POST array
$sender_acl_domain_admin = ($_data['sender_acl'] == "0") ? array() : (array)$_data['sender_acl'];
if (!empty($sender_acl_domain_admin) || !empty($sender_acl_admin)) {
// Check items in POST array and skip invalid
foreach ($sender_acl_domain_admin as $key => $val) {
if (!filter_var($val, FILTER_VALIDATE_EMAIL) && !is_valid_domain_name(ltrim($val, '@'))) {
unset($sender_acl_domain_admin[$key]);
}
if (is_valid_domain_name(ltrim($val, '@'))) {
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], ltrim($val, '@'))) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['sender_acl_invalid'])
);
return false;
}
}
if (filter_var($val, FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $val)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['sender_acl_invalid'])
);
return false;
}
}
}
// Merge both arrays
$sender_acl_merged = array_merge($sender_acl_domain_admin, $sender_acl_admin);
try {
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :username");
$stmt->execute(array(
':username' => $username
));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
foreach ($sender_acl_merged as $sender_acl) {
$domain = ltrim($sender_acl, '@');
if (is_valid_domain_name($domain)) {
$sender_acl = '@' . $domain;
}
try {
$stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`)
VALUES (:sender_acl, :username)");
$stmt->execute(array(
':sender_acl' => $sender_acl,
':username' => $username
));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
}
else {
try {
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :username");
$stmt->execute(array(
':username' => $username
));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
}
if (!empty($password) && !empty($password2)) {
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['password_complexity'])
);
return false;
}
if ($password != $password2) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['password_mismatch'])
);
return false;
}
$password_hashed = hash_password($password);
try {
$stmt = $pdo->prepare("UPDATE `alias` SET
`active` = :active
WHERE `address` = :address");
$stmt->execute(array(
':address' => $username,
':active' => $active
));
$stmt = $pdo->prepare("UPDATE `mailbox` SET
`active` = :active,
`password` = :password_hashed,
`name`= :name,
`quota` = :quota_b
WHERE `username` = :username");
$stmt->execute(array(
':password_hashed' => $password_hashed,
':active' => $active,
':name' => $name,
':quota_b' => $quota_b,
':username' => $username
));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
try {
$stmt = $pdo->prepare("UPDATE `alias` SET
`active` = :active
WHERE `address` = :address");
$stmt->execute(array(
':address' => $username,
':active' => $active
));
$stmt = $pdo->prepare("UPDATE `mailbox` SET
`active` = :active,
`name`= :name,
`quota` = :quota_b
WHERE `username` = :username");
$stmt->execute(array(
':active' => $active,
':name' => $name,
':quota_b' => $quota_b,
':username' => $username
));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['mailbox_modified'], implode(', ', $usernames))
);
break;
case 'resource':
if (!is_array($_data['name'])) {
$names = array();
$names[] = $_data['name'];
}
else {
$names = $_data['name'];
}
foreach ($names as $name) {
$is_now = mailbox('get', 'resource_details', $name);
if (!empty($is_now)) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
$multiple_bookings = (isset($_data['multiple_bookings'])) ? intval($_data['multiple_bookings']) : $is_now['multiple_bookings_int'];
$description = (!empty($_data['description'])) ? $_data['description'] : $is_now['description'];
$kind = (!empty($_data['kind'])) ? $_data['kind'] : $is_now['kind'];
}
else {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['resource_invalid'])
);
return false;
}
if (!filter_var($name, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['resource_invalid'])
);
return false;
}
if (empty($description)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['description_invalid'])
);
return false;
}
if ($kind != 'location' && $kind != 'group' && $kind != 'thing') {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['resource_invalid'])
);
return false;
}
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $name)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
try {
$stmt = $pdo->prepare("UPDATE `mailbox` SET
`active` = :active,
`name`= :description,
`kind`= :kind,
`multiple_bookings`= :multiple_bookings
WHERE `username` = :name");
$stmt->execute(array(
':active' => $active,
':description' => $description,
':multiple_bookings' => $multiple_bookings,
':kind' => $kind,
':name' => $name
));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['resource_modified'], implode(', ', $names))
);
break;
}
break;
case 'get':
switch ($_type) {
case 'sender_acl_handles':
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
return false;
}
$data['sender_acl_domains']['ro'] = array();
$data['sender_acl_domains']['rw'] = array();
$data['sender_acl_domains']['selectable'] = array();
$data['sender_acl_addresses']['ro'] = array();
$data['sender_acl_addresses']['rw'] = array();
$data['sender_acl_addresses']['selectable'] = array();
$data['fixed_sender_aliases'] = array();
try {
// Fixed addresses
$stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` REGEXP :goto AND `address` NOT LIKE '@%'");
$stmt->execute(array(':goto' => '(^|,)'.$_data.'($|,)'));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($rows)) {
$data['fixed_sender_aliases'][] = $row['address'];
}
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias_domain_alias` FROM `mailbox`, `alias_domain`
WHERE `alias_domain`.`target_domain` = `mailbox`.`domain`
AND `mailbox`.`username` = :username");
$stmt->execute(array(':username' => $_data));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($rows)) {
if (!empty($row['alias_domain_alias'])) {
$data['fixed_sender_aliases'][] = $row['alias_domain_alias'];
}
}
// Return array $data['sender_acl_domains/addresses']['ro'] with read-only objects
// Return array $data['sender_acl_domains/addresses']['rw'] with read-write objects (can be deleted)
$stmt = $pdo->prepare("SELECT REPLACE(`send_as`, '@', '') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND `send_as` LIKE '@%'");
$stmt->execute(array(':logged_in_as' => $_data));
$domain_rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($domain_row = array_shift($domain_rows)) {
if (is_valid_domain_name($domain_row['send_as']) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain_row['send_as'])) {
$data['sender_acl_domains']['ro'][] = $domain_row['send_as'];
continue;
}
if (is_valid_domain_name($domain_row['send_as']) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain_row['send_as'])) {
$data['sender_acl_domains']['rw'][] = $domain_row['send_as'];
continue;
}
}
$stmt = $pdo->prepare("SELECT `send_as` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND `send_as` NOT LIKE '@%'");
$stmt->execute(array(':logged_in_as' => $_data));
$address_rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($address_row = array_shift($address_rows)) {
if (filter_var($address_row['send_as'], FILTER_VALIDATE_EMAIL) && !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $address_row['send_as'])) {
$data['sender_acl_addresses']['ro'][] = $address_row['send_as'];
continue;
}
if (filter_var($address_row['send_as'], FILTER_VALIDATE_EMAIL) && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $address_row['send_as'])) {
$data['sender_acl_addresses']['rw'][] = $address_row['send_as'];
continue;
}
}
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE `domain` NOT IN (
SELECT REPLACE(`send_as`, '@', '') FROM `sender_acl`
WHERE `logged_in_as` = :logged_in_as
AND `send_as` LIKE '@%')");
$stmt->execute(array(
':logged_in_as' => $_data,
));
$rows_domain = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row_domain = array_shift($rows_domain)) {
if (is_valid_domain_name($row_domain['domain']) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row_domain['domain'])) {
$data['sender_acl_domains']['selectable'][] = $row_domain['domain'];
}
}
$stmt = $pdo->prepare("SELECT `address` FROM `alias`
WHERE `goto` != :goto
AND `address` NOT IN (
SELECT `send_as` FROM `sender_acl`
WHERE `logged_in_as` = :logged_in_as
AND `send_as` NOT LIKE '@%')");
$stmt->execute(array(
':logged_in_as' => $_data,
':goto' => $_data
));
$rows_mbox = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($rows_mbox)) {
if (filter_var($row['address'], FILTER_VALIDATE_EMAIL) && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['address'])) {
$data['sender_acl_addresses']['selectable'][] = $row['address'];
}
}
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
return $data;
break;
case 'mailboxes':
$mailboxes = array();
if (isset($_data) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
elseif (isset($_data) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
try {
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` != 'ALL' AND `domain` = :domain");
$stmt->execute(array(
':domain' => $_data,
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$mailboxes[] = $row['username'];
}
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
else {
try {
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role");
$stmt->execute(array(
':username' => $_SESSION['mailcow_cc_username'],
':role' => $_SESSION['mailcow_cc_role'],
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$mailboxes[] = $row['username'];
}
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
return $mailboxes;
break;
case 'tls_policy':
$policydata = array();
if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
}
else {
$_data = $_SESSION['mailcow_cc_username'];
}
try {
$stmt = $pdo->prepare("SELECT `tls_enforce_out`, `tls_enforce_in` FROM `mailbox` WHERE `username` = :username");
$stmt->execute(array(':username' => $_data));
$policydata = $stmt->fetch(PDO::FETCH_ASSOC);
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
return $policydata;
break;
case 'syncjob_details':
$syncjobdetails = array();
if (!is_numeric($_data)) {
return false;
}
try {
$stmt = $pdo->prepare("SELECT *,
CONCAT(LEFT(`password1`, 3), '...') AS `password1_short`,
`active` AS `active_int`,
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
FROM `imapsync` WHERE id = :id");
$stmt->execute(array(':id' => $_data));
$syncjobdetails = $stmt->fetch(PDO::FETCH_ASSOC);
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
}
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $syncjobdetails['user2'])) {
return false;
}
return $syncjobdetails;
break;
case 'syncjobs':
$syncjobdata = array();
if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
}
else {
$_data = $_SESSION['mailcow_cc_username'];
}
try {
$stmt = $pdo->prepare("SELECT `id` FROM `imapsync` WHERE `user2` = :username");
$stmt->execute(array(':username' => $_data));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$syncjobdata[] = $row['id'];
}
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
}
return $syncjobdata;
break;
case 'spam_score':
$default = "5, 15";
$policydata = array();
if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
}
else {
$_data = $_SESSION['mailcow_cc_username'];
}
try {
$stmt = $pdo->prepare("SELECT `value` FROM `filterconf` WHERE `object` = :username AND
(`option` = 'lowspamlevel' OR `option` = 'highspamlevel')");
$stmt->execute(array(':username' => $_data));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
if (empty($num_results)) {
return $default;
}
else {
try {
$stmt = $pdo->prepare("SELECT `value` FROM `filterconf` WHERE `option` = 'highspamlevel' AND `object` = :username");
$stmt->execute(array(':username' => $_data));
$highspamlevel = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT `value` FROM `filterconf` WHERE `option` = 'lowspamlevel' AND `object` = :username");
$stmt->execute(array(':username' => $_data));
$lowspamlevel = $stmt->fetch(PDO::FETCH_ASSOC);
return $lowspamlevel['value'].', '.$highspamlevel['value'];
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
break;
case 'time_limited_aliases':
$tladata = array();
if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
}
else {
$_data = $_SESSION['mailcow_cc_username'];
}
try {
$stmt = $pdo->prepare("SELECT `address`,
`goto`,
`validity`
FROM `spamalias`
WHERE `goto` = :username
AND `validity` >= :unixnow");
$stmt->execute(array(':username' => $_data, ':unixnow' => time()));
$tladata = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
}
return $tladata;
break;
case 'delimiter_action':
$policydata = array();
if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
}
else {
$_data = $_SESSION['mailcow_cc_username'];
}
try {
if ($redis->hGet('RCPT_WANTS_SUBJECT_TAG', $_data)) {
return "subject";
}
else {
return "subfolder";
}
}
catch (RedisException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Redis: '.$e
);
return false;
}
break;
case 'resources':
$resources = array();
if (isset($_data) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
elseif (isset($_data) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
try {
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` REGEXP 'location|thing|group' AND `domain` != 'ALL' AND `domain` = :domain");
$stmt->execute(array(
':domain' => $_data,
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$resources[] = $row['username'];
}
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
else {
try {
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` REGEXP 'location|thing|group' AND `domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role");
$stmt->execute(array(
':username' => $_SESSION['mailcow_cc_username'],
':role' => $_SESSION['mailcow_cc_role'],
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$resources[] = $row['username'];
}
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
return $resources;
break;
case 'alias_domains':
$aliasdomains = array();
if (isset($_data) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
elseif (isset($_data) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
try {
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = :domain");
$stmt->execute(array(
':domain' => $_data,
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$aliasdomains[] = $row['alias_domain'];
}
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
else {
try {
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role");
$stmt->execute(array(
':username' => $_SESSION['mailcow_cc_username'],
':role' => $_SESSION['mailcow_cc_role'],
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$aliasdomains[] = $row['alias_domain'];
}
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
return $aliasdomains;
break;
case 'aliases':
$aliases = array();
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
try {
$stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `address` != `goto` AND `domain` = :domain");
$stmt->execute(array(
':domain' => $_data,
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$aliases[] = $row['address'];
}
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
return $aliases;
break;
case 'domain_ratelimit':
$aliases = array();
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
try {
- if (($rl_value = $redis->hGet('RL_VALUE', $_data)) && $redis->hGet('RL_OBJECT', $_data)) {
+ if ($rl_value = $redis->hGet('RL_VALUE', $_data)) {
$rl = explode(' / 1', $rl_value);
$data['value'] = $rl[0];
$data['frame'] = $rl[1];
return $data;
}
else {
return false;
}
}
catch (RedisException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Redis: '.$e
);
return false;
}
return false;
break;
case 'alias_details':
$aliasdata = array();
try {
$stmt = $pdo->prepare("SELECT
`domain`,
`goto`,
`address`,
`active` as `active_int`,
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`,
`created`,
`modified`
FROM `alias`
WHERE `address` = :address AND `address` != `goto`");
$stmt->execute(array(
':address' => $_data,
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain");
$stmt->execute(array(
':domain' => $row['domain'],
));
$row_alias_domain = $stmt->fetch(PDO::FETCH_ASSOC);
if (isset($row_alias_domain['target_domain']) && !empty($row_alias_domain['target_domain'])) {
$aliasdata['in_primary_domain'] = $row_alias_domain['target_domain'];
}
else {
$aliasdata['in_primary_domain'] = "";
}
$aliasdata['domain'] = $row['domain'];
$aliasdata['goto'] = $row['goto'];
$aliasdata['address'] = $row['address'];
(!filter_var($aliasdata['address'], FILTER_VALIDATE_EMAIL)) ? $aliasdata['is_catch_all'] = 1 : $aliasdata['is_catch_all'] = 0;
$aliasdata['active'] = $row['active'];
$aliasdata['active_int'] = $row['active_int'];
$aliasdata['created'] = $row['created'];
$aliasdata['modified'] = $row['modified'];
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdata['domain'])) {
return false;
}
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
return $aliasdata;
break;
case 'alias_domain_details':
$aliasdomaindata = array();
try {
$stmt = $pdo->prepare("SELECT
`alias_domain`,
`target_domain`,
`active` AS `active_int`,
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`,
`created`,
`modified`
FROM `alias_domain`
WHERE `alias_domain` = :aliasdomain");
$stmt->execute(array(
':aliasdomain' => $_data,
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$aliasdomaindata['alias_domain'] = $row['alias_domain'];
$aliasdomaindata['target_domain'] = $row['target_domain'];
$aliasdomaindata['active'] = $row['active'];
$aliasdomaindata['active_int'] = $row['active_int'];
$aliasdomaindata['created'] = $row['created'];
$aliasdomaindata['modified'] = $row['modified'];
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdomaindata['target_domain'])) {
return false;
}
return $aliasdomaindata;
break;
case 'domains':
$domains = array();
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
return false;
}
try {
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE (`domain` IN (
SELECT `domain` from `domain_admins`
WHERE (`active`='1' AND `username` = :username))
)
OR ('admin'= :role)
AND `domain` != 'ALL'");
$stmt->execute(array(
':username' => $_SESSION['mailcow_cc_username'],
':role' => $_SESSION['mailcow_cc_role'],
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$domains[] = $row['domain'];
}
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
return $domains;
break;
case 'domain_details':
$domaindata = array();
$_data = idn_to_ascii(strtolower(trim($_data)));
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
try {
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain");
$stmt->execute(array(
':domain' => $_data
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($row)) {
$_data = $row['target_domain'];
}
$stmt = $pdo->prepare("SELECT
`domain`,
`description`,
`aliases`,
`mailboxes`,
`maxquota`,
`quota`,
`relayhost`,
`relay_all_recipients` as `relay_all_recipients_int`,
`backupmx` as `backupmx_int`,
`active` as `active_int`,
CASE `relay_all_recipients` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `relay_all_recipients`,
CASE `backupmx` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `backupmx`,
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
FROM `domain` WHERE `domain`= :domain");
$stmt->execute(array(
':domain' => $_data
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)) {
return false;
}
$stmt = $pdo->prepare("SELECT COUNT(*) AS `count`,
COALESCE(SUM(`quota`), 0) AS `in_use`
FROM `mailbox`
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `domain` = :domain");
$stmt->execute(array(':domain' => $row['domain']));
$MailboxDataDomain = $stmt->fetch(PDO::FETCH_ASSOC);
$domaindata['max_new_mailbox_quota'] = ($row['quota'] * 1048576) - $MailboxDataDomain['in_use'];
if ($domaindata['max_new_mailbox_quota'] > ($row['maxquota'] * 1048576)) {
$domaindata['max_new_mailbox_quota'] = ($row['maxquota'] * 1048576);
}
$domaindata['quota_used_in_domain'] = $MailboxDataDomain['in_use'];
$domaindata['mboxes_in_domain'] = $MailboxDataDomain['count'];
$domaindata['mboxes_left'] = $row['mailboxes'] - $MailboxDataDomain['count'];
$domaindata['domain_name'] = $row['domain'];
$domaindata['description'] = $row['description'];
$domaindata['max_num_aliases_for_domain'] = $row['aliases'];
$domaindata['max_num_mboxes_for_domain'] = $row['mailboxes'];
$domaindata['max_quota_for_mbox'] = $row['maxquota'] * 1048576;
$domaindata['max_quota_for_domain'] = $row['quota'] * 1048576;
$domaindata['relayhost'] = $row['relayhost'];
$domaindata['backupmx'] = $row['backupmx'];
$domaindata['backupmx_int'] = $row['backupmx_int'];
$domaindata['active'] = $row['active'];
$domaindata['active_int'] = $row['active_int'];
$domaindata['relay_all_recipients'] = $row['relay_all_recipients'];
$domaindata['relay_all_recipients_int'] = $row['relay_all_recipients_int'];
$stmt = $pdo->prepare("SELECT COUNT(*) AS `alias_count` FROM `alias`
WHERE (`domain`= :domain OR `domain` IN (SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = :domain2))
AND `address` NOT IN (
SELECT `username` FROM `mailbox`
)");
$stmt->execute(array(
':domain' => $_data,
':domain2' => $_data
));
$AliasDataDomain = $stmt->fetch(PDO::FETCH_ASSOC);
(isset($AliasDataDomain['alias_count'])) ? $domaindata['aliases_in_domain'] = $AliasDataDomain['alias_count'] : $domaindata['aliases_in_domain'] = "0";
$domaindata['aliases_left'] = $row['aliases'] - $AliasDataDomain['alias_count'];
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
return $domaindata;
break;
case 'mailbox_details':
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
$mailboxdata = array();
try {
$stmt = $pdo->prepare("SELECT
`domain`.`backupmx`,
`mailbox`.`username`,
`mailbox`.`name`,
`mailbox`.`active` AS `active_int`,
CASE `mailbox`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`,
`mailbox`.`domain`,
`mailbox`.`quota`,
`quota2`.`bytes`,
`quota2`.`messages`
FROM `mailbox`, `quota2`, `domain`
WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group' AND `mailbox`.`username` = `quota2`.`username` AND `domain`.`domain` = `mailbox`.`domain` AND `mailbox`.`username` = :mailbox");
$stmt->execute(array(
':mailbox' => $_data,
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT `maxquota`, `quota` FROM `domain` WHERE `domain` = :domain");
$stmt->execute(array(':domain' => $row['domain']));
$DomainQuota = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` = :domain AND `username` != :username");
$stmt->execute(array(':domain' => $row['domain'], ':username' => $_data));
$MailboxUsage = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT IFNULL(COUNT(`address`), 0) AS `sa_count` FROM `spamalias` WHERE `goto` = :address AND `validity` >= :unixnow");
$stmt->execute(array(':address' => $_data, ':unixnow' => time()));
$SpamaliasUsage = $stmt->fetch(PDO::FETCH_ASSOC);
$mailboxdata['max_new_quota'] = ($DomainQuota['quota'] * 1048576) - $MailboxUsage['in_use'];
if ($mailboxdata['max_new_quota'] > ($DomainQuota['maxquota'] * 1048576)) {
$mailboxdata['max_new_quota'] = ($DomainQuota['maxquota'] * 1048576);
}
$mailboxdata['username'] = $row['username'];
$mailboxdata['is_relayed'] = $row['backupmx'];
$mailboxdata['name'] = $row['name'];
$mailboxdata['active'] = $row['active'];
$mailboxdata['active_int'] = $row['active_int'];
$mailboxdata['domain'] = $row['domain'];
$mailboxdata['quota'] = $row['quota'];
$mailboxdata['quota_used'] = intval($row['bytes']);
$mailboxdata['percent_in_use'] = round((intval($row['bytes']) / intval($row['quota'])) * 100);
$mailboxdata['messages'] = $row['messages'];
$mailboxdata['spam_aliases'] = $SpamaliasUsage['sa_count'];
if ($mailboxdata['percent_in_use'] >= 90) {
$mailboxdata['percent_class'] = "danger";
}
elseif ($mailboxdata['percent_in_use'] >= 75) {
$mailboxdata['percent_class'] = "warning";
}
else {
$mailboxdata['percent_class'] = "success";
}
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
return $mailboxdata;
break;
case 'resource_details':
$resourcedata = array();
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
try {
$stmt = $pdo->prepare("SELECT
`username`,
`name`,
`kind`,
`multiple_bookings` AS `multiple_bookings_int`,
`local_part`,
`active` AS `active_int`,
CASE `multiple_bookings` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `multiple_bookings`,
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`,
`domain`
FROM `mailbox` WHERE `kind` REGEXP 'location|thing|group' AND `username` = :resource");
$stmt->execute(array(
':resource' => $_data,
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$resourcedata['name'] = $row['username'];
$resourcedata['kind'] = $row['kind'];
$resourcedata['multiple_bookings'] = $row['multiple_bookings'];
$resourcedata['multiple_bookings_int'] = $row['multiple_bookings_int'];
$resourcedata['description'] = $row['name'];
$resourcedata['active'] = $row['active'];
$resourcedata['active_int'] = $row['active_int'];
$resourcedata['domain'] = $row['domain'];
$resourcedata['local_part'] = $row['local_part'];
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
if (!isset($resourcedata['domain']) ||
(isset($resourcedata['domain']) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $resourcedata['domain']))) {
return false;
}
return $resourcedata;
break;
}
break;
case 'delete':
switch ($_type) {
case 'syncjob':
if (!is_array($_data['id'])) {
$ids = array();
$ids[] = $_data['id'];
}
else {
$ids = $_data['id'];
}
if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
foreach ($ids as $id) {
if (!is_numeric($id)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => $id
);
return false;
}
try {
$stmt = $pdo->prepare("SELECT `user2` FROM `imapsync` WHERE id = :id");
$stmt->execute(array(':id' => $id));
$user2 = $stmt->fetch(PDO::FETCH_ASSOC)['user2'];
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $user2)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
$stmt = $pdo->prepare("DELETE FROM `imapsync` WHERE `id`= :id");
$stmt->execute(array(':id' => $id));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => 'Deleted syncjob id/s ' . implode(', ', $ids)
);
return true;
break;
case 'time_limited_alias':
if (!is_array($_data['address'])) {
$addresses = array();
$addresses[] = $_data['address'];
}
else {
$addresses = $_data['address'];
}
if (!isset($_SESSION['acl']['spam_alias']) || $_SESSION['acl']['spam_alias'] != "1" ) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
foreach ($addresses as $address) {
try {
$stmt = $pdo->prepare("SELECT `goto` FROM `spamalias` WHERE `address` = :address");
$stmt->execute(array(':address' => $address));
$goto = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $goto)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
try {
$stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `goto` = :username AND `address` = :item");
$stmt->execute(array(
':username' => $goto,
':item' => $address
));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['mailbox_modified'], htmlspecialchars($usernames))
);
break;
case 'eas_cache':
if (!is_array($_data['username'])) {
$usernames = array();
$usernames[] = $_data['username'];
}
else {
$usernames = $_data['username'];
}
if (!isset($_SESSION['acl']['eas_reset']) || $_SESSION['acl']['eas_reset'] != "1" ) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
foreach ($usernames as $username) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
try {
$stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username");
$stmt->execute(array(
':username' => $username
));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['eas_reset'], htmlspecialchars(implode(', ', $usernames)))
);
break;
case 'domain':
if (!is_array($_data['domain'])) {
$domains = array();
$domains[] = $_data['domain'];
}
else {
$domains = $_data['domain'];
}
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
foreach ($domains as $domain) {
if (!is_valid_domain_name($domain)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['domain_invalid'])
);
return false;
}
$domain = idn_to_ascii(strtolower(trim($domain)));
try {
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox`
WHERE `domain` = :domain");
$stmt->execute(array(':domain' => $domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
if ($num_results != 0 || !empty($num_results)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['domain_not_empty'])
);
return false;
}
try {
$stmt = $pdo->prepare("DELETE FROM `domain` WHERE `domain` = :domain");
$stmt->execute(array(
':domain' => $domain,
));
$stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `domain` = :domain");
$stmt->execute(array(
':domain' => $domain,
));
$stmt = $pdo->prepare("DELETE FROM `alias` WHERE `domain` = :domain");
$stmt->execute(array(
':domain' => $domain,
));
$stmt = $pdo->prepare("DELETE FROM `alias_domain` WHERE `target_domain` = :domain");
$stmt->execute(array(
':domain' => $domain,
));
$stmt = $pdo->prepare("DELETE FROM `mailbox` WHERE `domain` = :domain");
$stmt->execute(array(
':domain' => $domain,
));
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` LIKE :domain");
$stmt->execute(array(
':domain' => '%@'.$domain,
));
$stmt = $pdo->prepare("DELETE FROM `quota2` WHERE `username` = :domain");
$stmt->execute(array(
':domain' => '%@'.$domain,
));
$stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `address` = :domain");
$stmt->execute(array(
':domain' => '%@'.$domain,
));
$stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :domain");
$stmt->execute(array(
':domain' => '%@'.$domain,
));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
try {
$redis->hDel('DOMAIN_MAP', $domain);
}
catch (RedisException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Redis: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['domain_removed'], htmlspecialchars(implode(', ', $domains)))
);
return true;
break;
case 'alias':
if (!is_array($_data['address'])) {
$addresses = array();
$addresses[] = $_data['address'];
}
else {
$addresses = $_data['address'];
}
foreach ($addresses as $address) {
$local_part = strstr($address, '@', true);
$domain = mailbox('get', 'alias_details', $address)['domain'];
try {
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :address");
$stmt->execute(array(':address' => $address));
$gotos = $stmt->fetch(PDO::FETCH_ASSOC);
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
$goto_array = explode(',', $gotos['goto']);
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
try {
$stmt = $pdo->prepare("DELETE FROM `alias` WHERE `address` = :address AND `address` NOT IN (SELECT `username` FROM `mailbox`)");
$stmt->execute(array(
':address' => $address
));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['alias_removed'], htmlspecialchars(implode(', ', $addresses)))
);
break;
case 'alias_domain':
if (!is_array($_data['alias_domain'])) {
$alias_domains = array();
$alias_domains[] = $_data['alias_domain'];
}
else {
$alias_domains = $_data['alias_domain'];
}
foreach ($alias_domains as $alias_domain) {
if (!is_valid_domain_name($alias_domain)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['domain_invalid'])
);
return false;
}
try {
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain`
WHERE `alias_domain`= :alias_domain");
$stmt->execute(array(':alias_domain' => $alias_domain));
$DomainData = $stmt->fetch(PDO::FETCH_ASSOC);
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $DomainData['target_domain'])) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
try {
$stmt = $pdo->prepare("DELETE FROM `alias_domain` WHERE `alias_domain` = :alias_domain");
$stmt->execute(array(
':alias_domain' => $alias_domain,
));
$stmt = $pdo->prepare("DELETE FROM `alias` WHERE `domain` = :alias_domain");
$stmt->execute(array(
':alias_domain' => $alias_domain,
));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['alias_domain_removed'], htmlspecialchars(implode(', ', $alias_domains)))
);
break;
case 'mailbox':
if (!is_array($_data['username'])) {
$usernames = array();
$usernames[] = $_data['username'];
}
else {
$usernames = $_data['username'];
}
foreach ($usernames as $username) {
if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
try {
$stmt = $pdo->prepare("DELETE FROM `alias` WHERE `goto` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `quota2` WHERE `username` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `mailbox` WHERE `username` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `goto` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `imapsync` WHERE `user2` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_user_profile` WHERE `c_uid` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $username . "/%' OR `c_uid` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_quick_contact` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_quick_appointment` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_folder_info` WHERE `c_path2` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("SELECT `address`, `goto` FROM `alias`
WHERE `goto` REGEXP :username");
$stmt->execute(array(':username' => '(^|,)'.$username.'($|,)'));
$GotoData = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($GotoData as $gotos) {
$goto_exploded = explode(',', $gotos['goto']);
if (($key = array_search($username, $goto_exploded)) !== false) {
unset($goto_exploded[$key]);
}
$gotos_rebuild = implode(',', $goto_exploded);
$stmt = $pdo->prepare("UPDATE `alias` SET
`goto` = :goto
WHERE `address` = :address");
$stmt->execute(array(
':goto' => $gotos_rebuild,
':address' => $gotos['address']
));
}
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['mailbox_removed'], htmlspecialchars(implode(', ', $usernames)))
);
break;
case 'resource':
if (!is_array($_data['name'])) {
$names = array();
$names[] = $_data['name'];
}
else {
$names = $_data['name'];
}
foreach ($names as $name) {
if (!filter_var($name, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $name)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
try {
$stmt = $pdo->prepare("DELETE FROM `mailbox` WHERE `username` = :username");
$stmt->execute(array(
':username' => $name
));
$stmt = $pdo->prepare("DELETE FROM `sogo_user_profile` WHERE `c_uid` = :username");
$stmt->execute(array(
':username' => $name
));
$stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username");
$stmt->execute(array(
':username' => $name
));
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $name . "/%' OR `c_uid` = :username");
$stmt->execute(array(
':username' => $name
));
$stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
$stmt->execute(array(
':username' => $name
));
$stmt = $pdo->prepare("DELETE FROM `sogo_quick_contact` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
$stmt->execute(array(
':username' => $name
));
$stmt = $pdo->prepare("DELETE FROM `sogo_quick_appointment` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
$stmt->execute(array(
':username' => $name
));
$stmt = $pdo->prepare("DELETE FROM `sogo_folder_info` WHERE `c_path2` = :username");
$stmt->execute(array(
':username' => $name
));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['resource_removed'], htmlspecialchars(implode(', ', $names)))
);
break;
}
break;
}
}
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index d849e632..0d973dfe 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,358 +1,358 @@
version: '2.1'
services:
unbound-mailcow:
image: mailcow/unbound:1.0
build: ./data/Dockerfiles/unbound
command: /usr/sbin/unbound
depends_on:
mysql-mailcow:
condition: service_healthy
healthcheck:
test: ["CMD", "nslookup", "mailcow.email", "127.0.0.1"]
interval: 30s
timeout: 3s
retries: 10
volumes:
- ./data/conf/unbound/unbound.conf:/etc/unbound/unbound.conf:ro
restart: always
networks:
mailcow-network:
ipv4_address: 172.22.1.254
aliases:
- unbound
mysql-mailcow:
image: mariadb:10.1
command: mysqld --max_allowed_packet=128M --max-connections=1500
healthcheck:
test: ["CMD", "mysqladmin", "ping", "--host", "localhost", "--silent"]
interval: 5s
timeout: 5s
retries: 10
volumes:
- mysql-vol-1:/var/lib/mysql/
- ./data/conf/mysql/:/etc/mysql/conf.d/:ro
environment:
- MYSQL_ROOT_PASSWORD=${DBROOT}
- MYSQL_DATABASE=${DBNAME}
- MYSQL_USER=${DBUSER}
- MYSQL_PASSWORD=${DBPASS}
restart: always
dns:
- 172.22.1.254
dns_search: mailcow-network
networks:
mailcow-network:
ipv4_address: 172.22.1.250
aliases:
- mysql
redis-mailcow:
image: redis:alpine
depends_on:
unbound-mailcow:
condition: service_healthy
volumes:
- redis-vol-1:/data/
restart: always
dns:
- 172.22.1.254
dns_search: mailcow-network
networks:
mailcow-network:
ipv4_address: 172.22.1.249
aliases:
- redis
clamd-mailcow:
image: mailcow/clamd:1.2
build: ./data/Dockerfiles/clamd
restart: always
environment:
- SKIP_CLAMD=${SKIP_CLAMD:-n}
dns:
- 172.22.1.254
dns_search: mailcow-network
networks:
mailcow-network:
aliases:
- clamd
rspamd-mailcow:
- image: mailcow/rspamd:1.5
+ image: mailcow/rspamd:1.6
build: ./data/Dockerfiles/rspamd
command: >
/bin/bash -c "
sleep 5;
/usr/bin/rspamd -f -u _rspamd -g _rspamd
"
depends_on:
- nginx-mailcow
volumes:
- ./data/conf/rspamd/custom/:/etc/rspamd/custom:ro
- ./data/conf/rspamd/override.d/:/etc/rspamd/override.d:ro
- ./data/conf/rspamd/local.d/:/etc/rspamd/local.d:ro
- ./data/conf/rspamd/lua/:/etc/rspamd/lua/:ro
- dkim-vol-1:/data/dkim
- rspamd-vol-1:/var/lib/rspamd
restart: always
dns:
- 172.22.1.254
dns_search: mailcow-network
hostname: rspamd
networks:
mailcow-network:
ipv4_address: 172.22.1.253
aliases:
- rspamd
php-fpm-mailcow:
image: mailcow/phpfpm:1.0
build: ./data/Dockerfiles/phpfpm
command: "php-fpm -d date.timezone=${TZ}"
depends_on:
- redis-mailcow
volumes:
- ./data/web:/web:ro
- ./data/conf/rspamd/dynmaps:/dynmaps:ro
- dkim-vol-1:/data/dkim
environment:
- DBNAME=${DBNAME}
- DBUSER=${DBUSER}
- DBPASS=${DBPASS}
- MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
- IMAP_PORT=${IMAP_PORT:-143}
- IMAPS_PORT=${IMAPS_PORT:-993}
- POP_PORT=${POP_PORT:-110}
- POPS_PORT=${POPS_PORT:-995}
- SIEVE_PORT=${SIEVE_PORT:-4190}
- SUBMISSION_PORT=${SUBMISSION_PORT:-587}
- SMTPS_PORT=${SMTPS_PORT:-465}
- SMTP_PORT=${SMTP_PORT:-25}
restart: always
dns:
- 172.22.1.254
dns_search: mailcow-network
networks:
mailcow-network:
aliases:
- phpfpm
sogo-mailcow:
image: mailcow/sogo:1.5
build: ./data/Dockerfiles/sogo
depends_on:
unbound-mailcow:
condition: service_healthy
environment:
- DBNAME=${DBNAME}
- DBUSER=${DBUSER}
- DBPASS=${DBPASS}
- TZ=${TZ}
- MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
volumes:
- ./data/conf/sogo/:/etc/sogo/
restart: always
dns:
- 172.22.1.254
dns_search: mailcow-network
networks:
mailcow-network:
ipv4_address: 172.22.1.252
aliases:
- sogo
dovecot-mailcow:
image: mailcow/dovecot:1.6
build: ./data/Dockerfiles/dovecot
depends_on:
unbound-mailcow:
condition: service_healthy
volumes:
- ./data/conf/dovecot:/usr/local/etc/dovecot
- ./data/assets/ssl:/etc/ssl/mail/:ro
- ./data/conf/sogo/:/etc/sogo/
- vmail-vol-1:/var/vmail
- crypt-vol-1:/mail_crypt/
environment:
- DBNAME=${DBNAME}
- DBUSER=${DBUSER}
- DBPASS=${DBPASS}
ports:
- "${DOVEADM_PORT:-127.0.0.1:19991}:12345"
- "${IMAP_PORT:-143}:143"
- "${IMAPS_PORT:-993}:993"
- "${POP_PORT:-110}:110"
- "${POPS_PORT:-995}:995"
- "${SIEVE_PORT:-4190}:4190"
restart: always
ulimits:
nproc: 65535
nofile:
soft: 20000
hard: 40000
dns:
- 172.22.1.254
dns_search: mailcow-network
hostname: ${MAILCOW_HOSTNAME}
networks:
mailcow-network:
aliases:
- dovecot
postfix-mailcow:
image: mailcow/postfix:1.3
build: ./data/Dockerfiles/postfix
depends_on:
unbound-mailcow:
condition: service_healthy
volumes:
- ./data/conf/postfix:/opt/postfix/conf
- ./data/assets/ssl:/etc/ssl/mail/:ro
- postfix-vol-1:/var/spool/postfix
- crypt-vol-1:/var/lib/zeyple
environment:
- DBNAME=${DBNAME}
- DBUSER=${DBUSER}
- DBPASS=${DBPASS}
ports:
- "${SMTP_PORT:-25}:25"
- "${SMTPS_PORT:-465}:465"
- "${SUBMISSION_PORT:-587}:587"
restart: always
dns:
- 172.22.1.254
dns_search: mailcow-network
hostname: ${MAILCOW_HOSTNAME}
networks:
mailcow-network:
aliases:
- postfix
memcached-mailcow:
image: memcached:alpine
depends_on:
unbound-mailcow:
condition: service_healthy
restart: always
dns:
- 172.22.1.254
dns_search: mailcow-network
networks:
mailcow-network:
aliases:
- memcached
nginx-mailcow:
depends_on:
- sogo-mailcow
- php-fpm-mailcow
image: nginx:mainline-alpine
healthcheck:
test: ["CMD", "ping", "php-fpm-mailcow", "-c", "5"]
interval: 5s
timeout: 5s
retries: 10
command: /bin/sh -c "envsubst < /etc/nginx/conf.d/templates/listen_plain.template > /etc/nginx/conf.d/listen_plain.active &&
envsubst < /etc/nginx/conf.d/templates/listen_ssl.template > /etc/nginx/conf.d/listen_ssl.active &&
envsubst < /etc/nginx/conf.d/templates/server_name.template > /etc/nginx/conf.d/server_name.active &&
nginx -g 'daemon off;'"
environment:
- HTTPS_PORT=${HTTPS_PORT:-443}
- HTTP_PORT=${HTTP_PORT:-80}
- MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
volumes:
- ./data/web:/web:ro
- ./data/conf/rspamd/dynmaps:/dynmaps:ro
- ./data/assets/ssl/:/etc/ssl/mail/:ro
- ./data/conf/nginx/:/etc/nginx/conf.d/:rw
ports:
- "${HTTPS_BIND:-0.0.0.0}:${HTTPS_PORT:-443}:${HTTPS_PORT:-443}"
- "${HTTP_BIND:-0.0.0.0}:${HTTP_PORT:-80}:${HTTP_PORT:-80}"
restart: always
dns:
- 172.22.1.254
dns_search: mailcow-network
networks:
mailcow-network:
ipv4_address: 172.22.1.251
aliases:
- nginx
acme-mailcow:
depends_on:
- nginx-mailcow
image: mailcow/acme:1.14
build: ./data/Dockerfiles/acme
dns:
- 172.22.1.254
dns_search: mailcow-network
environment:
- ADDITIONAL_SAN=${ADDITIONAL_SAN}
- MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
- DBNAME=${DBNAME}
- DBUSER=${DBUSER}
- DBPASS=${DBPASS}
- SKIP_LETS_ENCRYPT=${SKIP_LETS_ENCRYPT:-n}
- SKIP_IP_CHECK=${SKIP_IP_CHECK:-n}
volumes:
- ./data/web/.well-known/acme-challenge:/var/www/acme:rw
- ./data/assets/ssl:/var/lib/acme/:rw
- ./data/assets/ssl-example:/var/lib/ssl-example/:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
# do not restart the container too often. Things get worse when we hit let's encrypt's ratelimit.
restart: on-failure:1
networks:
mailcow-network:
aliases:
- acme
fail2ban-mailcow:
image: mailcow/fail2ban:1.5
build: ./data/Dockerfiles/fail2ban
depends_on:
- dovecot-mailcow
- postfix-mailcow
- sogo-mailcow
- php-fpm-mailcow
- redis-mailcow
restart: always
privileged: true
environment:
- TZ=${TZ}
- SKIP_FAIL2BAN=${SKIP_FAIL2BAN:-no}
network_mode: "host"
dns:
- 172.22.1.254
dns_search: mailcow-network
volumes:
- /lib/modules:/lib/modules:ro
ipv6nat:
image: robbertkl/ipv6nat
restart: always
privileged: true
network_mode: "host"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /lib/modules:/lib/modules:ro
networks:
mailcow-network:
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: 172.22.1.0/24
- subnet: fd4d:6169:6c63:6f77::/64
volumes:
vmail-vol-1:
mysql-vol-1:
dkim-vol-1:
redis-vol-1:
rspamd-vol-1:
postfix-vol-1:
crypt-vol-1:

File Metadata

Mime Type
text/x-diff
Expires
9月 9 Tue, 6:03 AM (11 h, 12 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5428
默认替代文本
(234 KB)

Event Timeline