Merge branch 'dev' into dns2
This commit is contained in:
commit
fcbbfe71bb
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,3 +16,4 @@ data/conf/rspamd/override.d/*
|
||||
!data/conf/nginx/dynmaps.conf
|
||||
!data/conf/nginx/site.conf
|
||||
data/conf/nginx/*.conf
|
||||
data/conf/dovecot/extra.conf
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=JWBSYHF4SMC68)
|
||||
|
||||
[![Donate (Bitcoin)](https://img.shields.io/badge/Donate-Bitcoin-blue.svg)](bitcoin:1E5rgzgA1sS3QH7r1ToWxRC3GEavfsGMrx)
|
||||
**mailcow Bitcoin donations:** 1E5rgzgA1sS3QH7r1ToWxRC3GEavfsGMrx
|
||||
|
||||
Please see [the official documentation](https://mailcow.github.io/mailcow-dockerized-docs/) for instructions.
|
||||
|
||||
|
@ -77,9 +77,12 @@ while true; do
|
||||
# Container ids may have changed
|
||||
CONTAINERS_RESTART=($(curl --silent --unix-socket /var/run/docker.sock http/containers/json | jq -rc 'map(select(.Names[] | contains ("nginx-mailcow") or contains ("postfix-mailcow") or contains ("dovecot-mailcow"))) | .[] .Id' | tr "\n" " "))
|
||||
|
||||
while read line; do
|
||||
SQL_DOMAIN_ARR+=("${line}")
|
||||
while read domain; do
|
||||
SQL_DOMAIN_ARR+=("${domain}")
|
||||
done < <(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain" -Bs)
|
||||
while read alias_domain; do
|
||||
SQL_DOMAIN_ARR+=("${alias_domain}")
|
||||
done < <(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT alias_domain FROM alias_domain" -Bs)
|
||||
|
||||
for SQL_DOMAIN in "${SQL_DOMAIN_ARR[@]}"; do
|
||||
A_CONFIG=$(dig A autoconfig.${SQL_DOMAIN} +short | tail -n 1)
|
||||
@ -138,20 +141,20 @@ while true; do
|
||||
done
|
||||
|
||||
# Unique elements
|
||||
ALL_VALIDATED=($(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} ${VALIDATED_MAILCOW_HOSTNAME} | xargs -n1 | sort -u | xargs))
|
||||
ALL_VALIDATED=($(echo ${VALIDATED_MAILCOW_HOSTNAME} ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
|
||||
if [[ -z ${ALL_VALIDATED[*]} ]]; then
|
||||
echo "Cannot validate hostnames, skipping Let's Encrypt..."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ORPHANED_SAN=($(echo ${SAN_ARRAY_NOW[*]} ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} ${MAILCOW_HOSTNAME} | tr ' ' '\n' | sort | uniq -u ))
|
||||
ORPHANED_SAN=($(echo ${SAN_ARRAY_NOW[*]} ${ALL_VALIDATED[*]} | tr ' ' '\n' | sort | uniq -u ))
|
||||
if [[ ! -z ${ORPHANED_SAN[*]} ]] && [[ ${ISSUER} != *"mailcow"* ]]; then
|
||||
DATE=$(date +%Y-%m-%d_%H_%M_%S)
|
||||
echo "Found orphaned SAN ${ORPHANED_SAN[*]} in certificate, moving old files to ${ACME_BASE}/acme/private/${DATE}.bak/, keeping key file..."
|
||||
mkdir -p ${ACME_BASE}/acme/private/${DATE}.bak/
|
||||
[[ -f ${ACME_BASE}/acme/private/account.key ]] && mv ${ACME_BASE}/acme/private/account.key ${ACME_BASE}/acme/private/${DATE}.bak/
|
||||
mv ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/acme/private/${DATE}.bak/
|
||||
mv ${ACME_BASE}/acme/cert.pem ${ACME_BASE}/acme/private/${DATE}.bak/
|
||||
[[ -f ${ACME_BASE}/acme/fullchain.pem ]] && mv ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/acme/private/${DATE}.bak/
|
||||
[[ -f ${ACME_BASE}/acme/cert.pem ]] && mv ${ACME_BASE}/acme/cert.pem ${ACME_BASE}/acme/private/${DATE}.bak/
|
||||
cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/acme/private/${DATE}.bak/ # Keep key for TLSA 3 1 1 records
|
||||
fi
|
||||
|
||||
|
@ -3,19 +3,16 @@ nodaemon=true
|
||||
|
||||
[program:syslog-ng]
|
||||
command=/usr/sbin/syslog-ng --foreground --no-caps
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
autostart=true
|
||||
stdout_syslog=true
|
||||
|
||||
[program:dovecot]
|
||||
command=/usr/local/sbin/dovecot -F
|
||||
autorestart=true
|
||||
|
||||
[program:logfiles]
|
||||
command=/usr/bin/tail -f /var/log/combined.log
|
||||
stdout_logfile=/dev/fd/1
|
||||
stdout_logfile_maxbytes=0
|
||||
|
||||
[program:cron]
|
||||
command=/usr/sbin/cron -f
|
||||
autorestart=true
|
||||
|
@ -13,9 +13,8 @@ source s_src {
|
||||
unix-stream("/dev/log");
|
||||
internal();
|
||||
};
|
||||
|
||||
destination d_combined { file("/var/log/combined.log"); };
|
||||
destination d_redis_persistent_log {
|
||||
destination d_stdout { pipe("/dev/stdout"); };
|
||||
destination d_redis_ui_log {
|
||||
redis(
|
||||
host("redis-mailcow")
|
||||
persist-name("redis1")
|
||||
@ -34,8 +33,8 @@ destination d_redis_f2b_channel {
|
||||
filter f_mail { facility(mail); };
|
||||
log {
|
||||
source(s_src);
|
||||
destination(d_combined);
|
||||
destination(d_stdout);
|
||||
filter(f_mail);
|
||||
destination(d_redis_persistent_log);
|
||||
destination(d_redis_ui_log);
|
||||
destination(d_redis_f2b_channel);
|
||||
};
|
||||
|
@ -25,14 +25,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
syslog-ng \
|
||||
syslog-ng-core \
|
||||
syslog-ng-mod-redis \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& touch /etc/default/locale
|
||||
|
||||
RUN addgroup --system --gid 600 zeyple
|
||||
RUN adduser --system --home /var/lib/zeyple --no-create-home --uid 600 --gid 600 --disabled-login zeyple
|
||||
RUN touch /var/log/zeyple.log && chown zeyple: /var/log/zeyple.log
|
||||
|
||||
RUN touch /etc/default/locale
|
||||
|
||||
COPY zeyple.py /usr/local/bin/zeyple.py
|
||||
COPY zeyple.conf /etc/zeyple.conf
|
||||
COPY supervisord.conf /etc/supervisor/supervisord.conf
|
||||
|
@ -20,12 +20,26 @@ dbname = ${DBNAME}
|
||||
query = SELECT IF( EXISTS( SELECT 'TLS_ACTIVE' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.goto WHERE (address='%s' OR address IN (SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain='%d')) AND mailbox.tls_enforce_in = '1' AND mailbox.active = '1'), 'reject_plaintext_session', NULL) AS 'tls_enforce_in';
|
||||
EOF
|
||||
|
||||
cat <<EOF > /opt/postfix/conf/sql/mysql_tls_enforce_out_policy.cf
|
||||
cat <<EOF > /opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf
|
||||
user = ${DBUSER}
|
||||
password = ${DBPASS}
|
||||
hosts = mysql
|
||||
dbname = ${DBNAME}
|
||||
query = SELECT IF( EXISTS( SELECT 'TLS_ACTIVE' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.goto WHERE (address='%s' OR address IN (SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain='%d')) AND mailbox.tls_enforce_out = '1' AND mailbox.active = '1'), 'smtp_enforced_tls:', NULL) AS 'tls_enforce_out';
|
||||
query = SELECT GROUP_CONCAT(transport SEPARATOR '') AS transport_maps
|
||||
FROM (
|
||||
SELECT IF(EXISTS(SELECT 'smtp_type' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.goto WHERE (address = '%s' OR address IN (SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain = '%d')) AND mailbox.tls_enforce_out = '1' AND mailbox.active = '1'), 'smtp_enforced_tls:', 'smtp:') AS 'transport'
|
||||
UNION ALL
|
||||
SELECT hostname AS transport FROM relayhosts LEFT OUTER JOIN domain ON domain.relayhost = relayhosts.id WHERE relayhosts.active = '1' AND domain = '%d' OR domain IN (SELECT target_domain FROM alias_domain WHERE alias_domain = '%d')
|
||||
)
|
||||
AS transport_view;
|
||||
EOF
|
||||
|
||||
cat <<EOF > /opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf
|
||||
user = ${DBUSER}
|
||||
password = ${DBPASS}
|
||||
hosts = mysql
|
||||
dbname = ${DBNAME}
|
||||
query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM relayhosts WHERE id IN (SELECT relayhost FROM domain WHERE CONCAT('@', domain) = '%s');
|
||||
EOF
|
||||
|
||||
cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_domain_catchall_maps.cf
|
||||
@ -110,6 +124,5 @@ if [[ $? != 0 ]]; then
|
||||
exit 1
|
||||
else
|
||||
postfix -c /opt/postfix/conf start
|
||||
supervisorctl restart postfix-maillog
|
||||
sleep 126144000
|
||||
fi
|
||||
|
@ -3,19 +3,16 @@ nodaemon=true
|
||||
|
||||
[program:syslog-ng]
|
||||
command=/usr/sbin/syslog-ng --foreground --no-caps
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
autostart=true
|
||||
stdout_syslog=true
|
||||
|
||||
[program:postfix]
|
||||
command=/opt/postfix.sh
|
||||
autorestart=true
|
||||
|
||||
[program:postfix-maillog]
|
||||
command=/bin/tail -f /var/log/zeyple.log /var/log/combined.log
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
|
||||
[unix_http_server]
|
||||
file=/var/tmp/supervisord.sock
|
||||
chmod=0770
|
||||
|
@ -13,9 +13,8 @@ source s_src {
|
||||
unix-stream("/dev/log");
|
||||
internal();
|
||||
};
|
||||
|
||||
destination d_combined { file("/var/log/combined.log"); };
|
||||
destination d_redis_persistent_log {
|
||||
destination d_stdout { pipe("/dev/stdout"); };
|
||||
destination d_redis_ui_log {
|
||||
redis(
|
||||
host("redis-mailcow")
|
||||
persist-name("redis1")
|
||||
@ -34,8 +33,8 @@ destination d_redis_f2b_channel {
|
||||
filter f_mail { facility(mail); };
|
||||
log {
|
||||
source(s_src);
|
||||
destination(d_combined);
|
||||
destination(d_stdout);
|
||||
filter(f_mail);
|
||||
destination(d_redis_persistent_log);
|
||||
destination(d_redis_ui_log);
|
||||
destination(d_redis_f2b_channel);
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
[zeyple]
|
||||
log_file = /var/log/zeyple.log
|
||||
log_file = /dev/null
|
||||
|
||||
[gpg]
|
||||
home = /var/lib/zeyple/keys
|
||||
|
@ -19,6 +19,7 @@ RUN apt-get update && apt-get install -y \
|
||||
&& chown _rspamd:_rspamd /run/rspamd
|
||||
|
||||
COPY settings.conf /etc/rspamd/modules.d/settings.conf
|
||||
COPY ratelimit.lua /usr/share/rspamd/lua/ratelimit.lua
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
717
data/Dockerfiles/rspamd/ratelimit.lua
Normal file
717
data/Dockerfiles/rspamd/ratelimit.lua
Normal file
@ -0,0 +1,717 @@
|
||||
--[[
|
||||
Copyright (c) 2011-2015, Vsevolod Stakhov <vsevolod@highsecure.ru>
|
||||
|
||||
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
|
||||
|
||||
local E = {}
|
||||
|
||||
-- Default settings for limits, 1-st member is burst, second is rate and the third is numeric type
|
||||
local settings = {
|
||||
}
|
||||
-- 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 ip_score_lower_bound = 10
|
||||
local ip_score_ham_multiplier = 1.1
|
||||
local ip_score_spam_divisor = 1.1
|
||||
|
||||
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 fun = require "fun"
|
||||
|
||||
local user_keywords = {'user'}
|
||||
|
||||
local limit_parser
|
||||
local function parse_string_limit(lim)
|
||||
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]
|
||||
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
|
||||
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()
|
||||
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']
|
||||
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'])
|
||||
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)
|
||||
if err then
|
||||
rspamd_logger.infox(task, 'got error while getting 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')
|
||||
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)
|
||||
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
|
||||
else
|
||||
bucket = 1
|
||||
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
|
||||
end
|
||||
|
||||
local _
|
||||
ret,_,upstream = 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
|
||||
|
||||
--- Check or update ratelimit
|
||||
local function rate_test_set(task, func)
|
||||
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,
|
||||
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 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
|
||||
(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 plim then
|
||||
table.insert(args, {{size, plim, 1}, rate_key})
|
||||
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
|
||||
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 plim then
|
||||
table.insert(args, {{size, plim, 1}, rate_key})
|
||||
end
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
local opts = rspamd_config:get_all_opt('ratelimit')
|
||||
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)
|
||||
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
|
||||
elseif type(lim) == 'string' then
|
||||
local plim, size = parse_string_limit(lim)
|
||||
if plim then
|
||||
settings[t] = {size, plim, 1}
|
||||
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)))
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
|
@ -3,9 +3,11 @@ nodaemon=true
|
||||
|
||||
[program:syslog-ng]
|
||||
command=/usr/sbin/syslog-ng --foreground --no-caps
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
autostart=true
|
||||
stdout_syslog=true
|
||||
priority=1
|
||||
|
||||
[program:cron]
|
||||
@ -22,22 +24,24 @@ priority=4
|
||||
|
||||
[program:reconf-domains]
|
||||
command=/reconf-domains.sh
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
priority=3
|
||||
autorestart=true
|
||||
|
||||
[program:sogo]
|
||||
command="/usr/sbin/sogod"
|
||||
user=sogo
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
autorestart = unexpected
|
||||
autostart = false
|
||||
priority=5
|
||||
|
||||
[program:sogo-syslog]
|
||||
command=/usr/bin/tail -f /var/log/combined.log
|
||||
stdout_logfile=/dev/fd/1
|
||||
stdout_logfile_maxbytes=0
|
||||
priority=6
|
||||
|
||||
[inet_http_server]
|
||||
port=9191
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
@version: 3.5
|
||||
@version: 3.8
|
||||
@include "scl.conf"
|
||||
options {
|
||||
chain_hostnames(off);
|
||||
@ -14,12 +14,10 @@ source s_src {
|
||||
internal();
|
||||
};
|
||||
source s_sogo {
|
||||
file("/var/log/sogo/sogo.log");
|
||||
pipe("/dev/sogo_log" owner(sogo) group(sogo));
|
||||
};
|
||||
destination d_combined {
|
||||
file("/var/log/combined.log");
|
||||
};
|
||||
destination d_redis_persistent_log {
|
||||
destination d_stdout { pipe("/dev/stdout"); };
|
||||
destination d_redis_ui_log {
|
||||
redis(
|
||||
host("redis-mailcow")
|
||||
persist-name("redis1")
|
||||
@ -37,11 +35,11 @@ destination d_redis_f2b_channel {
|
||||
};
|
||||
log {
|
||||
source(s_sogo);
|
||||
source(s_src);
|
||||
destination(d_combined);
|
||||
destination(d_redis_ui_log);
|
||||
destination(d_redis_f2b_channel);
|
||||
};
|
||||
log {
|
||||
source(s_sogo);
|
||||
destination(d_redis_persistent_log);
|
||||
destination(d_redis_f2b_channel);
|
||||
source(s_src);
|
||||
destination(d_stdout);
|
||||
};
|
||||
|
@ -1,3 +1,6 @@
|
||||
# --------------------------------------------------------------------------
|
||||
# Please create a file "extra.conf" for persistent overrides to dovecot.conf
|
||||
# --------------------------------------------------------------------------
|
||||
auth_mechanisms = plain login
|
||||
#mail_debug = yes
|
||||
log_path = syslog
|
||||
@ -31,6 +34,12 @@ passdb {
|
||||
args = /usr/local/etc/dovecot/sql/dovecot-mysql.conf
|
||||
driver = sql
|
||||
}
|
||||
# Set doveadm_password=your-secret-password in data/conf/dovecot/extra.conf (create if missing)
|
||||
service doveadm {
|
||||
inet_listener {
|
||||
port = 12345
|
||||
}
|
||||
}
|
||||
namespace inbox {
|
||||
inbox = yes
|
||||
location =
|
||||
@ -256,3 +265,4 @@ service imap-postlogin {
|
||||
unix_listener imap-postlogin {
|
||||
}
|
||||
}
|
||||
!include_try /usr/local/etc/dovecot/extra.conf
|
||||
|
@ -39,11 +39,11 @@ postscreen_greet_ttl = 2d
|
||||
postscreen_greet_wait = 3s
|
||||
postscreen_non_smtp_command_enable = no
|
||||
postscreen_pipelining_enable = no
|
||||
proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_out_policy.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf, $local_recipient_maps $mydestination $virtual_alias_maps $virtual_alias_domains $virtual_mailbox_maps $virtual_mailbox_domains $relay_recipient_maps $relay_domains $canonical_maps $sender_canonical_maps $recipient_canonical_maps $relocated_maps $transport_maps $mynetworks $smtpd_sender_login_maps
|
||||
proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf, $local_recipient_maps $mydestination $virtual_alias_maps $virtual_alias_domains $virtual_mailbox_maps $virtual_mailbox_domains $relay_recipient_maps $relay_domains $canonical_maps $sender_canonical_maps $recipient_canonical_maps $relocated_maps $transport_maps $mynetworks $smtpd_sender_login_maps
|
||||
queue_run_delay = 300s
|
||||
relay_domains = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_relay_domain_maps.cf
|
||||
relay_recipient_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_relay_recipient_maps.cf
|
||||
sender_dependent_default_transport_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_out_policy.cf
|
||||
sender_dependent_default_transport_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf
|
||||
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
|
||||
smtp_tls_cert_file = /etc/ssl/mail/cert.pem
|
||||
smtp_tls_key_file = /etc/ssl/mail/key.pem
|
||||
@ -94,3 +94,8 @@ mydestination = localhost.localdomain, localhost
|
||||
#content_filter=zeyple
|
||||
# Prefere IPv4, useful for v4-only envs
|
||||
smtp_address_preference = ipv4
|
||||
smtp_sender_dependent_authentication = yes
|
||||
smtp_sasl_auth_enable = yes
|
||||
smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf
|
||||
smtp_sasl_security_options =
|
||||
smtp_sasl_mechanism_filter = plain, login
|
||||
|
@ -5,7 +5,7 @@ allow_hdrfrom_mismatch = false;
|
||||
# If true, multiple from headers are allowed (but only first is used)
|
||||
allow_hdrfrom_multiple = true;
|
||||
# If true, username does not need to contain matching domain
|
||||
allow_username_mismatch = true;
|
||||
allow_username_mismatch = false;
|
||||
# If false, messages from authenticated users are not selected for signing
|
||||
auth_only = true;
|
||||
# Default path to key, can include '$domain' and '$selector' variables
|
||||
|
@ -1,18 +0,0 @@
|
||||
rates {
|
||||
# Limit for all mail per recipient (burst 100, rate 2 per minute)
|
||||
to = [100, 0.033333333];
|
||||
# Limit for all mail per one source ip (burst 30, rate 1.5 per minute)
|
||||
to_ip = [30, 0.025];
|
||||
# Limit for all mail per one source ip and from address (burst 20, rate 1 per minute)
|
||||
to_ip_from = [20, 0.01666666667];
|
||||
# Limit for all bounce mail (burst 10, rate 2 per hour)
|
||||
bounce_to = [10, 0.000555556];
|
||||
# Limit for bounce mail per one source ip (burst 5, rate 1 per hour)
|
||||
bounce_to_ip = [5, 0.000277778];
|
||||
# Limit for all mail per authenticated user (burst 20, rate 1 per minute)
|
||||
user = [20, 0.01666666667];
|
||||
}
|
||||
# If symbol is specified, then it is inserted instead of setting result
|
||||
#symbol = "R_RATELIMIT";
|
||||
whitelisted_rcpts = "postmaster,mailer-daemon";
|
||||
max_rcpt = 5;
|
@ -78,4 +78,5 @@
|
||||
//MySQL4DebugEnabled = YES;
|
||||
//SOGoUIxDebugEnabled = YES;
|
||||
//WODontZipResponse = YES;
|
||||
WOLogFile = "/dev/sogo_log";
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ $tfa_data = get_tfa();
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-3 col-sm-9">
|
||||
<button class="btn btn-default" id="edit_selected" data-id="admin" data-item="null" data-api-url='edit/admin' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
|
||||
<button class="btn btn-default" id="edit_selected" data-id="admin" data-item="null" data-api-url='edit/self' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@ -121,6 +121,18 @@ $tfa_data = get_tfa();
|
||||
|
||||
|
||||
<div role="tabpanel" class="tab-pane" id="tab-config">
|
||||
<div class="row">
|
||||
<div class="col-sm-2 hidden-xs">
|
||||
<div id="scrollbox" class="list-group">
|
||||
<a href="#dkim" class="list-group-item"><?=$lang['admin']['dkim_keys'];?></a>
|
||||
<a href="#fwdhosts" class="list-group-item"><?=$lang['admin']['forwarding_hosts'];?></a>
|
||||
<a href="#f2bparams" class="list-group-item"><?=$lang['admin']['f2b_parameters'];?></a>
|
||||
<a href="#relayhosts" class="list-group-item">Relayhosts</a>
|
||||
<a href="#top" class="list-group-item" style="border-top:1px dashed #dadada">↸ <?=$lang['admin']['to_top'];?></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
<span class="anchor" id="dkim"></span>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><?=$lang['admin']['dkim_keys'];?></div>
|
||||
<div class="panel-body">
|
||||
@ -254,6 +266,7 @@ XYZ
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="anchor" id="fwdhosts"></span>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><?=$lang['admin']['forwarding_hosts'];?></div>
|
||||
<div class="panel-body">
|
||||
@ -291,6 +304,7 @@ XYZ
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="anchor" id="f2bparams"></span>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><?=$lang['admin']['f2b_parameters'];?></div>
|
||||
<div class="panel-body">
|
||||
@ -318,6 +332,48 @@ XYZ
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="anchor" id="relayhosts"></span>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Relayhosts</div>
|
||||
<div class="panel-body">
|
||||
<p style="margin-bottom:40px"><?=$lang['admin']['relayhosts_hint'];?></p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-condensed" id="relayhoststable"></table>
|
||||
</div>
|
||||
<div class="mass-actions-admin">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button type="button" id="toggle_multi_select_all" data-id="rlyhosts" class="btn btn-default"><?=$lang['mailbox']['toggle_all'];?></button>
|
||||
<a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a id="edit_selected" data-id="rlyhosts" data-api-url='edit/relayhost' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li>
|
||||
<li><a id="edit_selected" data-id="rlyhosts" data-api-url='edit/relayhost' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li><a id="delete_selected" data-id="rlyhosts" data-api-url='delete/relayhost' href="#"><?=$lang['admin']['remove'];?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<legend><?=$lang['admin']['add_relayhost'];?></legend>
|
||||
<p class="help-block"><?=$lang['admin']['add_relayhost_add_hint'];?></p>
|
||||
<form class="form-inline" data-id="rlyhost" role="form" method="post">
|
||||
<div class="form-group">
|
||||
<label for="hostname"><?=$lang['admin']['host'];?></label>
|
||||
<input class="form-control" id="hostname" name="hostname" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="hostname"><?=$lang['admin']['username'];?></label>
|
||||
<input class="form-control" id="username" name="username">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="hostname"><?=$lang['admin']['password'];?></label>
|
||||
<input class="form-control" id="password" name="password">
|
||||
</div>
|
||||
<button class="btn btn-default" id="add_item" data-id="rlyhost" data-api-url='add/relayhost' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add'];?></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div role="tabpanel" class="tab-pane" id="tab-postfix-logs">
|
||||
|
@ -42,3 +42,14 @@ body.modal-open {
|
||||
-webkit-transform:rotateX(180deg);
|
||||
transform:rotateX(180deg);
|
||||
}
|
||||
.anchor {
|
||||
display: block;
|
||||
height: 65px;
|
||||
margin-top: -65px;
|
||||
visibility: hidden;
|
||||
}
|
||||
.scrollboxFixed {
|
||||
position: fixed;
|
||||
top: 65px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
@ -27,3 +27,6 @@ table.footable>tbody>tr.footable-empty>td {
|
||||
user-select: none;
|
||||
padding:10px 0 10px 0;
|
||||
}
|
||||
.inputMissingAttr {
|
||||
border-color: #FF4136;
|
||||
}
|
||||
|
@ -25,9 +25,8 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
?>
|
||||
<h4><?=$lang['edit']['alias'];?></h4>
|
||||
<br />
|
||||
<form class="form-horizontal" role="form" method="post" action="<?=($FORM_ACTION == "previous") ? $_SESSION['return_to'] : null;?>">
|
||||
<form class="form-horizontal" data-id="editalias" role="form" method="post">
|
||||
<input type="hidden" value="0" name="active">
|
||||
<input type="hidden" name="address" value="<?=htmlspecialchars($alias);?>">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="goto"><?=$lang['edit']['target_address'];?></label>
|
||||
<div class="col-sm-10">
|
||||
@ -43,7 +42,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button type="submit" name="mailbox_edit_alias" class="btn btn-success btn-sm"><?=$lang['edit']['save'];?></button>
|
||||
<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>
|
||||
@ -61,14 +60,13 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
$_GET["domainadmin"] != 'admin' &&
|
||||
$_SESSION['mailcow_cc_role'] == "admin") {
|
||||
$domain_admin = $_GET["domainadmin"];
|
||||
$result = get_domain_admin_details($domain_admin);
|
||||
$result = domain_admin('details', $domain_admin);
|
||||
if (!empty($result)) {
|
||||
?>
|
||||
<h4><?=$lang['edit']['domain_admin'];?></h4>
|
||||
<br />
|
||||
<form class="form-horizontal" role="form" method="post" action="<?=($FORM_ACTION == "previous") ? $_SESSION['return_to'] : null;?>">
|
||||
<form class="form-horizontal" data-id="editdomainadmin" role="form" method="post">
|
||||
<input type="hidden" value="0" name="active">
|
||||
<input type="hidden" name="username" value="<?=htmlspecialchars($domain_admin);?>">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="username_new"><?=$lang['edit']['username'];?></label>
|
||||
<div class="col-sm-10">
|
||||
@ -122,7 +120,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button type="submit" name="edit_domain_admin" class="btn btn-success btn-sm"><?=$lang['edit']['save'];?></button>
|
||||
<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>
|
||||
@ -139,14 +137,15 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
!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 class="form-horizontal" role="form" method="post" action="<?=($FORM_ACTION == "previous") ? $_SESSION['return_to'] : null;?>">
|
||||
<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">
|
||||
<input type="hidden" name="domain" value="<?=htmlspecialchars($domain);?>">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="description"><?=$lang['edit']['description'];?></label>
|
||||
<div class="col-sm-10">
|
||||
@ -178,6 +177,21 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
<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">
|
||||
@ -203,7 +217,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button type="submit" name="mailbox_edit_domain" class="btn btn-success btn-sm"><?=$lang['edit']['save'];?></button>
|
||||
<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>
|
||||
@ -223,6 +237,23 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
}
|
||||
?>
|
||||
<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>
|
||||
@ -282,12 +313,12 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
!empty($_GET["aliasdomain"])) {
|
||||
$alias_domain = $_GET["aliasdomain"];
|
||||
$result = mailbox('get', 'alias_domain_details', $alias_domain);
|
||||
$rl = mailbox('get', 'domain_ratelimit', $alias_domain);
|
||||
if (!empty($result)) {
|
||||
?>
|
||||
<h4><?=$lang['edit']['edit_alias_domain'];?></h4>
|
||||
<form class="form-horizontal" role="form" method="post" action="<?=($FORM_ACTION == "previous") ? $_SESSION['return_to'] : null;?>">
|
||||
<form class="form-horizontal" data-id="editaliasdomain" role="form" method="post">
|
||||
<input type="hidden" value="0" name="active">
|
||||
<input type="hidden" value="<?=$result['alias_domain'];?>" name="alias_domain">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="target_domain"><?=$lang['edit']['target_domain'];?></label>
|
||||
<div class="col-sm-10">
|
||||
@ -303,10 +334,27 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button type="submit" name="mailbox_edit_alias_domain" class="btn btn-success btn-sm"><?=$lang['edit']['save'];?></button>
|
||||
<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))) {
|
||||
?>
|
||||
@ -334,10 +382,9 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
if (!empty($result)) {
|
||||
?>
|
||||
<h4><?=$lang['edit']['mailbox'];?></h4>
|
||||
<form class="form-horizontal" role="form" method="post" action="<?=($FORM_ACTION == "previous") ? $_SESSION['return_to'] : null;?>">
|
||||
<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">
|
||||
<input type="hidden" name="username" value="<?=htmlspecialchars($result['username']);?>">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="name"><?=$lang['edit']['full_name'];?>:</label>
|
||||
<div class="col-sm-10">
|
||||
@ -355,7 +402,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
<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>
|
||||
<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);
|
||||
|
||||
@ -426,7 +473,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button type="submit" name="mailbox_edit_mailbox" class="btn btn-success btn-sm"><?=$lang['edit']['save'];?></button>
|
||||
<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>
|
||||
@ -439,10 +486,9 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
if (!empty($result)) {
|
||||
?>
|
||||
<h4><?=$lang['edit']['resource'];?></h4>
|
||||
<form class="form-horizontal" role="form" method="post" action="<?=($FORM_ACTION == "previous") ? $_SESSION['return_to'] : null;?>">
|
||||
<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">
|
||||
<input type="hidden" name="name" value="<?=htmlspecialchars($result['name']);?>">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="description"><?=$lang['add']['description'];?></label>
|
||||
<div class="col-sm-10">
|
||||
@ -475,7 +521,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button type="submit" name="mailbox_edit_resource" class="btn btn-success btn-sm"><?=$lang['edit']['save'];?></button>
|
||||
<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>
|
||||
@ -501,11 +547,10 @@ elseif (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] ==
|
||||
if (!empty($result)) {
|
||||
?>
|
||||
<h4><?=$lang['edit']['syncjob'];?></h4>
|
||||
<form class="form-horizontal" role="form" method="post" action="<?=($FORM_ACTION == "previous") ? $_SESSION['return_to'] : null;?>">
|
||||
<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">
|
||||
<input type="hidden" name="id" value="<?=htmlspecialchars($result['id']);?>">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="host1"><?=$lang['edit']['hostname'];?></label>
|
||||
<div class="col-sm-10">
|
||||
@ -587,7 +632,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] ==
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button type="submit" name="edit_syncjob" value="1" class="btn btn-success btn-sm"><?=$lang['edit']['save'];?></button>
|
||||
<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>
|
||||
|
507
data/web/inc/functions.domain_admin.inc.php
Normal file
507
data/web/inc/functions.domain_admin.inc.php
Normal file
@ -0,0 +1,507 @@
|
||||
<?php
|
||||
|
||||
function domain_admin($_action, $_data = null) {
|
||||
global $pdo;
|
||||
global $lang;
|
||||
switch ($_action) {
|
||||
case 'add':
|
||||
$username = strtolower(trim($_data['username']));
|
||||
$password = $_data['password'];
|
||||
$password2 = $_data['password2'];
|
||||
$domains = (array)$_data['domains'];
|
||||
$active = intval($_data['active']);
|
||||
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['access_denied'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (empty($domains)) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['domain_invalid'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username)) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['username_invalid'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox`
|
||||
WHERE `username` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
$num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
|
||||
$stmt = $pdo->prepare("SELECT `username` FROM `admin`
|
||||
WHERE `username` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
$num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
|
||||
$stmt = $pdo->prepare("SELECT `username` FROM `domain_admins`
|
||||
WHERE `username` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
$num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
}
|
||||
catch(PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
foreach ($num_results as $num_results_each) {
|
||||
if ($num_results_each != 0) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['object_exists'], htmlspecialchars($username))
|
||||
);
|
||||
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);
|
||||
foreach ($domains as $domain) {
|
||||
if (!is_valid_domain_name($domain)) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['domain_invalid'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
|
||||
VALUES (:username, :domain, :created, :active)");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
':domain' => $domain,
|
||||
':created' => date('Y-m-d H:i:s'),
|
||||
':active' => $active
|
||||
));
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
domain_admin('delete', $username);
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`)
|
||||
VALUES (:username, :password_hashed, '0', :active)");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
':password_hashed' => $password_hashed,
|
||||
':active' => $active
|
||||
));
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['password_empty'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'success',
|
||||
'msg' => sprintf($lang['success']['domain_admin_added'], htmlspecialchars($username))
|
||||
);
|
||||
break;
|
||||
case 'edit':
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['access_denied'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// Administrator
|
||||
if ($_SESSION['mailcow_cc_role'] == "admin") {
|
||||
if (!is_array($_data['username'])) {
|
||||
$usernames = array();
|
||||
$usernames[] = $_data['username'];
|
||||
}
|
||||
else {
|
||||
$usernames = $_data['username'];
|
||||
}
|
||||
foreach ($usernames as $username) {
|
||||
$is_now = domain_admin('details', $username);
|
||||
$domains = (isset($_data['domains'])) ? (array)$_data['domains'] : null;
|
||||
if (!empty($is_now)) {
|
||||
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
|
||||
$domains = (!empty($domains)) ? $domains : $is_now['selected_domains'];
|
||||
$username_new = (!empty($_data['username_new'])) ? $_data['username_new'] : $is_now['username'];
|
||||
}
|
||||
else {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['access_denied'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$password = $_data['password'];
|
||||
$password2 = $_data['password2'];
|
||||
|
||||
if (!empty($domains)) {
|
||||
foreach ($domains as $domain) {
|
||||
if (!is_valid_domain_name($domain)) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['domain_invalid'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username_new))) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['username_invalid'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if ($username_new != $username) {
|
||||
if (!empty(domain_admin('details', $username_new)['username'])) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['username_invalid'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
));
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($domains)) {
|
||||
foreach ($domains as $domain) {
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
|
||||
VALUES (:username_new, :domain, :created, :active)");
|
||||
$stmt->execute(array(
|
||||
':username_new' => $username_new,
|
||||
':domain' => $domain,
|
||||
':created' => date('Y-m-d H:i:s'),
|
||||
':active' => $active
|
||||
));
|
||||
}
|
||||
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 `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':password_hashed' => $password_hashed,
|
||||
':username_new' => $username_new,
|
||||
':username' => $username,
|
||||
':active' => $active
|
||||
));
|
||||
if (isset($_data['disable_tfa'])) {
|
||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
}
|
||||
else {
|
||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
|
||||
$stmt->execute(array(':username_new' => $username_new, ':username' => $username));
|
||||
}
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':username_new' => $username_new,
|
||||
':username' => $username,
|
||||
':active' => $active
|
||||
));
|
||||
if (isset($_data['disable_tfa'])) {
|
||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
}
|
||||
else {
|
||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
|
||||
$stmt->execute(array(':username_new' => $username_new, ':username' => $username));
|
||||
}
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'success',
|
||||
'msg' => sprintf($lang['success']['domain_admin_modified'], htmlspecialchars(implode(', ', $usernames)))
|
||||
);
|
||||
}
|
||||
// Domain administrator
|
||||
// Can only edit itself
|
||||
elseif ($_SESSION['mailcow_cc_role'] == "domainadmin") {
|
||||
$username = $_SESSION['mailcow_cc_username'];
|
||||
$password_old = $_data['user_old_pass'];
|
||||
$password_new = $_data['user_new_pass'];
|
||||
$password_new2 = $_data['user_new_pass2'];
|
||||
|
||||
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
|
||||
WHERE `username` = :user");
|
||||
$stmt->execute(array(':user' => $username));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!verify_ssha256($row['password'], $password_old)) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['access_denied'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($password_new2) && !empty($password_new)) {
|
||||
if ($password_new2 != $password_new) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['password_mismatch'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password_new)) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['password_complexity'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$password_hashed = hash_password($password_new);
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':password_hashed' => $password_hashed,
|
||||
':username' => $username
|
||||
));
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'success',
|
||||
'msg' => sprintf($lang['success']['domain_admin_modified'], htmlspecialchars($username))
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'delete':
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['access_denied'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$usernames = (array)$_data['username'];
|
||||
foreach ($usernames as $username) {
|
||||
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['username_invalid'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
));
|
||||
$stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :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']['domain_admin_removed'], htmlspecialchars(implode(', ', $usernames)))
|
||||
);
|
||||
break;
|
||||
case 'get':
|
||||
$domainadmins = array();
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['access_denied'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->query("SELECT DISTINCT
|
||||
`username`
|
||||
FROM `domain_admins`
|
||||
WHERE `username` IN (
|
||||
SELECT `username` FROM `admin`
|
||||
WHERE `superadmin`!='1'
|
||||
)");
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($row = array_shift($rows)) {
|
||||
$domainadmins[] = $row['username'];
|
||||
}
|
||||
}
|
||||
catch(PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
}
|
||||
return $domainadmins;
|
||||
break;
|
||||
case 'details':
|
||||
$domainadmindata = array();
|
||||
|
||||
if ($_SESSION['mailcow_cc_role'] == "domainadmin" && $_data != $_SESSION['mailcow_cc_username']) {
|
||||
return false;
|
||||
}
|
||||
elseif ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $_data))) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT
|
||||
`tfa`.`active` AS `tfa_active_int`,
|
||||
CASE `tfa`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `tfa_active`,
|
||||
`domain_admins`.`username`,
|
||||
`domain_admins`.`created`,
|
||||
`domain_admins`.`active` AS `active_int`,
|
||||
CASE `domain_admins`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
|
||||
FROM `domain_admins`
|
||||
LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`domain_admins`.`username`
|
||||
WHERE `domain_admins`.`username`= :domain_admin");
|
||||
$stmt->execute(array(
|
||||
':domain_admin' => $_data
|
||||
));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (empty($row)) {
|
||||
return false;
|
||||
}
|
||||
$domainadmindata['username'] = $row['username'];
|
||||
$domainadmindata['tfa_active'] = $row['tfa_active'];
|
||||
$domainadmindata['active'] = $row['active'];
|
||||
$domainadmindata['tfa_active_int'] = $row['tfa_active_int'];
|
||||
$domainadmindata['active_int'] = $row['active_int'];
|
||||
$domainadmindata['modified'] = $row['created'];
|
||||
// GET SELECTED
|
||||
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
|
||||
WHERE `domain` IN (
|
||||
SELECT `domain` FROM `domain_admins`
|
||||
WHERE `username`= :domain_admin)");
|
||||
$stmt->execute(array(':domain_admin' => $_data));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while($row = array_shift($rows)) {
|
||||
$domainadmindata['selected_domains'][] = $row['domain'];
|
||||
}
|
||||
// GET UNSELECTED
|
||||
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
|
||||
WHERE `domain` NOT IN (
|
||||
SELECT `domain` FROM `domain_admins`
|
||||
WHERE `username`= :domain_admin)");
|
||||
$stmt->execute(array(':domain_admin' => $_data));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while($row = array_shift($rows)) {
|
||||
$domainadmindata['unselected_domains'][] = $row['domain'];
|
||||
}
|
||||
if (!isset($domainadmindata['unselected_domains'])) {
|
||||
$domainadmindata['unselected_domains'] = "";
|
||||
}
|
||||
}
|
||||
catch(PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
}
|
||||
return $domainadmindata;
|
||||
break;
|
||||
}
|
||||
}
|
@ -73,7 +73,6 @@ function generate_tlsa_digest($hostname, $port, $starttls = null) {
|
||||
unset($lines[0]);
|
||||
return base64_decode(implode('', $lines));
|
||||
}
|
||||
|
||||
if (empty($starttls)) {
|
||||
$context = stream_context_create(array("ssl" => array("capture_peer_cert" => true, 'verify_peer' => false, 'allow_self_signed' => true)));
|
||||
$stream = stream_socket_client('tls://' . $hostname . ':' . $port, $error_nr, $error_msg, 5, STREAM_CLIENT_CONNECT, $context);
|
||||
@ -113,7 +112,6 @@ function generate_tlsa_digest($hostname, $port, $starttls = null) {
|
||||
stream_socket_enable_crypto($stream, true, STREAM_CRYPTO_METHOD_ANY_CLIENT);
|
||||
stream_set_blocking($stream, false);
|
||||
}
|
||||
|
||||
$params = stream_context_get_params($stream);
|
||||
if (!empty($params['options']['ssl']['peer_certificate'])) {
|
||||
$key_resource = openssl_pkey_get_public($params['options']['ssl']['peer_certificate']);
|
||||
@ -142,30 +140,6 @@ function verify_ssha256($hash, $password) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function doveadm_authenticate($hash, $algorithm, $password) {
|
||||
$descr = array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
|
||||
$pipes = array();
|
||||
$process = proc_open("/usr/bin/doveadm pw -s ".$algorithm." -t '".$hash."'", $descr, $pipes);
|
||||
if (is_resource($process)) {
|
||||
fputs($pipes[0], $password);
|
||||
fclose($pipes[0]);
|
||||
while ($f = fgets($pipes[1])) {
|
||||
if (preg_match('/(verified)/', $f)) {
|
||||
proc_close($process);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
fclose($pipes[1]);
|
||||
while ($f = fgets($pipes[2])) {
|
||||
proc_close($process);
|
||||
return false;
|
||||
}
|
||||
fclose($pipes[2]);
|
||||
proc_close($process);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function check_login($user, $pass) {
|
||||
global $pdo;
|
||||
global $redis;
|
||||
@ -272,7 +246,6 @@ function edit_admin_account($postarray) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($password) && !empty($password2)) {
|
||||
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
|
||||
$_SESSION['return'] = array(
|
||||
@ -348,28 +321,20 @@ function edit_admin_account($postarray) {
|
||||
function edit_user_account($postarray) {
|
||||
global $lang;
|
||||
global $pdo;
|
||||
if (isset($postarray['username']) && filter_var($postarray['username'], FILTER_VALIDATE_EMAIL)) {
|
||||
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $postarray['username'])) {
|
||||
$username = $_SESSION['mailcow_cc_username'];
|
||||
$role = $_SESSION['mailcow_cc_role'];
|
||||
$password_old = $postarray['user_old_pass'];
|
||||
if (filter_var($username, FILTER_VALIDATE_EMAIL === false) || $role != 'user') {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['access_denied'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
$username = $postarray['username'];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$username = $_SESSION['mailcow_cc_username'];
|
||||
}
|
||||
$password_old = $postarray['user_old_pass'];
|
||||
|
||||
if (isset($postarray['user_new_pass']) && isset($postarray['user_new_pass2'])) {
|
||||
$password_new = $postarray['user_new_pass'];
|
||||
$password_new2 = $postarray['user_new_pass2'];
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
|
||||
WHERE `kind` NOT REGEXP 'location|thing|group'
|
||||
AND `username` = :user");
|
||||
@ -382,7 +347,6 @@ function edit_user_account($postarray) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($password_new) && isset($password_new2)) {
|
||||
if (!empty($password_new2) && !empty($password_new)) {
|
||||
if ($password_new2 != $password_new) {
|
||||
@ -486,293 +450,12 @@ function is_valid_domain_name($domain_name) {
|
||||
&& preg_match("/^.{1,253}$/", $domain_name)
|
||||
&& preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name));
|
||||
}
|
||||
function add_domain_admin($postarray) {
|
||||
global $lang;
|
||||
global $pdo;
|
||||
$username = strtolower(trim($postarray['username']));
|
||||
$password = $postarray['password'];
|
||||
$password2 = $postarray['password2'];
|
||||
$domains = (array)$postarray['domains'];
|
||||
$active = intval($postarray['active']);
|
||||
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['access_denied'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (empty($domains)) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['domain_invalid'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username)) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['username_invalid'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox`
|
||||
WHERE `username` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
$num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
|
||||
$stmt = $pdo->prepare("SELECT `username` FROM `admin`
|
||||
WHERE `username` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
$num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
|
||||
$stmt = $pdo->prepare("SELECT `username` FROM `domain_admins`
|
||||
WHERE `username` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
$num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
}
|
||||
catch(PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
foreach ($num_results as $num_results_each) {
|
||||
if ($num_results_each != 0) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['object_exists'], htmlspecialchars($username))
|
||||
);
|
||||
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);
|
||||
foreach ($domains as $domain) {
|
||||
if (!is_valid_domain_name($domain)) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['domain_invalid'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
|
||||
VALUES (:username, :domain, :created, :active)");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
':domain' => $domain,
|
||||
':created' => date('Y-m-d H:i:s'),
|
||||
':active' => $active
|
||||
));
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
delete_domain_admin(array('username' => $username));
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`)
|
||||
VALUES (:username, :password_hashed, '0', :active)");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
':password_hashed' => $password_hashed,
|
||||
':active' => $active
|
||||
));
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['password_empty'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'success',
|
||||
'msg' => sprintf($lang['success']['domain_admin_added'], htmlspecialchars($username))
|
||||
);
|
||||
}
|
||||
function delete_domain_admin($postarray) {
|
||||
global $pdo;
|
||||
global $lang;
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['access_denied'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$usernames = (array)$postarray['username'];
|
||||
foreach ($usernames as $username) {
|
||||
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['username_invalid'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
));
|
||||
$stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :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']['domain_admin_removed'], htmlspecialchars(implode(', ', $usernames)))
|
||||
);
|
||||
}
|
||||
function get_domain_admins() {
|
||||
global $pdo;
|
||||
global $lang;
|
||||
$domainadmins = array();
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['access_denied'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->query("SELECT DISTINCT
|
||||
`username`
|
||||
FROM `domain_admins`
|
||||
WHERE `username` IN (
|
||||
SELECT `username` FROM `admin`
|
||||
WHERE `superadmin`!='1'
|
||||
)");
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($row = array_shift($rows)) {
|
||||
$domainadmins[] = $row['username'];
|
||||
}
|
||||
}
|
||||
catch(PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
}
|
||||
return $domainadmins;
|
||||
}
|
||||
function get_domain_admin_details($domain_admin) {
|
||||
global $pdo;
|
||||
|
||||
global $lang;
|
||||
$domainadmindata = array();
|
||||
if (isset($domain_admin) && $_SESSION['mailcow_cc_role'] != "admin") {
|
||||
return false;
|
||||
}
|
||||
if (!isset($domain_admin) && $_SESSION['mailcow_cc_role'] != "domainadmin") {
|
||||
return false;
|
||||
}
|
||||
(!isset($domain_admin)) ? $domain_admin = $_SESSION['mailcow_cc_username'] : null;
|
||||
|
||||
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $domain_admin))) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT
|
||||
`tfa`.`active` AS `tfa_active_int`,
|
||||
CASE `tfa`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `tfa_active`,
|
||||
`domain_admins`.`username`,
|
||||
`domain_admins`.`created`,
|
||||
`domain_admins`.`active` AS `active_int`,
|
||||
CASE `domain_admins`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
|
||||
FROM `domain_admins`
|
||||
LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`domain_admins`.`username`
|
||||
WHERE `domain_admins`.`username`= :domain_admin");
|
||||
$stmt->execute(array(
|
||||
':domain_admin' => $domain_admin
|
||||
));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (empty($row)) {
|
||||
return false;
|
||||
}
|
||||
$domainadmindata['username'] = $row['username'];
|
||||
$domainadmindata['tfa_active'] = $row['tfa_active'];
|
||||
$domainadmindata['active'] = $row['active'];
|
||||
$domainadmindata['tfa_active_int'] = $row['tfa_active_int'];
|
||||
$domainadmindata['active_int'] = $row['active_int'];
|
||||
$domainadmindata['modified'] = $row['created'];
|
||||
// GET SELECTED
|
||||
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
|
||||
WHERE `domain` IN (
|
||||
SELECT `domain` FROM `domain_admins`
|
||||
WHERE `username`= :domain_admin)");
|
||||
$stmt->execute(array(':domain_admin' => $domain_admin));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while($row = array_shift($rows)) {
|
||||
$domainadmindata['selected_domains'][] = $row['domain'];
|
||||
}
|
||||
// GET UNSELECTED
|
||||
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
|
||||
WHERE `domain` NOT IN (
|
||||
SELECT `domain` FROM `domain_admins`
|
||||
WHERE `username`= :domain_admin)");
|
||||
$stmt->execute(array(':domain_admin' => $domain_admin));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while($row = array_shift($rows)) {
|
||||
$domainadmindata['unselected_domains'][] = $row['domain'];
|
||||
}
|
||||
if (!isset($domainadmindata['unselected_domains'])) {
|
||||
$domainadmindata['unselected_domains'] = "";
|
||||
}
|
||||
}
|
||||
catch(PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
}
|
||||
return $domainadmindata;
|
||||
}
|
||||
function set_tfa($postarray) {
|
||||
global $lang;
|
||||
global $pdo;
|
||||
global $yubi;
|
||||
global $u2f;
|
||||
global $tfa;
|
||||
|
||||
if ($_SESSION['mailcow_cc_role'] != "domainadmin" &&
|
||||
$_SESSION['mailcow_cc_role'] != "admin") {
|
||||
$_SESSION['return'] = array(
|
||||
@ -847,7 +530,6 @@ function set_tfa($postarray) {
|
||||
'msg' => sprintf($lang['success']['object_modified'], htmlspecialchars($username))
|
||||
);
|
||||
break;
|
||||
|
||||
case "u2f":
|
||||
$key_id = (!isset($postarray["key_id"])) ? 'unidentified' : $postarray["key_id"];
|
||||
try {
|
||||
@ -871,7 +553,6 @@ function set_tfa($postarray) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case "totp":
|
||||
$key_id = (!isset($postarray["key_id"])) ? 'unidentified' : $postarray["key_id"];
|
||||
if ($tfa->verifyCode($_POST['totp_secret'], $_POST['totp_confirm_token']) === true) {
|
||||
@ -900,7 +581,6 @@ function set_tfa($postarray) {
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case "none":
|
||||
try {
|
||||
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
|
||||
@ -977,7 +657,6 @@ function get_tfa($username = null) {
|
||||
elseif (empty($username)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT * FROM `tfa`
|
||||
WHERE `username` = :username AND `active` = '1'");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
@ -1041,7 +720,6 @@ function verify_tfa_login($username, $token) {
|
||||
global $yubi;
|
||||
global $u2f;
|
||||
global $tfa;
|
||||
|
||||
$stmt = $pdo->prepare("SELECT `authmech` FROM `tfa`
|
||||
WHERE `username` = :username AND `active` = '1'");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
@ -1126,237 +804,6 @@ function verify_tfa_login($username, $token) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function edit_domain_admin($postarray) {
|
||||
global $lang;
|
||||
global $pdo;
|
||||
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['access_denied'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// Administrator
|
||||
if ($_SESSION['mailcow_cc_role'] == "admin") {
|
||||
if (!is_array($postarray['username'])) {
|
||||
$usernames = array();
|
||||
$usernames[] = $postarray['username'];
|
||||
}
|
||||
else {
|
||||
$usernames = $postarray['username'];
|
||||
}
|
||||
foreach ($usernames as $username) {
|
||||
$is_now = get_domain_admin_details($username);
|
||||
$domains = (isset($postarray['domains'])) ? (array)$postarray['domains'] : null;
|
||||
if (!empty($is_now)) {
|
||||
$active = (isset($postarray['active'])) ? $postarray['active'] : $is_now['active_int'];
|
||||
$domains = (!empty($domains)) ? $domains : $is_now['selected_domains'];
|
||||
$username_new = (!empty($postarray['username_new'])) ? $postarray['username_new'] : $is_now['username'];
|
||||
}
|
||||
else {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['access_denied'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$password = $postarray['password'];
|
||||
$password2 = $postarray['password2'];
|
||||
|
||||
if (!empty($domains)) {
|
||||
foreach ($domains as $domain) {
|
||||
if (!is_valid_domain_name($domain)) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['domain_invalid'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username_new))) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['username_invalid'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if ($username_new != $username) {
|
||||
if (!empty(get_domain_admin_details($username_new)['username'])) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['username_invalid'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
));
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($domains)) {
|
||||
foreach ($domains as $domain) {
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
|
||||
VALUES (:username_new, :domain, :created, :active)");
|
||||
$stmt->execute(array(
|
||||
':username_new' => $username_new,
|
||||
':domain' => $domain,
|
||||
':created' => date('Y-m-d H:i:s'),
|
||||
':active' => $active
|
||||
));
|
||||
}
|
||||
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 `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':password_hashed' => $password_hashed,
|
||||
':username_new' => $username_new,
|
||||
':username' => $username,
|
||||
':active' => $active
|
||||
));
|
||||
if (isset($postarray['disable_tfa'])) {
|
||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
}
|
||||
else {
|
||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
|
||||
$stmt->execute(array(':username_new' => $username_new, ':username' => $username));
|
||||
}
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':username_new' => $username_new,
|
||||
':username' => $username,
|
||||
':active' => $active
|
||||
));
|
||||
if (isset($postarray['disable_tfa'])) {
|
||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
}
|
||||
else {
|
||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
|
||||
$stmt->execute(array(':username_new' => $username_new, ':username' => $username));
|
||||
}
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'success',
|
||||
'msg' => sprintf($lang['success']['domain_admin_modified'], htmlspecialchars(implode(', ', $usernames)))
|
||||
);
|
||||
}
|
||||
// Domain administrator
|
||||
// Can only edit itself
|
||||
elseif ($_SESSION['mailcow_cc_role'] == "domainadmin") {
|
||||
$username = $_SESSION['mailcow_cc_username'];
|
||||
$password_old = $postarray['user_old_pass'];
|
||||
$password_new = $postarray['user_new_pass'];
|
||||
$password_new2 = $postarray['user_new_pass2'];
|
||||
|
||||
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
|
||||
WHERE `username` = :user");
|
||||
$stmt->execute(array(':user' => $username));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!verify_ssha256($row['password'], $password_old)) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['access_denied'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($password_new2) && !empty($password_new)) {
|
||||
if ($password_new2 != $password_new) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['password_mismatch'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password_new)) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['password_complexity'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$password_hashed = hash_password($password_new);
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':password_hashed' => $password_hashed,
|
||||
':username' => $username
|
||||
));
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'success',
|
||||
'msg' => sprintf($lang['success']['domain_admin_modified'], htmlspecialchars($username))
|
||||
);
|
||||
}
|
||||
}
|
||||
function get_admin_details() {
|
||||
// No parameter to be given, only one admin should exist
|
||||
global $pdo;
|
||||
|
@ -135,9 +135,9 @@ function mailbox($_action, $_type, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT `user2`, `user1` FROM `imapsync`
|
||||
WHERE `user2` = :user2 AND `user1` = :user1");
|
||||
$stmt->execute(array(':user1' => $user1, ':user2' => $username));
|
||||
$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) {
|
||||
@ -260,8 +260,8 @@ function mailbox($_action, $_type, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `maxquota`, `quota`, `transport`, `backupmx`, `active`, `relay_all_recipients`)
|
||||
VALUES (:domain, :description, :aliases, :mailboxes, :maxquota, :quota, 'virtual', :backupmx, :active, :relay_all_recipients)");
|
||||
$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,
|
||||
@ -879,7 +879,7 @@ function mailbox($_action, $_type, $_data = null) {
|
||||
$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'])) ? $_data['active'] : $is_now['active_int'];
|
||||
$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 {
|
||||
@ -903,7 +903,7 @@ function mailbox($_action, $_type, $_data = null) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (empty(mailbox('get', 'domain_details', $target_domain))) {
|
||||
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'])
|
||||
@ -950,12 +950,10 @@ function mailbox($_action, $_type, $_data = null) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$tls_enforce_out = intval($_data['tls_enforce_out']);
|
||||
$tls_enforce_in = intval($_data['tls_enforce_in']);
|
||||
$is_now = mailbox('get', 'tls_policy', $username);
|
||||
if (!empty($is_now)) {
|
||||
$tls_enforce_in = (isset($_data['tls_enforce_in'])) ? $_data['tls_enforce_in'] : $is_now['tls_enforce_in'];
|
||||
$tls_enforce_out = (isset($_data['tls_enforce_out'])) ? $_data['tls_enforce_out'] : $is_now['tls_enforce_out'];
|
||||
$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(
|
||||
@ -1136,6 +1134,63 @@ function mailbox($_action, $_type, $_data = null) {
|
||||
'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();
|
||||
@ -1149,9 +1204,9 @@ function mailbox($_action, $_type, $_data = null) {
|
||||
if (!empty($is_now)) {
|
||||
$username = $is_now['user2'];
|
||||
$user1 = (!empty($_data['user1'])) ? $_data['user1'] : $is_now['user1'];
|
||||
$active = (isset($_data['active'])) ? $_data['active'] : $is_now['active_int'];
|
||||
$delete2duplicates = (isset($_data['delete2duplicates'])) ? $_data['delete2duplicates'] : $is_now['delete2duplicates'];
|
||||
$delete1 = (isset($_data['delete1'])) ? $_data['delete1'] : $is_now['delete1'];
|
||||
$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'];
|
||||
@ -1253,7 +1308,7 @@ function mailbox($_action, $_type, $_data = null) {
|
||||
foreach ($addresses as $address) {
|
||||
$is_now = mailbox('get', 'alias_details', $address);
|
||||
if (!empty($is_now)) {
|
||||
$active = (isset($_data['active'])) ? $_data['active'] : $is_now['active_int'];
|
||||
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
|
||||
$goto = (!empty($_data['goto'])) ? $_data['goto'] : $is_now['goto'];
|
||||
}
|
||||
else {
|
||||
@ -1383,9 +1438,10 @@ function mailbox($_action, $_type, $_data = null) {
|
||||
elseif ($_SESSION['mailcow_cc_role'] == "admin") {
|
||||
$is_now = mailbox('get', 'domain_details', $domain);
|
||||
if (!empty($is_now)) {
|
||||
$active = (isset($_data['active'])) ? $_data['active'] : $is_now['active_int'];
|
||||
$backupmx = (isset($_data['backupmx'])) ? $_data['backupmx'] : $is_now['backupmx_int'];
|
||||
$relay_all_recipients = (isset($_data['relay_all_recipients'])) ? $_data['relay_all_recipients'] : $is_now['relay_all_recipients_int'];
|
||||
$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);
|
||||
@ -1476,6 +1532,7 @@ function mailbox($_action, $_type, $_data = null) {
|
||||
`active` = :active,
|
||||
`quota` = :quota,
|
||||
`maxquota` = :maxquota,
|
||||
`relayhost` = :relayhost,
|
||||
`mailboxes` = :mailboxes,
|
||||
`aliases` = :aliases,
|
||||
`description` = :description
|
||||
@ -1486,6 +1543,7 @@ function mailbox($_action, $_type, $_data = null) {
|
||||
':active' => $active,
|
||||
':quota' => $quota,
|
||||
':maxquota' => $maxquota,
|
||||
':relayhost' => $relayhost,
|
||||
':mailboxes' => $mailboxes,
|
||||
':aliases' => $aliases,
|
||||
':description' => $description,
|
||||
@ -1524,7 +1582,7 @@ function mailbox($_action, $_type, $_data = null) {
|
||||
}
|
||||
$is_now = mailbox('get', 'mailbox_details', $username);
|
||||
if (!empty($is_now)) {
|
||||
$active = (isset($_data['active'])) ? $_data['active'] : $is_now['active_int'];
|
||||
$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);
|
||||
@ -1588,19 +1646,15 @@ function mailbox($_action, $_type, $_data = null) {
|
||||
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() : $_data['sender_acl'];
|
||||
$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
|
||||
foreach ($sender_acl_domain_admin as $sender_acl) {
|
||||
if (!filter_var($sender_acl, FILTER_VALIDATE_EMAIL) && !is_valid_domain_name(ltrim($sender_acl, '@'))) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['sender_acl_invalid'])
|
||||
);
|
||||
return false;
|
||||
// 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($sender_acl, '@'))) {
|
||||
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], ltrim($sender_acl, '@'))) {
|
||||
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'])
|
||||
@ -1608,8 +1662,8 @@ function mailbox($_action, $_type, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (filter_var($sender_acl, FILTER_VALIDATE_EMAIL)) {
|
||||
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $sender_acl)) {
|
||||
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'])
|
||||
@ -1761,8 +1815,8 @@ function mailbox($_action, $_type, $_data = null) {
|
||||
foreach ($names as $name) {
|
||||
$is_now = mailbox('get', 'resource_details', $name);
|
||||
if (!empty($is_now)) {
|
||||
$active = (isset($_data['active'])) ? $_data['active'] : $is_now['active_int'];
|
||||
$multiple_bookings = (isset($_data['multiple_bookings'])) ? $_data['multiple_bookings'] : $is_now['multiple_bookings_int'];
|
||||
$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'];
|
||||
}
|
||||
@ -2267,6 +2321,31 @@ function mailbox($_action, $_type, $_data = null) {
|
||||
}
|
||||
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)) {
|
||||
$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 {
|
||||
@ -2404,6 +2483,7 @@ function mailbox($_action, $_type, $_data = null) {
|
||||
`mailboxes`,
|
||||
`maxquota`,
|
||||
`quota`,
|
||||
`relayhost`,
|
||||
`relay_all_recipients` as `relay_all_recipients_int`,
|
||||
`backupmx` as `backupmx_int`,
|
||||
`active` as `active_int`,
|
||||
@ -2438,6 +2518,7 @@ function mailbox($_action, $_type, $_data = null) {
|
||||
$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'];
|
||||
|
179
data/web/inc/functions.relayhost.inc.php
Normal file
179
data/web/inc/functions.relayhost.inc.php
Normal file
@ -0,0 +1,179 @@
|
||||
<?php
|
||||
function relayhost($_action, $_data = null) {
|
||||
global $pdo;
|
||||
global $lang;
|
||||
switch ($_action) {
|
||||
case 'add':
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['access_denied'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$hostname = trim($_data['hostname']);
|
||||
$username = str_replace(':', '\:', trim($_data['username']));
|
||||
$password = str_replace(':', '\:', trim($_data['password']));
|
||||
if (empty($hostname)) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'Invalid host specified: '. htmlspecialchars($host)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO `relayhosts` (`hostname`, `username` ,`password`, `active`)
|
||||
VALUES (:hostname, :username, :password, :active)");
|
||||
$stmt->execute(array(
|
||||
':hostname' => $hostname,
|
||||
':username' => $username,
|
||||
':password' => $password,
|
||||
':active' => '1'
|
||||
));
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'success',
|
||||
'msg' => sprintf($lang['success']['relayhost_added'], htmlspecialchars(implode(', ', $hosts)))
|
||||
);
|
||||
break;
|
||||
case 'edit':
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['access_denied'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$ids = (array)$_data['id'];
|
||||
foreach ($ids as $id) {
|
||||
$is_now = relayhost('details', $id);
|
||||
if (!empty($is_now)) {
|
||||
$hostname = (!empty($_data['hostname'])) ? trim($_data['hostname']) : $is_now['hostname'];
|
||||
$username = (!empty($_data['username'])) ? trim($_data['username']) : $is_now['username'];
|
||||
$password = (!empty($_data['password'])) ? trim($_data['password']) : $is_now['password'];
|
||||
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
|
||||
}
|
||||
else {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'Relayhost invalid'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE `relayhosts` SET
|
||||
`hostname` = :hostname,
|
||||
`username` = :username,
|
||||
`password` = :password,
|
||||
`active` = :active
|
||||
WHERE `id` = :id");
|
||||
$stmt->execute(array(
|
||||
':id' => $id,
|
||||
':hostname' => $hostname,
|
||||
':username' => $username,
|
||||
':password' => $password,
|
||||
':active' => $active
|
||||
));
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'success',
|
||||
'msg' => sprintf($lang['success']['object_modified'], htmlspecialchars(implode(', ', $hostnames)))
|
||||
);
|
||||
break;
|
||||
case 'delete':
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['access_denied'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$ids = (array)$_data['id'];
|
||||
foreach ($ids as $id) {
|
||||
try {
|
||||
$stmt = $pdo->prepare("DELETE FROM `relayhosts` WHERE `id`= :id");
|
||||
$stmt->execute(array(':id' => $id));
|
||||
$stmt = $pdo->prepare("UPDATE `domain` SET `relayhost` = '0' WHERE `relayhost`= :id");
|
||||
$stmt->execute(array(':id' => $id));
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'success',
|
||||
'msg' => sprintf($lang['success']['relayhost_removed'], htmlspecialchars(implode(', ', $hostnames)))
|
||||
);
|
||||
break;
|
||||
case 'get':
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
return false;
|
||||
}
|
||||
$relayhosts = array();
|
||||
try {
|
||||
$stmt = $pdo->query("SELECT `id`, `hostname`, `username` FROM `relayhosts`");
|
||||
$relayhosts = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
catch(PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
}
|
||||
return $relayhosts;
|
||||
break;
|
||||
case 'details':
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) {
|
||||
return false;
|
||||
}
|
||||
$relayhostdata = array();
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT `id`,
|
||||
`hostname`,
|
||||
`username`,
|
||||
`password`,
|
||||
`active` AS `active_int`,
|
||||
CONCAT(LEFT(`password`, 3), '...') AS `password_short`,
|
||||
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
|
||||
FROM `relayhosts`
|
||||
WHERE `id` = :id");
|
||||
$stmt->execute(array(':id' => $_data));
|
||||
$relayhostdata = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!empty($relayhostdata)) {
|
||||
$stmt = $pdo->prepare("SELECT GROUP_CONCAT(`domain` SEPARATOR ', ') AS `used_by_domains` FROM `domain` WHERE `relayhost` = :id");
|
||||
$stmt->execute(array(':id' => $_data));
|
||||
$used_by_domains = $stmt->fetch(PDO::FETCH_ASSOC)['used_by_domains'];
|
||||
$used_by_domains = (empty($used_by_domains)) ? '' : $used_by_domains;
|
||||
$relayhostdata['used_by_domains'] = $used_by_domains;
|
||||
}
|
||||
}
|
||||
catch(PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
}
|
||||
return $relayhostdata;
|
||||
break;
|
||||
}
|
||||
}
|
@ -29,7 +29,7 @@
|
||||
<link rel="shortcut icon" href="/favicon.png" type="image/png">
|
||||
<link rel="icon" href="/favicon.png" type="image/png">
|
||||
</head>
|
||||
<body style="padding-top: 70px;">
|
||||
<body style="padding-top: 70px;" id="top">
|
||||
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
|
@ -3,7 +3,7 @@ function init_db_schema() {
|
||||
try {
|
||||
global $pdo;
|
||||
|
||||
$db_version = "18052017_1017";
|
||||
$db_version = "20072107_1029";
|
||||
|
||||
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
@ -60,6 +60,24 @@ function init_db_schema() {
|
||||
),
|
||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||
),
|
||||
"relayhosts" => array(
|
||||
"cols" => array(
|
||||
"id" => "INT NOT NULL AUTO_INCREMENT",
|
||||
"hostname" => "VARCHAR(255) NOT NULL",
|
||||
"username" => "VARCHAR(255) NOT NULL",
|
||||
"password" => "VARCHAR(255) NOT NULL",
|
||||
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
|
||||
),
|
||||
"keys" => array(
|
||||
"primary" => array(
|
||||
"" => array("id")
|
||||
),
|
||||
"key" => array(
|
||||
"hostname" => array("hostname")
|
||||
)
|
||||
),
|
||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||
),
|
||||
"alias" => array(
|
||||
"cols" => array(
|
||||
"address" => "VARCHAR(255) NOT NULL",
|
||||
@ -95,7 +113,7 @@ function init_db_schema() {
|
||||
"mailboxes" => "INT(10) NOT NULL DEFAULT '0'",
|
||||
"maxquota" => "BIGINT(20) NOT NULL DEFAULT '0'",
|
||||
"quota" => "BIGINT(20) NOT NULL DEFAULT '102400'",
|
||||
"transport" => "VARCHAR(255) NOT NULL",
|
||||
"relayhost" => "VARCHAR(255) NOT NULL DEFAULT '0'",
|
||||
"backupmx" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||
"relay_all_recipients" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
||||
|
@ -61,9 +61,11 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.en.php';
|
||||
include $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.'.$_SESSION['mailcow_locale'].'.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.domain_admin.inc.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.policy.inc.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.dkim.inc.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fwdhost.inc.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.relayhost.inc.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fail2ban.inc.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/init_db.inc.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.inc.php';
|
||||
|
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
// Start session
|
||||
ini_set("session.cookie_httponly", 1);
|
||||
ini_set('session.gc_maxlifetime', $SESSION_LIFETIME);
|
||||
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
|
||||
strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == "https") {
|
||||
ini_set("session.cookie_secure", 1);
|
||||
|
@ -54,53 +54,12 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admi
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "user") {
|
||||
if (isset($_POST["edit_user_account"])) {
|
||||
edit_user_account($_POST);
|
||||
}
|
||||
if (isset($_POST["edit_syncjob"])) {
|
||||
mailbox('edit', 'syncjob', $_POST);
|
||||
}
|
||||
}
|
||||
if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "admin" || $_SESSION['mailcow_cc_role'] == "domainadmin")) {
|
||||
if (isset($_POST["edit_domain_admin"])) {
|
||||
edit_domain_admin($_POST);
|
||||
}
|
||||
if (isset($_POST["set_tfa"])) {
|
||||
set_tfa($_POST);
|
||||
}
|
||||
if (isset($_POST["unset_tfa_key"])) {
|
||||
unset_tfa_key($_POST);
|
||||
}
|
||||
if (isset($_POST["mailbox_edit_alias"])) {
|
||||
mailbox('edit', 'alias', $_POST);
|
||||
}
|
||||
if (isset($_POST["mailbox_edit_domain"])) {
|
||||
mailbox('edit', 'domain', $_POST);
|
||||
}
|
||||
if (isset($_POST["mailbox_edit_mailbox"])) {
|
||||
mailbox('edit', 'mailbox', $_POST);
|
||||
}
|
||||
if (isset($_POST["mailbox_edit_alias_domain"])) {
|
||||
mailbox('edit', 'alias_domain', $_POST);
|
||||
}
|
||||
if (isset($_POST["mailbox_edit_resource"])) {
|
||||
mailbox('edit', 'resource', $_POST);
|
||||
}
|
||||
if (isset($_POST["mailbox_delete_domain"])) {
|
||||
mailbox('delete', 'domain', $_POST);
|
||||
}
|
||||
if (isset($_POST["mailbox_delete_alias"])) {
|
||||
mailbox('delete', 'delete_alias', $_POST);
|
||||
}
|
||||
if (isset($_POST["mailbox_delete_alias_domain"])) {
|
||||
mailbox('delete', 'alias_domain', $_POST);
|
||||
}
|
||||
if (isset($_POST["mailbox_delete_mailbox"])) {
|
||||
mailbox('delete', 'mailbox', $_POST);
|
||||
}
|
||||
if (isset($_POST["mailbox_delete_resource"])) {
|
||||
mailbox('delete', 'resource', $_POST);
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
@ -328,6 +328,44 @@ jQuery(function($){
|
||||
}
|
||||
});
|
||||
}
|
||||
function draw_relayhosts() {
|
||||
ft_forwardinghoststable = FooTable.init('#relayhoststable', {
|
||||
"columns": [
|
||||
{"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"},
|
||||
{"name":"id","type":"text","title":"ID","style":{"width":"50px"}},
|
||||
{"name":"hostname","type":"text","title":lang.host,"style":{"width":"250px"}},
|
||||
{"name":"username","title":lang.username,"breakpoints":"xs sm"},
|
||||
{"name":"used_by_domains","title":lang.in_use_by, "type": "text","breakpoints":"xs sm"},
|
||||
{"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active},
|
||||
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
|
||||
],
|
||||
"rows": $.ajax({
|
||||
dataType: 'json',
|
||||
url: '/api/v1/get/relayhost/all',
|
||||
jsonp: false,
|
||||
error: function () {
|
||||
console.log('Cannot draw forwarding hosts table');
|
||||
},
|
||||
success: function (data) {
|
||||
$.each(data, function (i, item) {
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="#" id="delete_selected" data-id="single-rlshost" data-api-url="delete/relayhost" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
item.chkbox = '<input type="checkbox" data-id="rlyhosts" name="multi_select" value="' + item.id + '" />';
|
||||
});
|
||||
}
|
||||
}),
|
||||
"empty": lang.empty,
|
||||
"paging": {
|
||||
"enabled": true,
|
||||
"limit": 5,
|
||||
"size": log_pagination_size
|
||||
},
|
||||
"sorting": {
|
||||
"enabled": true
|
||||
}
|
||||
});
|
||||
}
|
||||
function draw_rspamd_history() {
|
||||
ft_postfix_logs = FooTable.init('#rspamd_history', {
|
||||
"columns": [{
|
||||
@ -504,5 +542,20 @@ jQuery(function($){
|
||||
draw_fail2ban_logs();
|
||||
draw_domain_admins();
|
||||
draw_fwd_hosts();
|
||||
draw_relayhosts();
|
||||
draw_rspamd_history();
|
||||
});
|
||||
|
||||
$(window).load(function(){
|
||||
width = $("#scrollbox").width();
|
||||
$(window).bind('scroll', function() {
|
||||
if ($(window).scrollTop() > 70) {
|
||||
$('#scrollbox').addClass('scrollboxFixed');
|
||||
$("#scrollbox").css("width", width);
|
||||
} else {
|
||||
width = $("#scrollbox").width();
|
||||
$('#scrollbox').removeClass('scrollboxFixed');
|
||||
$("#scrollbox").removeAttr("style");
|
||||
}
|
||||
});
|
||||
});
|
@ -64,8 +64,23 @@ $(document).ready(function() {
|
||||
// If clicked element #edit_selected is in a form with the same data-id as the button,
|
||||
// we merge all input fields by {"name":"value"} into api-attr
|
||||
if ($(this).closest("form").data('id') == id) {
|
||||
var req_empty = false;
|
||||
$(this).closest("form").find('select, textarea, input').each(function() {
|
||||
if ($(this).prop('required')) {
|
||||
if (!$(this).val()) {
|
||||
req_empty = true;
|
||||
$(this).addClass('inputMissingAttr');
|
||||
} else {
|
||||
$(this).removeClass('inputMissingAttr');
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!req_empty) {
|
||||
var attr_to_merge = $(this).closest("form").serializeObject();
|
||||
var api_attr = $.extend(api_attr, attr_to_merge)
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// If clicked element #edit_selected has data-item attribute, it is added to "items"
|
||||
if (typeof $(this).data('item') !== 'undefined') {
|
||||
@ -77,6 +92,7 @@ $(document).ready(function() {
|
||||
}
|
||||
if (typeof multi_data[id] == "undefined") return;
|
||||
api_items = multi_data[id];
|
||||
// alert(JSON.stringify(api_attr));
|
||||
if (Object.keys(api_items).length !== 0) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
|
@ -60,6 +60,39 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||
));
|
||||
}
|
||||
break;
|
||||
case "relayhost":
|
||||
if (isset($_POST['attr'])) {
|
||||
$attr = (array)json_decode($_POST['attr'], true);
|
||||
if (relayhost('add', $attr) === false) {
|
||||
if (isset($_SESSION['return'])) {
|
||||
echo json_encode($_SESSION['return']);
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'error',
|
||||
'msg' => 'Cannot add item'
|
||||
));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isset($_SESSION['return'])) {
|
||||
echo json_encode($_SESSION['return']);
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'success',
|
||||
'msg' => 'Task completed'
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'error',
|
||||
'msg' => 'Cannot find attributes in post data'
|
||||
));
|
||||
}
|
||||
break;
|
||||
case "mailbox":
|
||||
if (isset($_POST['attr'])) {
|
||||
$attr = (array)json_decode($_POST['attr'], true);
|
||||
@ -426,7 +459,7 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||
case "domain-admin":
|
||||
if (isset($_POST['attr'])) {
|
||||
$attr = (array)json_decode($_POST['attr'], true);
|
||||
if (add_domain_admin($attr) === false) {
|
||||
if (domain_admin('add', $attr) === false) {
|
||||
if (isset($_SESSION['return'])) {
|
||||
echo json_encode($_SESSION['return']);
|
||||
}
|
||||
@ -496,6 +529,42 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "relayhost":
|
||||
switch ($object) {
|
||||
case "all":
|
||||
$relayhosts = relayhost('get');
|
||||
if (!empty($relayhosts)) {
|
||||
foreach ($relayhosts as $relayhost) {
|
||||
if ($details = relayhost('details', $relayhost['id'])) {
|
||||
$data[] = $details;
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!isset($data) || empty($data)) {
|
||||
echo '{}';
|
||||
}
|
||||
else {
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
}
|
||||
}
|
||||
else {
|
||||
echo '{}';
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$data = relayhost('details', $object);
|
||||
if (!isset($data) || empty($data)) {
|
||||
echo '{}';
|
||||
}
|
||||
else {
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "logs":
|
||||
switch ($object) {
|
||||
case "dovecot":
|
||||
@ -826,10 +895,10 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||
case "domain-admin":
|
||||
switch ($object) {
|
||||
case "all":
|
||||
$domain_admins = get_domain_admins();
|
||||
$domain_admins = domain_admin('get');
|
||||
if (!empty($domain_admins)) {
|
||||
foreach ($domain_admins as $domain_admin) {
|
||||
if ($details = get_domain_admin_details($domain_admin)) {
|
||||
if ($details = domain_admin('details', $domain_admin)) {
|
||||
$data[] = $details;
|
||||
}
|
||||
else {
|
||||
@ -849,7 +918,7 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||
break;
|
||||
|
||||
default:
|
||||
$data = get_domain_admin_details($object);
|
||||
$data = domain_admin('details', $object);
|
||||
if (!isset($data) || empty($data)) {
|
||||
echo '{}';
|
||||
}
|
||||
@ -930,6 +999,47 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||
));
|
||||
}
|
||||
break;
|
||||
case "relayhost":
|
||||
if (isset($_POST['items'])) {
|
||||
$items = (array)json_decode($_POST['items'], true);
|
||||
if (is_array($items)) {
|
||||
if (relayhost('delete', array('id' => $items)) === false) {
|
||||
if (isset($_SESSION['return'])) {
|
||||
echo json_encode($_SESSION['return']);
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'error',
|
||||
'msg' => 'Deletion of items/s failed'
|
||||
));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isset($_SESSION['return'])) {
|
||||
echo json_encode($_SESSION['return']);
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'success',
|
||||
'msg' => 'Task completed'
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'error',
|
||||
'msg' => 'Cannot find id array in post data'
|
||||
));
|
||||
}
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'error',
|
||||
'msg' => 'Cannot find items in post data'
|
||||
));
|
||||
}
|
||||
break;
|
||||
case "syncjob":
|
||||
if (isset($_POST['items'])) {
|
||||
$items = (array)json_decode($_POST['items'], true);
|
||||
@ -1385,7 +1495,7 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||
if (isset($_POST['items'])) {
|
||||
$items = (array)json_decode($_POST['items'], true);
|
||||
if (is_array($items)) {
|
||||
if (delete_domain_admin(array('username' => $items)) === false) {
|
||||
if (domain_admin('delete', array('username' => $items)) === false) {
|
||||
if (isset($_SESSION['return'])) {
|
||||
echo json_encode($_SESSION['return']);
|
||||
}
|
||||
@ -1470,6 +1580,50 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||
));
|
||||
}
|
||||
break;
|
||||
case "relayhost":
|
||||
if (isset($_POST['items']) && isset($_POST['attr'])) {
|
||||
$items = (array)json_decode($_POST['items'], true);
|
||||
$attr = (array)json_decode($_POST['attr'], true);
|
||||
$postarray = array_merge(array('id' => $items), $attr);
|
||||
if (is_array($postarray['id'])) {
|
||||
if (relayhost('edit', $postarray) === false) {
|
||||
if (isset($_SESSION['return'])) {
|
||||
echo json_encode($_SESSION['return']);
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'error',
|
||||
'msg' => 'Edit failed'
|
||||
));
|
||||
}
|
||||
exit();
|
||||
}
|
||||
else {
|
||||
if (isset($_SESSION['return'])) {
|
||||
echo json_encode($_SESSION['return']);
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'success',
|
||||
'msg' => 'Task completed'
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'error',
|
||||
'msg' => 'Incomplete post data'
|
||||
));
|
||||
}
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'error',
|
||||
'msg' => 'Incomplete post data'
|
||||
));
|
||||
}
|
||||
break;
|
||||
case "delimiter_action":
|
||||
if (isset($_POST['items']) && isset($_POST['attr'])) {
|
||||
$items = (array)json_decode($_POST['items'], true);
|
||||
@ -1603,6 +1757,7 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||
}
|
||||
break;
|
||||
case "mailbox":
|
||||
// sender_acl:0 removes all entries
|
||||
if (isset($_POST['items']) && isset($_POST['attr'])) {
|
||||
$items = (array)json_decode($_POST['items'], true);
|
||||
$attr = (array)json_decode($_POST['attr'], true);
|
||||
@ -1778,6 +1933,50 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||
));
|
||||
}
|
||||
break;
|
||||
case "domain-ratelimit":
|
||||
if (isset($_POST['items']) && isset($_POST['attr'])) {
|
||||
$items = (array)json_decode($_POST['items'], true);
|
||||
$attr = (array)json_decode($_POST['attr'], true);
|
||||
$postarray = array_merge(array('domain' => $items), $attr);
|
||||
if (is_array($postarray['domain'])) {
|
||||
if (mailbox('edit', 'domain_ratelimit', $postarray) === false) {
|
||||
if (isset($_SESSION['return'])) {
|
||||
echo json_encode($_SESSION['return']);
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'error',
|
||||
'msg' => 'Edit failed'
|
||||
));
|
||||
}
|
||||
exit();
|
||||
}
|
||||
else {
|
||||
if (isset($_SESSION['return'])) {
|
||||
echo json_encode($_SESSION['return']);
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'success',
|
||||
'msg' => 'Task completed'
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'error',
|
||||
'msg' => 'Incomplete post data'
|
||||
));
|
||||
}
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'error',
|
||||
'msg' => 'Incomplete post data'
|
||||
));
|
||||
}
|
||||
break;
|
||||
case "alias-domain":
|
||||
if (isset($_POST['items']) && isset($_POST['attr'])) {
|
||||
$items = (array)json_decode($_POST['items'], true);
|
||||
@ -1822,7 +2021,7 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||
));
|
||||
}
|
||||
break;
|
||||
case "spam_score":
|
||||
case "spam-score":
|
||||
if (isset($_POST['items']) && isset($_POST['attr'])) {
|
||||
$items = (array)json_decode($_POST['items'], true);
|
||||
$attr = (array)json_decode($_POST['attr'], true);
|
||||
@ -1872,7 +2071,7 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||
$attr = (array)json_decode($_POST['attr'], true);
|
||||
$postarray = array_merge(array('username' => $items), $attr);
|
||||
if (is_array($postarray['username'])) {
|
||||
if (edit_domain_admin($postarray) === false) {
|
||||
if (domain_admin('edit', $postarray) === false) {
|
||||
if (isset($_SESSION['return'])) {
|
||||
echo json_encode($_SESSION['return']);
|
||||
}
|
||||
@ -1989,8 +2188,77 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||
));
|
||||
}
|
||||
break;
|
||||
case "admin":
|
||||
// No items as there is only one admin
|
||||
case "self":
|
||||
// No items, logged-in user, users and domain admins
|
||||
if ($_SESSION['mailcow_cc_role'] == "domainadmin") {
|
||||
if (isset($_POST['attr'])) {
|
||||
$attr = (array)json_decode($_POST['attr'], true);
|
||||
if (domain_admin('edit', $attr) === false) {
|
||||
if (isset($_SESSION['return'])) {
|
||||
echo json_encode($_SESSION['return']);
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'error',
|
||||
'msg' => 'Edit failed'
|
||||
));
|
||||
}
|
||||
exit();
|
||||
}
|
||||
else {
|
||||
if (isset($_SESSION['return'])) {
|
||||
echo json_encode($_SESSION['return']);
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'success',
|
||||
'msg' => 'Task completed'
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'error',
|
||||
'msg' => 'Incomplete post data'
|
||||
));
|
||||
}
|
||||
}
|
||||
elseif ($_SESSION['mailcow_cc_role'] == "user") {
|
||||
if (isset($_POST['attr'])) {
|
||||
$attr = (array)json_decode($_POST['attr'], true);
|
||||
if (edit_user_account($attr) === false) {
|
||||
if (isset($_SESSION['return'])) {
|
||||
echo json_encode($_SESSION['return']);
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'error',
|
||||
'msg' => 'Edit failed'
|
||||
));
|
||||
}
|
||||
exit();
|
||||
}
|
||||
else {
|
||||
if (isset($_SESSION['return'])) {
|
||||
echo json_encode($_SESSION['return']);
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'success',
|
||||
'msg' => 'Task completed'
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
echo json_encode(array(
|
||||
'type' => 'error',
|
||||
'msg' => 'Incomplete post data'
|
||||
));
|
||||
}
|
||||
}
|
||||
elseif ($_SESSION['mailcow_cc_role'] == "admin") {
|
||||
if (isset($_POST['attr'])) {
|
||||
$attr = (array)json_decode($_POST['attr'], true);
|
||||
if (edit_admin_account($attr) === false) {
|
||||
@ -2023,6 +2291,7 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||
'msg' => 'Incomplete post data'
|
||||
));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -486,16 +486,23 @@ $lang['admin']['time'] = 'Zeit';
|
||||
$lang['admin']['priority'] = 'Gewichtung';
|
||||
$lang['admin']['refresh'] = 'Neu laden';
|
||||
$lang['admin']['logs'] = 'Logs';
|
||||
$lang['admin']['to_top'] = 'Nach oben';
|
||||
$lang['admin']['in_use_by'] = 'Verwendet von';
|
||||
$lang['admin']['message'] = 'Nachricht';
|
||||
$lang['admin']['forwarding_hosts'] = 'Weiterleitungs-Hosts';
|
||||
$lang['admin']['forwarding_hosts_hint'] = 'Eingehende Nachrichten werden von den hier gelisteten Hosts bedingungslos akzeptiert. Diese Hosts werden dann nicht mit DNSBLs abgeglichen oder Greylisting unterworfen. Von ihnen empfangener Spam wird nie abgelehnt, optional kann er aber in den Spam-Ordner einsortiert werden. Die übliche Verwendung für diese Funktion ist, um Mailserver anzugeben, auf denen eine Weiterleitung zu Ihrem mailcow-Server eingerichtet wurde.';
|
||||
$lang['admin']['forwarding_hosts_add_hint'] = 'Sie können entweder IPv4/IPv6-Adressen, Netzwerke in CIDR-Notation, Hostnamen (die zu IP-Adressen aufgelöst werden), oder Domainnamen (die zu IP-Adressen aufgelöst werden, indem ihr SPF-Record abgefragt wird oder, in dessen Abwesenheit, ihre MX-Records) angeben.';
|
||||
$lang['admin']['relayhosts_hint'] = 'Erstellen Sie Relayhosts, um diese im Einstellungsdialog einer Domain auszuwählen.';
|
||||
$lang['admin']['add_relayhost_add_hint'] = 'Bitte beachten Sie, dass Relayhost Anmeldedaten im Klartext gespeichert werden.';
|
||||
$lang['admin']['host'] = 'Host';
|
||||
$lang['admin']['source'] = 'Quelle';
|
||||
$lang['admin']['add_forwarding_host'] = 'Weiterleitungs-Host hinzufügen';
|
||||
$lang['admin']['add_relayhost'] = 'Relayhost hinzufügen';
|
||||
$lang['delete']['remove_forwardinghost_warning'] = '<b>Warnung:</b> Sie entfernen den Weiterleitungs-Host <b>%s</b>!';
|
||||
$lang['success']['forwarding_host_removed'] = "Weiterleitungs-Host %s wurde entfernt";
|
||||
$lang['success']['forwarding_host_added'] = "Weiterleitungs-Host %s wurde hinzugefügt";
|
||||
$lang['success']['relayhost_removed'] = "Relayhost %s wurde entfernt";
|
||||
$lang['success']['relayhost_added'] = "Relayhost %s wurde hinzugefügt";
|
||||
$lang['diagnostics']['dns_records'] = 'DNS-Einträge';
|
||||
$lang['diagnostics']['dns_records_24hours'] = 'Bitte beachten Sie, dass es bis zu 24 Stunden dauern kann, bis Änderungen an Ihren DNS-Einträgen als aktueller Status auf dieser Seite dargestellt werden. Diese Seite ist nur als Hilfsmittel gedacht, um die korrekten Werte für DNS-Einträge zu anzuzeigen und zu überprüfen, ob die Daten im DNS hinterlegt sind.';
|
||||
$lang['diagnostics']['dns_records_name'] = 'Name';
|
||||
|
@ -499,16 +499,23 @@ $lang['admin']['time'] = 'Time';
|
||||
$lang['admin']['priority'] = 'Priority';
|
||||
$lang['admin']['message'] = 'Message';
|
||||
$lang['admin']['refresh'] = 'Refresh';
|
||||
$lang['admin']['to_top'] = 'Back to top';
|
||||
$lang['admin']['in_use_by'] = 'In use by';
|
||||
$lang['admin']['logs'] = 'Logs';
|
||||
$lang['admin']['forwarding_hosts'] = 'Forwarding Hosts';
|
||||
$lang['admin']['forwarding_hosts_hint'] = 'Incoming messages are unconditionally accepted from any hosts listed here. These hosts are then not checked against DNSBLs or subjected to greylisting. Spam received from them is never rejected, but optionally it can be filed into the Junk folder. The most common use for this is to specify mail servers on which you have set up a rule that forwards incoming emails to your mailcow server.';
|
||||
$lang['admin']['forwarding_hosts_add_hint'] = 'You can either specify IPv4/IPv6 addresses, networks in CIDR notation, host names (which will be resolved to IP addresses), or domain names (which will be resolved to IP addresses by querying SPF records or, in their absence, MX records).';
|
||||
$lang['admin']['relayhosts_hint'] = 'Define relayhosts here to be able to select them in a domains configuration dialog.';
|
||||
$lang['admin']['add_relayhost_add_hint'] = 'Please be aware that relayhost authentication data will be stored as plain text.';
|
||||
$lang['admin']['host'] = 'Host';
|
||||
$lang['admin']['source'] = 'Source';
|
||||
$lang['admin']['add_forwarding_host'] = 'Add Forwarding Host';
|
||||
$lang['admin']['add_relayhost'] = 'Add Relayhost';
|
||||
$lang['delete']['remove_forwardinghost_warning'] = '<b>Warning:</b> You are about to remove the forwarding host <b>%s</b>!';
|
||||
$lang['success']['forwarding_host_removed'] = "Forwarding host %s has been removed";
|
||||
$lang['success']['forwarding_host_added'] = "Forwarding host %s has been added";
|
||||
$lang['success']['relayhost_removed'] = "Relayhost %s has been removed";
|
||||
$lang['success']['relayhost_added'] = "Relayhost %s has been added";
|
||||
$lang['diagnostics']['dns_records'] = 'DNS Records';
|
||||
$lang['diagnostics']['dns_records_24hours'] = 'Please note that changes made to DNS may take up to 24 hours to correctly have their current state reflected on this page. It is intended as a way for you to easily see how to configure your DNS records and to check whether all your records are correctly stored in DNS.';
|
||||
$lang['diagnostics']['dns_records_name'] = 'Name';
|
||||
|
@ -357,7 +357,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "use
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<form class="form-horizontal" role="form" method="post" autocomplete="off">
|
||||
<form class="form-horizontal" data-id="pwchange" role="form" method="post" autocomplete="off">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-3" for="user_new_pass"><?=$lang['user']['new_password'];?></label>
|
||||
<div class="col-sm-5">
|
||||
@ -380,7 +380,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "use
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-3 col-sm-9">
|
||||
<button type="submit" name="edit_<?=($_SESSION['mailcow_cc_role'] == "domainadmin") ? "domain_admin" : "user_account";?>" class="btn btn-sm btn-success"><?=$lang['user']['change_password'];?></button>
|
||||
<button class="btn btn-default" id="edit_selected" data-id="pwchange" data-item="null" data-api-url='edit/self' data-api-attr='{}' href="#"><?=$lang['user']['change_password'];?></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -80,7 +80,7 @@ services:
|
||||
- clamd
|
||||
|
||||
rspamd-mailcow:
|
||||
image: mailcow/rspamd:1.3
|
||||
image: mailcow/rspamd:1.4
|
||||
build: ./data/Dockerfiles/rspamd
|
||||
command: >
|
||||
/bin/bash -c "
|
||||
@ -96,9 +96,6 @@ services:
|
||||
- dkim-vol-1:/data/dkim
|
||||
- rspamd-vol-1:/var/lib/rspamd
|
||||
restart: always
|
||||
logging:
|
||||
options:
|
||||
max-size: "5m"
|
||||
dns:
|
||||
- 172.22.1.254
|
||||
dns_search: mailcow-network
|
||||
@ -133,9 +130,6 @@ services:
|
||||
- SMTPS_PORT=${SMTPS_PORT:-465}
|
||||
- SMTP_PORT=${SMTP_PORT:-25}
|
||||
restart: always
|
||||
logging:
|
||||
options:
|
||||
max-size: "5m"
|
||||
dns:
|
||||
- 172.22.1.254
|
||||
dns_search: mailcow-network
|
||||
@ -145,7 +139,7 @@ services:
|
||||
- phpfpm
|
||||
|
||||
sogo-mailcow:
|
||||
image: mailcow/sogo:1.2
|
||||
image: mailcow/sogo:1.3
|
||||
build: ./data/Dockerfiles/sogo
|
||||
depends_on:
|
||||
unbound-mailcow:
|
||||
@ -159,9 +153,6 @@ services:
|
||||
volumes:
|
||||
- ./data/conf/sogo/:/etc/sogo/
|
||||
restart: always
|
||||
logging:
|
||||
options:
|
||||
max-size: "5m"
|
||||
dns:
|
||||
- 172.22.1.254
|
||||
dns_search: mailcow-network
|
||||
@ -172,7 +163,7 @@ services:
|
||||
- sogo
|
||||
|
||||
dovecot-mailcow:
|
||||
image: mailcow/dovecot:1.3
|
||||
image: mailcow/dovecot:1.4
|
||||
build: ./data/Dockerfiles/dovecot
|
||||
depends_on:
|
||||
unbound-mailcow:
|
||||
@ -187,16 +178,19 @@ services:
|
||||
- DBNAME=${DBNAME}
|
||||
- DBUSER=${DBUSER}
|
||||
- DBPASS=${DBPASS}
|
||||
logging:
|
||||
options:
|
||||
max-size: "5m"
|
||||
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
|
||||
@ -207,7 +201,7 @@ services:
|
||||
- dovecot
|
||||
|
||||
postfix-mailcow:
|
||||
image: mailcow/postfix:1.1
|
||||
image: mailcow/postfix:1.3
|
||||
build: ./data/Dockerfiles/postfix
|
||||
depends_on:
|
||||
unbound-mailcow:
|
||||
@ -226,9 +220,6 @@ services:
|
||||
- "${SMTPS_PORT:-465}:465"
|
||||
- "${SUBMISSION_PORT:-587}:587"
|
||||
restart: always
|
||||
logging:
|
||||
options:
|
||||
max-size: "5m"
|
||||
dns:
|
||||
- 172.22.1.254
|
||||
dns_search: mailcow-network
|
||||
@ -275,9 +266,6 @@ services:
|
||||
- ./data/conf/rspamd/dynmaps:/dynmaps:ro
|
||||
- ./data/assets/ssl/:/etc/ssl/mail/:ro
|
||||
- ./data/conf/nginx/:/etc/nginx/conf.d/:rw
|
||||
logging:
|
||||
options:
|
||||
max-size: "5m"
|
||||
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}"
|
||||
@ -294,7 +282,7 @@ services:
|
||||
acme-mailcow:
|
||||
depends_on:
|
||||
- nginx-mailcow
|
||||
image: mailcow/acme:1.12
|
||||
image: mailcow/acme:1.13
|
||||
build: ./data/Dockerfiles/acme
|
||||
dns:
|
||||
- 172.22.1.254
|
||||
|
@ -68,6 +68,7 @@ IMAPS_PORT=993
|
||||
POP_PORT=110
|
||||
POPS_PORT=995
|
||||
SIEVE_PORT=4190
|
||||
DOVEADM_PORT=127.0.0.1:19991
|
||||
|
||||
# Your timezone
|
||||
TZ=${TZ}
|
||||
|
@ -7,7 +7,7 @@ if [[ -z $(which git) ]]; then echo "Cannot find git, exiting."; exit 1; fi
|
||||
if [[ -z $(which awk) ]]; then echo "Cannot find awk, exiting."; exit 1; fi
|
||||
if [[ -z $(which sha1sum) ]]; then echo "Cannot find sha1sum, exiting."; exit 1; fi
|
||||
|
||||
CONFIG_ARRAY=("SKIP_LETS_ENCRYPT" "SKIP_CLAMD" "SKIP_IP_CHECK" "SKIP_FAIL2BAN" "ADDITIONAL_SAN")
|
||||
CONFIG_ARRAY=("SKIP_LETS_ENCRYPT" "SKIP_CLAMD" "SKIP_IP_CHECK" "SKIP_FAIL2BAN" "ADDITIONAL_SAN" "DOVEADM_PORT")
|
||||
echo >> mailcow.conf
|
||||
for option in ${CONFIG_ARRAY[@]}; do
|
||||
if [[ ${option} == "ADDITIONAL_SAN" ]]; then
|
||||
@ -18,7 +18,12 @@ for option in ${CONFIG_ARRAY[@]}; do
|
||||
elif [[ ${option} == "COMPOSE_PROJECT_NAME" ]]; then
|
||||
if ! grep -q ${option} mailcow.conf; then
|
||||
echo "Adding new option \"${option}\" to mailcow.conf"
|
||||
echo "${COMPOSE_PROJECT_NAME}=mailcow-dockerized" >> mailcow.conf
|
||||
echo "COMPOSE_PROJECT_NAME=mailcow-dockerized" >> mailcow.conf
|
||||
fi
|
||||
elif [[ ${option} == "DOVEADM_PORT" ]]; then
|
||||
if ! grep -q ${option} mailcow.conf; then
|
||||
echo "Adding new option \"${option}\" to mailcow.conf"
|
||||
echo "DOVEADM_PORT=127.0.0.1:19991" >> mailcow.conf
|
||||
fi
|
||||
elif ! grep -q ${option} mailcow.conf; then
|
||||
echo "Adding new option \"${option}\" to mailcow.conf"
|
||||
|
Loading…
Reference in New Issue
Block a user