Merge branch 'master' of https://github.com/andryyy/mailcow-dockerized into recipient_map

Conflicts:
	data/web/inc/init_db.inc.php
This commit is contained in:
Michael Kuron
2018-01-27 17:22:08 +01:00
42 changed files with 710 additions and 434 deletions

View File

@@ -6,20 +6,38 @@ LABEL maintainer "André Peters <andre.peters@servercow.de>"
COPY dl_files.sh bootstrap.sh ./
# Installation
RUN apk add --update \
&& apk add --no-cache clamav clamav-libunrar curl bash tini \
ENV CLAMAV 0.99.3
RUN apk add --no-cache --virtual build-dependencies alpine-sdk ncurses-dev zlib-dev bzip2-dev pcre-dev linux-headers fts-dev libxml2-dev libressl-dev \
&& apk add --no-cache curl bash tini libxml2 libbz2 pcre fts libressl \
&& wget -O - https://www.clamav.net/downloads/production/clamav-${CLAMAV}.tar.gz | tar xfvz - \
&& cd clamav-${CLAMAV} \
&& LIBS=-lfts ./configure \
--prefix=/usr \
--libdir=/usr/lib \
--sysconfdir=/etc/clamav \
--mandir=/usr/share/man \
--infodir=/usr/share/info \
--without-iconv \
--disable-llvm \
--with-user=clamav \
--with-group=clamav \
--with-dbdir=/var/lib/clamav \
--enable-clamdtop \
--enable-bigstack \
--with-pcre \
&& make -j4 \
&& make install \
&& make clean \
&& cd .. && rm -rf clamav-${CLAMAV} \
&& apk del build-dependencies \
&& addgroup -S clamav \
&& adduser -S -D -h /var/lib/clamav -s /sbin/nologin -G clamav -g clamav clamav \
&& mkdir -p /run/clamav \
&& chown clamav:clamav /run/clamav \
&& chmod +x /dl_files.sh \
&& set -ex; /bin/bash /dl_files.sh \
&& mkdir /run/clamav \
&& chown clamav:clamav /run/clamav \
&& chmod 750 /run/clamav \
&& sed -i '/Foreground yes/s/^#//g' /etc/clamav/clamd.conf \
&& sed -i '/TCPSocket 3310/s/^#//g' /etc/clamav/clamd.conf \
&& sed -i 's#LogFile /var/log/clamav/clamd.log#LogFile /tmp/logpipe_clamd#g' /etc/clamav/clamd.conf \
&& sed -i 's/#PhishingSignatures yes/PhishingSignatures no/g' /etc/clamav/clamd.conf \
&& sed -i 's/#PhishingScanURLs yes/PhishingScanURLs no/g' /etc/clamav/clamd.conf \
&& sed -i 's#UpdateLogFile /var/log/clamav/freshclam.log#UpdateLogFile /tmp/logpipe_freshclam#g' /etc/clamav/freshclam.conf \
&& sed -i '/Foreground yes/s/^#//g' /etc/clamav/freshclam.conf
&& chmod 750 /run/clamav
# Port provision
EXPOSE 3310

View File

@@ -33,26 +33,29 @@ open my $file, '<', "/etc/sogo/sieve.creds";
my $creds = <$file>;
close $file;
my ($master_user, $master_pass) = split /:/, $creds;
my $sth = $dbh->prepare("SELECT id, user1, user2, host1, authmech1, password1, exclude, port1, enc1, delete2duplicates, maxage, subfolder2, delete1, delete2 FROM imapsync WHERE active = 1 AND (UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(last_run) > mins_interval * 60 OR last_run IS NULL) ORDER BY last_run");
my $sth = $dbh->prepare("SELECT id, user1, user2, host1, authmech1, password1, exclude, port1, enc1, delete2duplicates, maxage, subfolder2, delete1, delete2, automap, skipcrossduplicates, maxbytespersecond FROM imapsync WHERE active = 1 AND (UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(last_run) > mins_interval * 60 OR last_run IS NULL) ORDER BY last_run");
$sth->execute();
my $row;
while ($row = $sth->fetchrow_arrayref()) {
$id = @$row[0];
$user1 = @$row[1];
$user2 = @$row[2];
$host1 = @$row[3];
$authmech1 = @$row[4];
$password1 = @$row[5];
$exclude = @$row[6];
$port1 = @$row[7];
$enc1 = @$row[8];
$delete2duplicates = @$row[9];
$maxage = @$row[10];
$subfolder2 = @$row[11];
$delete1 = @$row[12];
$delete2 = @$row[13];
$id = @$row[0];
$user1 = @$row[1];
$user2 = @$row[2];
$host1 = @$row[3];
$authmech1 = @$row[4];
$password1 = @$row[5];
$exclude = @$row[6];
$port1 = @$row[7];
$enc1 = @$row[8];
$delete2duplicates = @$row[9];
$maxage = @$row[10];
$subfolder2 = @$row[11];
$delete1 = @$row[12];
$delete2 = @$row[13];
$automap = @$row[14];
$skipcrossduplicates = @$row[15];
$maxbytespersecond = @$row[16];
$is_running = $dbh->prepare("UPDATE imapsync SET is_running = 1 WHERE id = ?");
$is_running->bind_param( 1, ${id} );
@@ -72,11 +75,14 @@ while ($row = $sth->fetchrow_arrayref()) {
"--tmpdir", "/tmp",
"--subscribeall",
($exclude eq "" ? () : ("--exclude", $exclude)),
($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)),
($maxage eq "0" ? () : ('--maxage', $maxage)),
($delete2duplicates ne "1" ? () : ('--delete2duplicates')),
($delete1 ne "1" ? () : ('--delete')),
($delete2 ne "1" ? () : ('--delete2')),
($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)),
($maxage eq "0" ? () : ('--maxage', $maxage)),
($maxbytespersecond eq "0" ? () : ('--maxbytespersecond', $maxage)),
($delete2duplicates ne "1" ? () : ('--delete2duplicates')),
($delete1 ne "1" ? () : ('--delete')),
($delete2 ne "1" ? () : ('--delete2')),
($automap ne "1" ? () : ('--automap')),
($skipcrossduplicates ne "1" ? () : ('--skipcrossduplicates')),
(!defined($enc1) ? () : ($enc1)),
"--host1", $host1,
"--user1", $user1,

View File

@@ -14,11 +14,11 @@ import json
yes_regex = re.compile(r'([yY][eE][sS]|[yY])+$')
if re.search(yes_regex, os.getenv('SKIP_FAIL2BAN', 0)):
print "SKIP_FAIL2BAN=y, Skipping Fail2ban container..."
print 'SKIP_FAIL2BAN=y, Skipping Fail2ban container...'
time.sleep(31536000)
raise SystemExit
r = redis.StrictRedis(host='172.22.1.249', decode_responses=True, port=6379, db=0)
r = redis.StrictRedis(host=os.getenv('IPV4_NETWORK', '172.22.1') + '.249', decode_responses=True, port=6379, db=0)
pubsub = r.pubsub()
RULES = {}
@@ -29,19 +29,23 @@ RULES[4] = '-login: Aborted login \(tried to use disallowed .+\): user=.+, rip=(
RULES[5] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked'
RULES[6] = 'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)'
r.setnx("F2B_BAN_TIME", "1800")
r.setnx("F2B_MAX_ATTEMPTS", "10")
r.setnx("F2B_RETRY_WINDOW", "600")
r.setnx('F2B_BAN_TIME', '1800')
r.setnx('F2B_MAX_ATTEMPTS', '10')
r.setnx('F2B_RETRY_WINDOW', '600')
r.setnx('F2B_NETBAN_IPV6', '64')
r.setnx('F2B_NETBAN_IPV4', '24')
bans = {}
log = {}
quit_now = False
def ban(address):
BAN_TIME = int(r.get("F2B_BAN_TIME"))
MAX_ATTEMPTS = int(r.get("F2B_MAX_ATTEMPTS"))
RETRY_WINDOW = int(r.get("F2B_RETRY_WINDOW"))
WHITELIST = r.hgetall("F2B_WHITELIST")
BAN_TIME = int(r.get('F2B_BAN_TIME'))
MAX_ATTEMPTS = int(r.get('F2B_MAX_ATTEMPTS'))
RETRY_WINDOW = int(r.get('F2B_RETRY_WINDOW'))
WHITELIST = r.hgetall('F2B_WHITELIST')
NETBAN_IPV6 = '/' + str(r.get('F2B_NETBAN_IPV6'))
NETBAN_IPV4 = '/' + str(r.get('F2B_NETBAN_IPV4'))
ip = ipaddress.ip_address(address.decode('ascii'))
if type(ip) is ipaddress.IPv6Address and ip.ipv4_mapped:
@@ -56,13 +60,13 @@ def ban(address):
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)
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 + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)).decode('ascii'), strict=False)
net = str(net)
if not net in bans or time.time() - bans[net]['last_attempt'] > RETRY_WINDOW:
@@ -78,45 +82,45 @@ def ban(address):
if bans[net]['attempts'] >= MAX_ATTEMPTS:
log['time'] = int(round(time.time()))
log['priority'] = "crit"
log['message'] = "Banning %s" % net
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
print "Banning %s for %d minutes" % (net, BAN_TIME / 60)
log['priority'] = 'crit'
log['message'] = 'Banning %s' % net
r.lpush('F2B_LOG', json.dumps(log, ensure_ascii=False))
print 'Banning %s for %d minutes' % (net, BAN_TIME / 60)
if type(ip) is ipaddress.IPv4Address:
subprocess.call(["iptables", "-I", "INPUT", "-s", net, "-j", "REJECT"])
subprocess.call(["iptables", "-I", "FORWARD", "-s", net, "-j", "REJECT"])
subprocess.call(['iptables', '-I', 'INPUT', '-s', net, '-j', 'REJECT'])
subprocess.call(['iptables', '-I', 'FORWARD', '-s', net, '-j', 'REJECT'])
else:
subprocess.call(["ip6tables", "-I", "INPUT", "-s", net, "-j", "REJECT"])
subprocess.call(["ip6tables", "-I", "FORWARD", "-s", net, "-j", "REJECT"])
r.hset("F2B_ACTIVE_BANS", "%s" % net, log['time'] + BAN_TIME)
subprocess.call(['ip6tables', '-I', 'INPUT', '-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:
log['time'] = int(round(time.time()))
log['priority'] = "warn"
log['message'] = "%d more attempts in the next %d seconds until %s is banned" % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net)
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
print "%d more attempts in the next %d seconds until %s is banned" % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net)
log['priority'] = 'warn'
log['message'] = '%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net)
r.lpush('F2B_LOG', json.dumps(log, ensure_ascii=False))
print '%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net)
def unban(net):
log['time'] = int(round(time.time()))
log['priority'] = "info"
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
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)
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
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
print "Unbanning %s" % net
log['message'] = 'Unbanning %s' % net
r.lpush('F2B_LOG', json.dumps(log, ensure_ascii=False))
print 'Unbanning %s' % net
if type(ipaddress.ip_network(net.decode('ascii'))) is ipaddress.IPv4Network:
subprocess.call(["iptables", "-D", "INPUT", "-s", net, "-j", "REJECT"])
subprocess.call(["iptables", "-D", "FORWARD", "-s", net, "-j", "REJECT"])
subprocess.call(['iptables', '-D', 'INPUT', '-s', net, '-j', 'REJECT'])
subprocess.call(['iptables', '-D', 'FORWARD', '-s', net, '-j', 'REJECT'])
else:
subprocess.call(["ip6tables", "-D", "INPUT", "-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)
subprocess.call(['ip6tables', '-D', 'INPUT', '-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]
def quit(signum, frame):
@@ -125,21 +129,21 @@ def quit(signum, frame):
def clear():
log['time'] = int(round(time.time()))
log['priority'] = "info"
log['message'] = "Clearing all bans"
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
print "Clearing all bans"
log['priority'] = 'info'
log['message'] = 'Clearing all bans'
r.lpush('F2B_LOG', json.dumps(log, ensure_ascii=False))
print 'Clearing all bans'
for net in bans.copy():
unban(net)
pubsub.unsubscribe()
def watch():
log['time'] = int(round(time.time()))
log['priority'] = "info"
log['message'] = "Watching Redis channel F2B_CHANNEL"
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
pubsub.subscribe("F2B_CHANNEL")
print "Subscribing to Redis channel F2B_CHANNEL"
log['priority'] = 'info'
log['message'] = 'Watching Redis channel F2B_CHANNEL'
r.lpush('F2B_LOG', json.dumps(log, ensure_ascii=False))
pubsub.subscribe('F2B_CHANNEL')
print 'Subscribing to Redis channel F2B_CHANNEL'
while True:
for item in pubsub.listen():
for rule_id, rule_regex in RULES.iteritems():
@@ -150,18 +154,18 @@ def watch():
ip = ipaddress.ip_address(addr.decode('ascii'))
if ip.is_private or ip.is_loopback:
continue
print "%s matched rule id %d" % (addr, rule_id)
print '%s matched rule id %d' % (addr, rule_id)
log['time'] = int(round(time.time()))
log['priority'] = "warn"
log['message'] = "%s matched rule id %d" % (addr, rule_id)
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
log['priority'] = 'warn'
log['message'] = '%s matched rule id %d' % (addr, rule_id)
r.lpush('F2B_LOG', json.dumps(log, ensure_ascii=False))
ban(addr)
def autopurge():
while not quit_now:
BAN_TIME = int(r.get("F2B_BAN_TIME"))
MAX_ATTEMPTS = int(r.get("F2B_MAX_ATTEMPTS"))
QUEUE_UNBAN = r.hgetall("F2B_QUEUE_UNBAN")
BAN_TIME = int(r.get('F2B_BAN_TIME'))
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))

View File

@@ -1,10 +1,11 @@
FROM php:7.1-fpm-alpine
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
ENV REDIS_PECL 3.1.4
ENV MEMCACHED_PECL 3.0.3
ENV APCU_PECL 5.1.8
ENV REDIS_PECL 3.1.6
ENV MEMCACHED_PECL 3.0.4
ENV APCU_PECL 5.1.9
ENV IMAGICK_PECL 3.4.3
ENV MAILPARSE_PECL 3.0.2
RUN apk add -U --no-cache libxml2-dev \
icu-dev \
@@ -41,27 +42,16 @@ RUN apk add -U --no-cache libxml2-dev \
Net_Sieve \
NET_SMTP \
Mail_mime \
&& pecl install redis-${REDIS_PECL} memcached-${MEMCACHED_PECL} APCu-${APCU_PECL} imagick-${IMAGICK_PECL} mailparse \
&& pecl install redis-${REDIS_PECL} memcached-${MEMCACHED_PECL} APCu-${APCU_PECL} imagick-${IMAGICK_PECL} mailparse-${MAILPARSE_PECL} \
&& docker-php-ext-enable redis apcu memcached imagick mailparse \
&& pecl clear-cache \
&& docker-php-ext-configure intl \
&& docker-php-ext-install -j 4 intl gettext ldap sockets soap pdo pdo_mysql xmlrpc gd zip pcntl opcache \
&& docker-php-ext-configure imap --with-imap --with-imap-ssl \
&& docker-php-ext-install -j 4 imap \
&& apk del --purge autoconf g++ make libxml2-dev icu-dev imap-dev openssl-dev cyrus-sasl-dev pcre-dev libpng-dev libpng-dev libjpeg-turbo-dev libwebp-dev zlib-dev imagemagick-dev \
&& { \
echo 'opcache.enable=1'; \
echo 'opcache.enable_cli=1'; \
echo 'opcache.interned_strings_buffer=8'; \
echo 'opcache.max_accelerated_files=10000'; \
echo 'opcache.memory_consumption=128'; \
echo 'opcache.save_comments=1'; \
echo 'opcache.revalidate_freq=1'; \
} > /usr/local/etc/php/conf.d/opcache-recommended.ini
&& apk del --purge autoconf g++ make libxml2-dev icu-dev imap-dev openssl-dev cyrus-sasl-dev pcre-dev libpng-dev libpng-dev libjpeg-turbo-dev libwebp-dev zlib-dev imagemagick-dev
COPY ./docker-entrypoint.sh /
EXPOSE 9000
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["php-fpm"]

View File

@@ -26,7 +26,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
syslog-ng-core \
syslog-ng-mod-redis \
&& rm -rf /var/lib/apt/lists/* \
&& touch /etc/default/locale
&& touch /etc/default/locale \
&& printf '#!/bin/bash\n/usr/sbin/postconf -c /opt/postfix/conf "$@"' > /usr/local/sbin/postconf \
&& chmod +x /usr/local/sbin/postconf
RUN addgroup --system --gid 600 zeyple
RUN adduser --system --home /var/lib/zeyple --no-create-home --uid 600 --gid 600 --disabled-login zeyple

View File

@@ -6,7 +6,7 @@ while read QUERY; do
echo "500 dunno"
continue
fi
result=$(curl -s http://172.22.1.251:8081/forwardinghosts.php?host=${QUERY[1]})
result=$(curl -s http://nginx:8081/forwardinghosts.php?host=${QUERY[1]})
logger -t whitelist_forwardinghosts -p mail.info "Look up ${QUERY[1]} on whitelist, result $result"
echo ${result}
done

View File

@@ -1 +1 @@
settings = "http://172.22.1.251:8081/settings.php";
settings = "http://nginx:8081/settings.php";

View File

@@ -18,11 +18,13 @@ done
mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS sogo_view"
mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, home, kind, multiple_bookings) AS
SELECT mailbox.username, mailbox.domain, mailbox.username, mailbox.password, mailbox.name, mailbox.username, IFNULL(ga.aliases, ''), IFNULL(gda.ad_alias, ''), CONCAT('/var/vmail/', maildir), mailbox.kind, mailbox.multiple_bookings FROM mailbox
LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username = mailbox.username
CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, sa_aliases, ad_aliases, home, kind, multiple_bookings) AS
SELECT mailbox.username, mailbox.domain, mailbox.username, mailbox.password, mailbox.name, mailbox.username, IFNULL(GROUP_CONCAT(ga.aliases SEPARATOR ' '), ''), IFNULL(gsa.send_as_acl, ''), IFNULL(gda.ad_alias, ''), CONCAT('/var/vmail/', maildir), mailbox.kind, mailbox.multiple_bookings FROM mailbox
LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username REGEXP CONCAT('(^|,)', mailbox.username, '($|,)')
LEFT OUTER JOIN grouped_sender_acl gsa ON gsa.username = mailbox.username
LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username
WHERE mailbox.active = '1';
WHERE mailbox.active = '1'
GROUP BY mailbox.username;
EOF
@@ -67,6 +69,7 @@ while read line
<key>MailFieldNames</key>
<array>
<string>aliases</string>
<string>sa_aliases</string>
<string>ad_aliases</string>
</array>
<key>KindFieldName</key>

View File

@@ -191,6 +191,7 @@ phpfpm_checks() {
host_ip=$(get_container_ip php-fpm-mailcow)
err_c_cur=${err_count}
cgi-fcgi -bind -connect ${host_ip}:9000 | grep "Content-type" 1>&2; err_count=$(( ${err_count} + ($? * 2)))
cgi-fcgi -bind -connect ${host_ip}:9001 | grep "Content-type" 1>&2; err_count=$(( ${err_count} + ($? * 2)))
/usr/lib/nagios/plugins/check_ping -4 -H ${host_ip} -w 2000,10% -c 4000,100% -p2 1>&2; err_count=$(( ${err_count} + $? ))
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))