commit
aa5b03dd99
|
@ -13,3 +13,6 @@ data/assets/ssl/*
|
||||||
data/web/.well-known/acme-challenge
|
data/web/.well-known/acme-challenge
|
||||||
data/conf/rspamd/local.d/*
|
data/conf/rspamd/local.d/*
|
||||||
data/conf/rspamd/override.d/*
|
data/conf/rspamd/override.d/*
|
||||||
|
!data/conf/nginx/dynmaps.conf
|
||||||
|
!data/conf/nginx/site.conf
|
||||||
|
data/conf/nginx/*.conf
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
sudo: required
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
script:
|
||||||
|
- echo 'Europe/Berlin' | MAILCOW_HOSTNAME=build.mailcow ./generate_config.sh
|
||||||
|
- docker-compose pull --ignore-pull-failures --parallel
|
||||||
|
- docker-compose build
|
||||||
|
- docker login --username=$DOCKER_HUB_USERNAME --password=$DOCKER_HUB_PASSWORD
|
||||||
|
- docker-compose push
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- secure: MpxpTwD7f0CNEVLitSpVmocK7O9r+BwFE1deEHK4AlQo/oc9cOlhGe1EL3mx9zbglPmjlDg/8kMUGv6vSirIabfBo9Szjps76bHckFr9lr2Ykkg0e29oC8pgPpSXD1eY/1ZIN/FvIkxpUFLETo1okS/j9q/A0DCGFmti0n3EoMORsgRz9CpNAiEh0zpSd6+euPAGHuczuCrDuO84my9bIOCjA/+aPunHNeXiuM8yIM2SxCSyGtIKT0+jvquIvLF58VxivysXBlRfhDn8fhB09nXA2Ru/derYQACfcmNSn9Pd4bDpebPJW5B9H/XA8xjb58uKinUlncbAMB/QnxoT75j9YRWJZRSQ+34XNYP6ZgK9soZ2TC6djQyEKTUu45Kp/1s+poSn42m9jytJJTmmK0KxsZTRcC8JD5nrjIMZWPUNNTwC5L4+I7ZRWg2WooK3LNyq1Ng8Hn6W77wSgsvAJw2HD3Lx58AprGUhHuBeaIZRuSN9aKwZrl9vKQJLqPnOp/nF2EC6kot5HYYtcotGtETXPUDih21gWD5ZM2BqVqYfQQnJnNMgeYmMdj6QQuTFqhuNJf7hXRIRkTnD3j1gDOLKQZazW0+N2JE8XWDFwi6fKScDsxT85lJti9HmzHa7+k4RVHmUYuDgRoPuzUgjWHvPsiz3/Z8WQ9JYpH84S8w=
|
||||||
|
- secure: fWzZisT6nGDNL4lf6tXB07eFG2drgBakHxzdF/NFVvzuP861RFR6omuL+ED0PgXrEHDJBxaBLv52je8irmUXrAH1CNr7T8DWiZo/h5h609Uzr+38T1NnIu4krL0Wo6/CDwlLKnzqTq9yBIZLQSHVJmo8AOpo1JPIi2ajodqj9ZfmAxDQTQl+G6zvQjtqIkYHsHY7A44Rto0f14ykn7w2S82Jn6Ry89VNI5V1WEO3sMpM/XekNP/HokNcRIuntL/0+kuLvTJ5akGoTjBQxSnSW95opzPeGky74HRU2obExJYqKvF0VfVJRNAqejwjIiFIbbjqV0Sk5391kFuhuBErQQDM1bOHGdxZ41HsJH29qNWIl7C33Yl10qERoqecgsJ1N/bS2ZEmWqm/zQh5GClCXPvYmzEqMYsMGM3vjbKdjDlc1Wh2w/eFclsXN9LSXh1mc35rtj46frcT6e5Kof87AIfC9hTgDvk9kAsyjaHMkSHSZthbZXCIcsD8qriNm5UqfFBYD79mPIP1S2YMQ2jscCsjHOZgYVrcm0kzDF21J1w6H0Lo7d1jw37LYlegBdtLQ9gYgqY2D5m+nxWuVoD5FZmpR+5JGtK+ootyLFF8aiFoHXd4op1JCxRLjgkmnZKXzw3kTQSpE7oa7CgzchtQmK2nqcqla1b5Qk7ilVcjooo=
|
|
@ -36,6 +36,8 @@ else
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
[[ ! -f ${ACME_BASE}/dhparams.pem ]] && cp ${SSL_EXAMPLE}/dhparams.pem ${ACME_BASE}/dhparams.pem
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||||
echo "SKIP_LETS_ENCRYPT=y, skipping Let's Encrypt..."
|
echo "SKIP_LETS_ENCRYPT=y, skipping Let's Encrypt..."
|
||||||
|
@ -44,7 +46,7 @@ while true; do
|
||||||
declare -a SQL_DOMAIN_ARR
|
declare -a SQL_DOMAIN_ARR
|
||||||
declare -a VALIDATED_CONFIG_DOMAINS
|
declare -a VALIDATED_CONFIG_DOMAINS
|
||||||
declare -a ADDITIONAL_VALIDATED_SAN
|
declare -a ADDITIONAL_VALIDATED_SAN
|
||||||
IFS=' ' read -r -a ADDITIONAL_SAN_ARR <<< "${ADDITIONAL_SAN}"
|
IFS=',' read -r -a ADDITIONAL_SAN_ARR <<< "${ADDITIONAL_SAN}"
|
||||||
IPV4=$(curl -4s https://mailcow.email/ip.php)
|
IPV4=$(curl -4s https://mailcow.email/ip.php)
|
||||||
|
|
||||||
while read line; do
|
while read line; do
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env python2
|
#!/usr/bin/env python2
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import os
|
||||||
import time
|
import time
|
||||||
import atexit
|
import atexit
|
||||||
import signal
|
import signal
|
||||||
|
@ -12,7 +13,12 @@ import redis
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
|
|
||||||
r = redis.StrictRedis(host='172.22.1.249', port=6379, db=0)
|
yes_regex = re.compile(r'([yY][eE][sS]|[yY])+$')
|
||||||
|
if re.search(yes_regex, os.getenv('SKIP_FAIL2BAN', 0)):
|
||||||
|
print "Skipping Fail2ban container..."
|
||||||
|
raise SystemExit
|
||||||
|
|
||||||
|
r = redis.StrictRedis(host='172.22.1.249', decode_responses=True, port=6379, db=0)
|
||||||
RULES = {
|
RULES = {
|
||||||
'mailcowdockerized_postfix-mailcow_1': 'warning: .*\[([0-9a-f\.:]+)\]: SASL .* authentication failed',
|
'mailcowdockerized_postfix-mailcow_1': 'warning: .*\[([0-9a-f\.:]+)\]: SASL .* authentication failed',
|
||||||
'mailcowdockerized_dovecot-mailcow_1': '-login: Disconnected \(auth failed, .*\): user=.*, method=.*, rip=([0-9a-f\.:]+),',
|
'mailcowdockerized_dovecot-mailcow_1': '-login: Disconnected \(auth failed, .*\): user=.*, method=.*, rip=([0-9a-f\.:]+),',
|
||||||
|
@ -32,6 +38,7 @@ def ban(address):
|
||||||
BAN_TIME = int(r.get("F2B_BAN_TIME"))
|
BAN_TIME = int(r.get("F2B_BAN_TIME"))
|
||||||
MAX_ATTEMPTS = int(r.get("F2B_MAX_ATTEMPTS"))
|
MAX_ATTEMPTS = int(r.get("F2B_MAX_ATTEMPTS"))
|
||||||
RETRY_WINDOW = int(r.get("F2B_RETRY_WINDOW"))
|
RETRY_WINDOW = int(r.get("F2B_RETRY_WINDOW"))
|
||||||
|
WHITELIST = r.hgetall("F2B_WHITELIST")
|
||||||
|
|
||||||
ip = ipaddress.ip_address(address.decode('ascii'))
|
ip = ipaddress.ip_address(address.decode('ascii'))
|
||||||
if type(ip) is ipaddress.IPv6Address and ip.ipv4_mapped:
|
if type(ip) is ipaddress.IPv6Address and ip.ipv4_mapped:
|
||||||
|
@ -40,6 +47,18 @@ def ban(address):
|
||||||
if ip.is_private or ip.is_loopback:
|
if ip.is_private or ip.is_loopback:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self_network = ipaddress.ip_network(address.decode('ascii'))
|
||||||
|
if WHITELIST:
|
||||||
|
for wl_key in WHITELIST:
|
||||||
|
wl_net = ipaddress.ip_network(wl_key.decode('ascii'), False)
|
||||||
|
if wl_net.overlaps(self_network):
|
||||||
|
log['time'] = int(round(time.time()))
|
||||||
|
log['priority'] = "info"
|
||||||
|
log['message'] = "Address %s is whitelisted by rule %s" % (self_network, wl_net)
|
||||||
|
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
|
||||||
|
print "Address %s is whitelisted by rule %s" % (self_network, wl_net)
|
||||||
|
return
|
||||||
|
|
||||||
net = ipaddress.ip_network((address + ('/24' if type(ip) is ipaddress.IPv4Address else '/64')).decode('ascii'), strict=False)
|
net = ipaddress.ip_network((address + ('/24' if type(ip) is ipaddress.IPv4Address else '/64')).decode('ascii'), strict=False)
|
||||||
net = str(net)
|
net = str(net)
|
||||||
|
|
||||||
|
@ -66,6 +85,7 @@ def ban(address):
|
||||||
else:
|
else:
|
||||||
subprocess.call(["ip6tables", "-I", "INPUT", "-s", net, "-j", "REJECT"])
|
subprocess.call(["ip6tables", "-I", "INPUT", "-s", net, "-j", "REJECT"])
|
||||||
subprocess.call(["ip6tables", "-I", "FORWARD", "-s", net, "-j", "REJECT"])
|
subprocess.call(["ip6tables", "-I", "FORWARD", "-s", net, "-j", "REJECT"])
|
||||||
|
r.hset("F2B_ACTIVE_BANS", "%s" % net, log['time'] + BAN_TIME)
|
||||||
else:
|
else:
|
||||||
log['time'] = int(round(time.time()))
|
log['time'] = int(round(time.time()))
|
||||||
log['priority'] = "warn"
|
log['priority'] = "warn"
|
||||||
|
@ -76,6 +96,13 @@ def ban(address):
|
||||||
def unban(net):
|
def unban(net):
|
||||||
log['time'] = int(round(time.time()))
|
log['time'] = int(round(time.time()))
|
||||||
log['priority'] = "info"
|
log['priority'] = "info"
|
||||||
|
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
|
||||||
|
if not net in bans:
|
||||||
|
log['message'] = "%s is not banned, skipping unban and deleting from queue (if any)" % net
|
||||||
|
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
|
||||||
|
print "%s is not banned, skipping unban and deleting from queue (if any)" % net
|
||||||
|
r.hdel("F2B_QUEUE_UNBAN", "%s" % net)
|
||||||
|
return
|
||||||
log['message'] = "Unbanning %s" % net
|
log['message'] = "Unbanning %s" % net
|
||||||
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
|
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
|
||||||
print "Unbanning %s" % net
|
print "Unbanning %s" % net
|
||||||
|
@ -85,6 +112,8 @@ def unban(net):
|
||||||
else:
|
else:
|
||||||
subprocess.call(["ip6tables", "-D", "INPUT", "-s", net, "-j", "REJECT"])
|
subprocess.call(["ip6tables", "-D", "INPUT", "-s", net, "-j", "REJECT"])
|
||||||
subprocess.call(["ip6tables", "-D", "FORWARD", "-s", net, "-j", "REJECT"])
|
subprocess.call(["ip6tables", "-D", "FORWARD", "-s", net, "-j", "REJECT"])
|
||||||
|
r.hdel("F2B_ACTIVE_BANS", "%s" % net)
|
||||||
|
r.hdel("F2B_QUEUE_UNBAN", "%s" % net)
|
||||||
del bans[net]
|
del bans[net]
|
||||||
|
|
||||||
def quit(signum, frame):
|
def quit(signum, frame):
|
||||||
|
@ -117,11 +146,15 @@ def autopurge():
|
||||||
while not quit_now:
|
while not quit_now:
|
||||||
BAN_TIME = int(r.get("F2B_BAN_TIME"))
|
BAN_TIME = int(r.get("F2B_BAN_TIME"))
|
||||||
MAX_ATTEMPTS = int(r.get("F2B_MAX_ATTEMPTS"))
|
MAX_ATTEMPTS = int(r.get("F2B_MAX_ATTEMPTS"))
|
||||||
|
QUEUE_UNBAN = r.hgetall("F2B_QUEUE_UNBAN")
|
||||||
|
if QUEUE_UNBAN:
|
||||||
|
for net in QUEUE_UNBAN:
|
||||||
|
unban(str(net))
|
||||||
for net in bans.copy():
|
for net in bans.copy():
|
||||||
if bans[net]['attempts'] >= MAX_ATTEMPTS:
|
if bans[net]['attempts'] >= MAX_ATTEMPTS:
|
||||||
if time.time() - bans[net]['last_attempt'] > BAN_TIME:
|
if time.time() - bans[net]['last_attempt'] > BAN_TIME:
|
||||||
unban(net)
|
unban(net)
|
||||||
time.sleep(60)
|
time.sleep(30)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
threads = []
|
threads = []
|
||||||
|
|
|
@ -7,9 +7,6 @@ rspamd_config.MAILCOW_AUTH = {
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
local redis_params
|
|
||||||
redis_params = rspamd_parse_redis_server('tag_settings')
|
|
||||||
if redis_params then
|
|
||||||
rspamd_config:register_symbol({
|
rspamd_config:register_symbol({
|
||||||
name = 'TAG_MOO',
|
name = 'TAG_MOO',
|
||||||
type = 'postfilter',
|
type = 'postfilter',
|
||||||
|
@ -20,11 +17,6 @@ rspamd_config:register_symbol({
|
||||||
local tagged_rcpt = task:get_symbol("TAGGED_RCPT")
|
local tagged_rcpt = task:get_symbol("TAGGED_RCPT")
|
||||||
local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN")
|
local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN")
|
||||||
|
|
||||||
local user = task:get_recipients(0)[1]['user']
|
|
||||||
local domain = task:get_recipients(0)[1]['domain']
|
|
||||||
local rcpt = user .. '@' .. domain
|
|
||||||
|
|
||||||
|
|
||||||
if tagged_rcpt and mailcow_domain then
|
if tagged_rcpt and mailcow_domain then
|
||||||
local tag = tagged_rcpt[1].options[1]
|
local tag = tagged_rcpt[1].options[1]
|
||||||
rspamd_logger.infox("found tag: %s", tag)
|
rspamd_logger.infox("found tag: %s", tag)
|
||||||
|
@ -57,5 +49,5 @@ rspamd_config:register_symbol({
|
||||||
end,
|
end,
|
||||||
priority = 11
|
priority = 11
|
||||||
})
|
})
|
||||||
end
|
|
||||||
|
|
||||||
|
|
|
@ -269,6 +269,34 @@ $tfa_data = get_tfa();
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">Fail2Ban parameters</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<?php
|
||||||
|
$f2b_data = get_f2b_parameters();
|
||||||
|
?>
|
||||||
|
<form class="form" data-id="f2b" role="form" method="post">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="ban_time">Ban time (s):</label>
|
||||||
|
<input type="number" class="form-control" id="ban_time" name="ban_time" value="<?=$f2b_data['ban_time'];?>" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="max_attempts">Max. attempts:</label>
|
||||||
|
<input type="number" class="form-control" id="max_attempts" name="max_attempts" value="<?=$f2b_data['max_attempts'];?>" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="retry_window">Retry window (s) for max. attempts:</label>
|
||||||
|
<input type="number" class="form-control" id="retry_window" name="retry_window" value="<?=$f2b_data['retry_window'];?>" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="retry_window">Whitelisted networks/hosts</label>
|
||||||
|
<textarea class="form-control" id="whitelist" name="whitelist" rows="5"><?=$f2b_data['whitelist'];?></textarea>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-default" id="add_item" data-id="f2b" data-api-url='edit/fail2ban' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div role="tabpanel" class="tab-pane" id="tab-postfix-logs">
|
<div role="tabpanel" class="tab-pane" id="tab-postfix-logs">
|
||||||
|
|
|
@ -1,164 +0,0 @@
|
||||||
<?php
|
|
||||||
require_once 'inc/vars.inc.php';
|
|
||||||
require_once 'inc/functions.inc.php';
|
|
||||||
$config = array(
|
|
||||||
'useEASforOutlook' => 'yes',
|
|
||||||
'autodiscoverType' => 'activesync',
|
|
||||||
'imap' => array(
|
|
||||||
'server' => $mailcow_hostname,
|
|
||||||
'port' => '993',
|
|
||||||
'ssl' => 'on',
|
|
||||||
),
|
|
||||||
'smtp' => array(
|
|
||||||
'server' => $mailcow_hostname,
|
|
||||||
'port' => '465',
|
|
||||||
'ssl' => 'on'
|
|
||||||
),
|
|
||||||
'activesync' => array(
|
|
||||||
'url' => 'https://'.$mailcow_hostname.'/Microsoft-Server-ActiveSync'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if(file_exists('inc/vars.local.inc.php')) {
|
|
||||||
include_once 'inc/vars.local.inc.php';
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------- DO NOT MODIFY ANYTHING BEYOND THIS LINE. IGNORE AT YOUR OWN RISK. ---------- */
|
|
||||||
|
|
||||||
error_reporting(0);
|
|
||||||
|
|
||||||
$data = trim(file_get_contents("php://input"));
|
|
||||||
|
|
||||||
// Desktop client needs IMAP, unless it's Outlook 2013 or higher on Windows
|
|
||||||
if (strpos($data, 'autodiscover/outlook/responseschema')) { // desktop client
|
|
||||||
$config['autodiscoverType'] = 'imap';
|
|
||||||
if ($config['useEASforOutlook'] == 'yes' &&
|
|
||||||
strpos($_SERVER['HTTP_USER_AGENT'], 'Windows NT') !== FALSE && // Windows
|
|
||||||
preg_match('/(Outlook|Office) (1[5-9]\.|[2-9]|1[0-9][0-9])/', $_SERVER['HTTP_USER_AGENT']) && // Outlook 2013 (version 15) or higher
|
|
||||||
strpos($_SERVER['HTTP_USER_AGENT'], 'MS Connectivity Analyzer') === FALSE // https://testconnectivity.microsoft.com doesn't support EAS for Outlook
|
|
||||||
) {
|
|
||||||
$config['autodiscoverType'] = 'activesync';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$dsn = "$database_type:host=$database_host;dbname=$database_name";
|
|
||||||
$opt = [
|
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
||||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
||||||
PDO::ATTR_EMULATE_PREPARES => false,
|
|
||||||
];
|
|
||||||
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
|
||||||
$login_user = strtolower(trim($_SERVER['PHP_AUTH_USER']));
|
|
||||||
$as = check_login($login_user, $_SERVER['PHP_AUTH_PW']);
|
|
||||||
|
|
||||||
if (!isset($_SERVER['PHP_AUTH_USER']) OR $as !== "user") {
|
|
||||||
header('WWW-Authenticate: Basic realm=""');
|
|
||||||
header('HTTP/1.0 401 Unauthorized');
|
|
||||||
exit;
|
|
||||||
} else {
|
|
||||||
if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
|
|
||||||
if ($as === "user") {
|
|
||||||
header("Content-Type: application/xml");
|
|
||||||
echo '<?xml version="1.0" encoding="utf-8" ?><Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">';
|
|
||||||
|
|
||||||
if(!$data) {
|
|
||||||
list($usec, $sec) = explode(' ', microtime());
|
|
||||||
echo '<Response>';
|
|
||||||
echo '<Error Time="' . date('H:i:s', $sec) . substr($usec, 0, strlen($usec) - 2) . '" Id="2477272013">';
|
|
||||||
echo '<ErrorCode>600</ErrorCode><Message>Invalid Request</Message><DebugData /></Error>';
|
|
||||||
echo '</Response>';
|
|
||||||
echo '</Autodiscover>';
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
$discover = new SimpleXMLElement($data);
|
|
||||||
$email = $discover->Request->EMailAddress;
|
|
||||||
|
|
||||||
if ($config['autodiscoverType'] == 'imap') {
|
|
||||||
?>
|
|
||||||
<Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
|
|
||||||
<User>
|
|
||||||
<DisplayName><?php echo $displayname; ?></DisplayName>
|
|
||||||
</User>
|
|
||||||
<Account>
|
|
||||||
<AccountType>email</AccountType>
|
|
||||||
<Action>settings</Action>
|
|
||||||
<Protocol>
|
|
||||||
<Type>IMAP</Type>
|
|
||||||
<Server><?php echo $config['imap']['server']; ?></Server>
|
|
||||||
<Port><?php echo $config['imap']['port']; ?></Port>
|
|
||||||
<DomainRequired>off</DomainRequired>
|
|
||||||
<LoginName><?php echo $email; ?></LoginName>
|
|
||||||
<SPA>off</SPA>
|
|
||||||
<SSL><?php echo $config['imap']['ssl']; ?></SSL>
|
|
||||||
<AuthRequired>on</AuthRequired>
|
|
||||||
</Protocol>
|
|
||||||
<Protocol>
|
|
||||||
<Type>SMTP</Type>
|
|
||||||
<Server><?php echo $config['smtp']['server']; ?></Server>
|
|
||||||
<Port><?php echo $config['smtp']['port']; ?></Port>
|
|
||||||
<DomainRequired>off</DomainRequired>
|
|
||||||
<LoginName><?php echo $email; ?></LoginName>
|
|
||||||
<SPA>off</SPA>
|
|
||||||
<SSL><?php echo $config['smtp']['ssl']; ?></SSL>
|
|
||||||
<AuthRequired>on</AuthRequired>
|
|
||||||
<UsePOPAuth>on</UsePOPAuth>
|
|
||||||
<SMTPLast>off</SMTPLast>
|
|
||||||
</Protocol>
|
|
||||||
<Protocol>
|
|
||||||
<Type>CalDAV</Type>
|
|
||||||
<Server>https://<?php echo $mailcow_hostname; ?>/SOGo/dav/<?php echo $email; ?>/Calendar</Server>
|
|
||||||
<DomainRequired>off</DomainRequired>
|
|
||||||
<LoginName><?php echo $email; ?></LoginName>
|
|
||||||
</Protocol>
|
|
||||||
<Protocol>
|
|
||||||
<Type>CardDAV</Type>
|
|
||||||
<Server>https://<?php echo $mailcow_hostname; ?>/SOGo/dav/<?php echo $email; ?>/Contacts</Server>
|
|
||||||
<DomainRequired>off</DomainRequired>
|
|
||||||
<LoginName><?php echo $email; ?></LoginName>
|
|
||||||
</Protocol>
|
|
||||||
</Account>
|
|
||||||
</Response>
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
else if ($config['autodiscoverType'] == 'activesync') {
|
|
||||||
$username = trim($email);
|
|
||||||
try {
|
|
||||||
$stmt = $pdo->prepare("SELECT `name` FROM `mailbox` WHERE `username`= :username");
|
|
||||||
$stmt->execute(array(':username' => $username));
|
|
||||||
$MailboxData = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
}
|
|
||||||
catch(PDOException $e) {
|
|
||||||
die("Failed to determine name from SQL");
|
|
||||||
}
|
|
||||||
if (!empty($MailboxData['name'])) {
|
|
||||||
$displayname = utf8_encode($MailboxData['name']);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$displayname = $email;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006">
|
|
||||||
<Culture>en:en</Culture>
|
|
||||||
<User>
|
|
||||||
<DisplayName><?php echo $displayname; ?></DisplayName>
|
|
||||||
<EMailAddress><?php echo $email; ?></EMailAddress>
|
|
||||||
</User>
|
|
||||||
<Action>
|
|
||||||
<Settings>
|
|
||||||
<Server>
|
|
||||||
<Type>MobileSync</Type>
|
|
||||||
<Url><?php echo $config['activesync']['url']; ?></Url>
|
|
||||||
<Name><?php echo $config['activesync']['url']; ?></Name>
|
|
||||||
</Server>
|
|
||||||
</Settings>
|
|
||||||
</Action>
|
|
||||||
</Response>
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
</Autodiscover>
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
|
@ -11,6 +11,11 @@ table.footable>tbody>tr.footable-empty>td {
|
||||||
.table-responsive {
|
.table-responsive {
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
@media screen and (max-width: 767px) {
|
||||||
|
.table-responsive {
|
||||||
|
overflow-x: scroll !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
body {
|
body {
|
||||||
overflow-y:scroll;
|
overflow-y:scroll;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,11 @@ table.footable>tbody>tr.footable-empty>td {
|
||||||
.table-responsive {
|
.table-responsive {
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
@media screen and (max-width: 767px) {
|
||||||
|
.table-responsive {
|
||||||
|
overflow-x: scroll !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
.footer-add-item {
|
.footer-add-item {
|
||||||
display:block;
|
display:block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -11,6 +11,11 @@ table.footable>tbody>tr.footable-empty>td {
|
||||||
.table-responsive {
|
.table-responsive {
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
@media screen and (max-width: 767px) {
|
||||||
|
.table-responsive {
|
||||||
|
overflow-x: scroll !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
.footer-add-item {
|
.footer-add-item {
|
||||||
display:block;
|
display:block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -30,4 +35,3 @@ table.footable>tbody>tr.footable-empty>td {
|
||||||
.inputMissingAttr {
|
.inputMissingAttr {
|
||||||
border-color: #FF4136;
|
border-color: #FF4136;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,11 @@ table.footable>tbody>tr.footable-empty>td {
|
||||||
.table-responsive {
|
.table-responsive {
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
@media screen and (max-width: 767px) {
|
||||||
|
.table-responsive {
|
||||||
|
overflow-x: scroll !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
.footer-add-item {
|
.footer-add-item {
|
||||||
display:block;
|
display:block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -229,6 +229,7 @@ function check_login($user, $pass) {
|
||||||
}
|
}
|
||||||
if (!isset($_SESSION['ldelay'])) {
|
if (!isset($_SESSION['ldelay'])) {
|
||||||
$_SESSION['ldelay'] = "0";
|
$_SESSION['ldelay'] = "0";
|
||||||
|
error_log("Mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
||||||
}
|
}
|
||||||
elseif (!isset($_SESSION['mailcow_cc_username'])) {
|
elseif (!isset($_SESSION['mailcow_cc_username'])) {
|
||||||
$_SESSION['ldelay'] = $_SESSION['ldelay']+0.5;
|
$_SESSION['ldelay'] = $_SESSION['ldelay']+0.5;
|
||||||
|
@ -1434,4 +1435,94 @@ function get_logs($container, $lines = 100) {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
function get_f2b_parameters() {
|
||||||
|
global $lang;
|
||||||
|
global $redis;
|
||||||
|
$data = array();
|
||||||
|
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$data['ban_time'] = $redis->Get('F2B_BAN_TIME');
|
||||||
|
$data['max_attempts'] = $redis->Get('F2B_MAX_ATTEMPTS');
|
||||||
|
$data['retry_window'] = $redis->Get('F2B_RETRY_WINDOW');
|
||||||
|
$wl = $redis->hGetAll('F2B_WHITELIST');
|
||||||
|
if (is_array($wl)) {
|
||||||
|
foreach ($wl as $key => $value) {
|
||||||
|
$tmp_data[] = $key;
|
||||||
|
}
|
||||||
|
$data['whitelist'] = implode(PHP_EOL, $tmp_data);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$data['whitelist'] = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (RedisException $e) {
|
||||||
|
$_SESSION['return'] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'msg' => 'Redis: '.$e
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
function edit_f2b_parameters($postarray) {
|
||||||
|
global $lang;
|
||||||
|
global $redis;
|
||||||
|
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
|
$_SESSION['return'] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'msg' => sprintf($lang['danger']['access_denied'])
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$is_now = get_f2b_parameters();
|
||||||
|
if (!empty($is_now)) {
|
||||||
|
$ban_time = intval((isset($postarray['ban_time'])) ? $postarray['ban_time'] : $is_now['ban_time']);
|
||||||
|
$max_attempts = intval((isset($postarray['max_attempts'])) ? $postarray['max_attempts'] : $is_now['active_int']);
|
||||||
|
$retry_window = intval((isset($postarray['retry_window'])) ? $postarray['retry_window'] : $is_now['retry_window']);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$_SESSION['return'] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'msg' => sprintf($lang['danger']['access_denied'])
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$wl = $postarray['whitelist'];
|
||||||
|
$ban_time = ($ban_time < 60) ? 60 : $ban_time;
|
||||||
|
$max_attempts = ($max_attempts < 1) ? 1 : $max_attempts;
|
||||||
|
$retry_window = ($retry_window < 1) ? 1 : $retry_window;
|
||||||
|
try {
|
||||||
|
$redis->Set('F2B_BAN_TIME', $ban_time);
|
||||||
|
$redis->Set('F2B_MAX_ATTEMPTS', $max_attempts);
|
||||||
|
$redis->Set('F2B_RETRY_WINDOW', $retry_window);
|
||||||
|
$redis->Del('F2B_WHITELIST');
|
||||||
|
if(!empty($wl)) {
|
||||||
|
$wl_array = array_map('trim', preg_split( "/( |,|;|\n)/", $wl));
|
||||||
|
if (is_array($wl_array)) {
|
||||||
|
foreach ($wl_array as $wl_item) {
|
||||||
|
$cidr = explode('/', $wl_item);
|
||||||
|
if (filter_var($cidr[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) && (!isset($cidr[1]) || ($cidr[1] >= 0 && $cidr[1] <= 32))) {
|
||||||
|
$redis->hSet('F2B_WHITELIST', $wl_item, 1);
|
||||||
|
}
|
||||||
|
elseif (filter_var($cidr[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && (!isset($cidr[1]) || ($cidr[1] >= 0 && $cidr[1] <= 128))) {
|
||||||
|
$redis->hSet('F2B_WHITELIST', $wl_item, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (RedisException $e) {
|
||||||
|
$_SESSION['return'] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'msg' => 'Redis: '.$e
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$_SESSION['return'] = array(
|
||||||
|
'type' => 'success',
|
||||||
|
'msg' => 'Saved changes to Fail2ban configuration'
|
||||||
|
);
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
|
|
|
@ -17,6 +17,28 @@ $database_name = getenv('DBNAME');
|
||||||
// Other variables
|
// Other variables
|
||||||
$mailcow_hostname = getenv('MAILCOW_HOSTNAME');
|
$mailcow_hostname = getenv('MAILCOW_HOSTNAME');
|
||||||
|
|
||||||
|
// Autodiscover settings
|
||||||
|
$autodiscover_config = array(
|
||||||
|
// Enable the autodiscover service for Outlook desktop clients
|
||||||
|
'useEASforOutlook' => 'yes',
|
||||||
|
// General autodiscover service type: "activesync" or "imap"
|
||||||
|
'autodiscoverType' => 'activesync',
|
||||||
|
'imap' => array(
|
||||||
|
'server' => $mailcow_hostname,
|
||||||
|
'port' => getenv('IMAPS_PORT'),
|
||||||
|
'ssl' => 'on',
|
||||||
|
),
|
||||||
|
'smtp' => array(
|
||||||
|
'server' => $mailcow_hostname,
|
||||||
|
'port' => getenv('SMTPS_PORT'),
|
||||||
|
'ssl' => 'on'
|
||||||
|
),
|
||||||
|
'activesync' => array(
|
||||||
|
'url' => 'https://'.$mailcow_hostname.'/Microsoft-Server-ActiveSync'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
// Where to go after adding and editing objects
|
// Where to go after adding and editing objects
|
||||||
// Can be "form" or "previous"
|
// Can be "form" or "previous"
|
||||||
// "form" will stay in the current form, "previous" will redirect to previous page
|
// "form" will stay in the current form, "previous" will redirect to previous page
|
||||||
|
|
|
@ -1921,6 +1921,41 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "fail2ban":
|
||||||
|
// No items
|
||||||
|
if (isset($_POST['attr'])) {
|
||||||
|
$attr = (array)json_decode($_POST['attr'], true);
|
||||||
|
if (edit_f2b_parameters($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'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "admin":
|
case "admin":
|
||||||
// No items as there is only one admin
|
// No items as there is only one admin
|
||||||
if (isset($_POST['attr'])) {
|
if (isset($_POST['attr'])) {
|
||||||
|
|
|
@ -122,6 +122,14 @@ services:
|
||||||
- DBUSER=${DBUSER}
|
- DBUSER=${DBUSER}
|
||||||
- DBPASS=${DBPASS}
|
- DBPASS=${DBPASS}
|
||||||
- MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
|
- MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
|
||||||
|
- IMAP_PORT=${IMAP_PORT:-143}
|
||||||
|
- IMAPS_PORT=${IMAPS_PORT:-993}
|
||||||
|
- POP_PORT=${POP_PORT:-110}
|
||||||
|
- POPS_PORT=${POPS_PORT:-995}
|
||||||
|
- SIEVE_PORT=${SIEVE_PORT:-4190}
|
||||||
|
- SUBMISSION_PORT=${SUBMISSION_PORT:-587}
|
||||||
|
- SMTPS_PORT=${SMTPS_PORT:-465}
|
||||||
|
- SMTP_PORT=${SMTP_PORT:-25}
|
||||||
restart: always
|
restart: always
|
||||||
logging:
|
logging:
|
||||||
options:
|
options:
|
||||||
|
@ -285,7 +293,7 @@ services:
|
||||||
acme-mailcow:
|
acme-mailcow:
|
||||||
depends_on:
|
depends_on:
|
||||||
- nginx-mailcow
|
- nginx-mailcow
|
||||||
image: mailcow/acme:1.4
|
image: mailcow/acme:1.5
|
||||||
build: ./data/Dockerfiles/acme
|
build: ./data/Dockerfiles/acme
|
||||||
dns:
|
dns:
|
||||||
- 172.22.1.254
|
- 172.22.1.254
|
||||||
|
@ -311,7 +319,7 @@ services:
|
||||||
- acme
|
- acme
|
||||||
|
|
||||||
fail2ban-mailcow:
|
fail2ban-mailcow:
|
||||||
image: mailcow/fail2ban:1.2
|
image: mailcow/fail2ban:1.3
|
||||||
build: ./data/Dockerfiles/fail2ban
|
build: ./data/Dockerfiles/fail2ban
|
||||||
depends_on:
|
depends_on:
|
||||||
- dovecot-mailcow
|
- dovecot-mailcow
|
||||||
|
@ -323,6 +331,7 @@ services:
|
||||||
privileged: true
|
privileged: true
|
||||||
environment:
|
environment:
|
||||||
- TZ=${TZ}
|
- TZ=${TZ}
|
||||||
|
- SKIP_FAIL2BAN=${SKIP_FAIL2BAN:-no}
|
||||||
network_mode: "host"
|
network_mode: "host"
|
||||||
dns:
|
dns:
|
||||||
- 172.22.1.254
|
- 172.22.1.254
|
||||||
|
@ -330,7 +339,6 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
- /lib/modules:/lib/modules:ro
|
- /lib/modules:/lib/modules:ro
|
||||||
|
|
||||||
ipv6nat:
|
ipv6nat:
|
||||||
image: robbertkl/ipv6nat
|
image: robbertkl/ipv6nat
|
||||||
restart: always
|
restart: always
|
||||||
|
|
|
@ -81,6 +81,8 @@ ADDITIONAL_SAN=
|
||||||
# To never run acme-mailcow for Let's Encrypt, set this to y
|
# To never run acme-mailcow for Let's Encrypt, set this to y
|
||||||
SKIP_LETS_ENCRYPT=n
|
SKIP_LETS_ENCRYPT=n
|
||||||
|
|
||||||
|
# To never run fail2ban-mailcow
|
||||||
|
SKIP_FAIL2BAN=n
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue