Merge pull request #185 from andryyy/dev

Merge from master to dev
This commit is contained in:
André Peters 2017-04-11 11:26:23 +02:00 committed by GitHub
commit 9633a34f9f
44 changed files with 1008 additions and 668 deletions

View File

@ -1,33 +1,30 @@
FROM ubuntu:xenial FROM debian:stretch-slim
#ubuntu:xenial
MAINTAINER Andre Peters <andre.peters@servercow.de> MAINTAINER Andre Peters <andre.peters@servercow.de>
ENV DEBIAN_FRONTEND noninteractive ENV DEBIAN_FRONTEND noninteractive
ENV LC_ALL C ENV LC_ALL C
ENV DOVECOT_VERSION 2.2.28
ENV PIGEONHOLE_VERSION 0.4.17
RUN dpkg-divert --local --rename --add /sbin/initctl \ RUN apt-get update \
&& ln -sf /bin/true /sbin/initctl \ && apt-get -y install libpam-dev \
&& dpkg-divert --local --rename --add /usr/bin/ischroot \ default-libmysqlclient-dev \
&& ln -sf /bin/true /usr/bin/ischroot lzma-dev \
liblz-dev \
RUN apt-get update libbz2-dev \
RUN apt-get -y install dovecot-common \ liblz4-dev \
dovecot-core \ liblzma-dev \
dovecot-imapd \ build-essential \
dovecot-lmtpd \ autotools-dev \
dovecot-managesieved \ automake \
dovecot-sieve \
dovecot-mysql \
dovecot-pop3d \
dovecot-dev \
syslog-ng \ syslog-ng \
syslog-ng-core \ syslog-ng-core \
ca-certificates \ ca-certificates \
supervisor \ supervisor \
wget \ wget \
curl \ curl \
build-essential \ libssl-dev \
autotools-dev \
automake \
libauthen-ntlm-perl \ libauthen-ntlm-perl \
libcrypt-ssleay-perl \ libcrypt-ssleay-perl \
libdigest-hmac-perl \ libdigest-hmac-perl \
@ -52,36 +49,57 @@ RUN apt-get -y install dovecot-common \
make \ make \
cpanminus cpanminus
RUN wget https://www.dovecot.org/releases/2.2/dovecot-$DOVECOT_VERSION.tar.gz -O - | tar xvz \
&& cd dovecot-$DOVECOT_VERSION \
&& ./configure --with-mysql --with-lzma --with-lz4 --with-ssl=openssl --with-notify=inotify --with-storages=mdbox,sdbox,maildir,mbox,imapc,pop3c --with-bzlib --with-zlib \
&& make -j3 \
&& make install \
&& make clean
RUN wget https://pigeonhole.dovecot.org/releases/2.2/dovecot-2.2-pigeonhole-$PIGEONHOLE_VERSION.tar.gz -O - | tar xvz \
&& cd dovecot-2.2-pigeonhole-$PIGEONHOLE_VERSION \
&& ./configure \
&& make -j3 \
&& make install \
&& make clean
RUN sed -i -E 's/^(\s*)system\(\);/\1unix-stream("\/dev\/log");/' /etc/syslog-ng/syslog-ng.conf RUN sed -i -E 's/^(\s*)system\(\);/\1unix-stream("\/dev\/log");/' /etc/syslog-ng/syslog-ng.conf
RUN cpanm Data::Uniqid Mail::IMAPClient String::Util RUN cpanm Data::Uniqid Mail::IMAPClient String::Util
RUN echo '* * * * * root /usr/local/bin/imapsync_cron.pl' > /etc/cron.d/imapsync RUN echo '* * * * * root /usr/local/bin/imapsync_cron.pl' > /etc/cron.d/imapsync
RUN echo '30 3 * * * vmail /usr/bin/doveadm quota recalc -A' > /etc/cron.d/dovecot-sync RUN echo '30 3 * * * vmail /usr/bin/doveadm quota recalc -A' > /etc/cron.d/dovecot-sync
WORKDIR /tmp
RUN wget http://hg.dovecot.org/dovecot-antispam-plugin/archive/tip.tar.gz -O - | tar xvz \
&& cd /tmp/dovecot-antispam* \
&& ./autogen.sh \
&& ./configure --prefix=/usr \
&& make \
&& make install
COPY ./imapsync /usr/local/bin/imapsync COPY ./imapsync /usr/local/bin/imapsync
COPY ./postlogin.sh /usr/local/bin/postlogin.sh COPY ./postlogin.sh /usr/local/bin/postlogin.sh
COPY ./imapsync_cron.pl /usr/local/bin/imapsync_cron.pl COPY ./imapsync_cron.pl /usr/local/bin/imapsync_cron.pl
COPY ./rspamd-pipe /usr/local/bin/rspamd-pipe COPY ./report-spam.sieve /usr/local/lib/dovecot/sieve/report-spam.sieve
COPY ./report-ham.sieve /usr/local/lib/dovecot/sieve/report-ham.sieve
COPY ./rspamd-pipe-ham /usr/local/lib/dovecot/sieve/rspamd-pipe-ham
COPY ./rspamd-pipe-spam /usr/local/lib/dovecot/sieve/rspamd-pipe-spam
COPY ./docker-entrypoint.sh / COPY ./docker-entrypoint.sh /
COPY ./supervisord.conf /etc/supervisor/supervisord.conf COPY ./supervisord.conf /etc/supervisor/supervisord.conf
RUN chmod +x /usr/local/bin/rspamd-pipe RUN chmod +x /usr/local/lib/dovecot/sieve/rspamd-pipe-ham \
RUN chmod +x /usr/local/bin/imapsync_cron.pl /usr/local/lib/dovecot/sieve/rspamd-pipe-spam \
/usr/local/bin/imapsync_cron.pl \
/usr/local/bin/postlogin.sh \
/usr/local/bin/imapsync
RUN groupadd -g 5000 vmail RUN groupadd -g 5000 vmail \
RUN useradd -g vmail -u 5000 vmail -d /var/vmail && groupadd -g 401 dovecot \
&& groupadd -g 402 dovenull \
&& useradd -g vmail -u 5000 vmail -d /var/vmail \
&& useradd -c "Dovecot unprivileged user" -d /dev/null -u 401 -g dovecot -s /bin/false dovecot \
&& useradd -c "Dovecot login user" -d /dev/null -u 402 -g dovenull -s /bin/false dovenull
EXPOSE 24 10001 EXPOSE 24 10001
ENTRYPOINT ["/docker-entrypoint.sh"] ENTRYPOINT ["/docker-entrypoint.sh"]
CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* RUN apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
/tmp/* \
/var/tmp/* \
/dovecot-2.2-pigeonhole-$PIGEONHOLE_VERSION \
/dovecot-$DOVECOT_VERSION

View File

@ -6,12 +6,16 @@ sed -i "/^\$DBUSER/c\\\$DBUSER='${DBUSER}';" /usr/local/bin/imapsync_cron.pl
sed -i "/^\$DBPASS/c\\\$DBPASS='${DBPASS}';" /usr/local/bin/imapsync_cron.pl sed -i "/^\$DBPASS/c\\\$DBPASS='${DBPASS}';" /usr/local/bin/imapsync_cron.pl
sed -i "/^\$DBNAME/c\\\$DBNAME='${DBNAME}';" /usr/local/bin/imapsync_cron.pl sed -i "/^\$DBNAME/c\\\$DBNAME='${DBNAME}';" /usr/local/bin/imapsync_cron.pl
[[ ! -d /etc/dovecot/sql/ ]] && mkdir -p /etc/dovecot/sql/ # Create missing directories
[[ ! -d /usr/local/etc/dovecot/sql/ ]] && mkdir -p /usr/local/etc/dovecot/sql/
[[ ! -d /var/vmail/sieve ]] && mkdir -p /var/vmail/sieve
[[ ! -d /etc/sogo ]] && mkdir -p /etc/sogo
# Set Dovecot sql config parameters, escape " in db password # Set Dovecot sql config parameters, escape " in db password
DBPASS=$(echo ${DBPASS} | sed 's/"/\\"/g') DBPASS=$(echo ${DBPASS} | sed 's/"/\\"/g')
cat <<EOF > /etc/dovecot/sql/dovecot-dict-sql.conf # Create quota dict for Dovecot
cat <<EOF > /usr/local/etc/dovecot/sql/dovecot-dict-sql.conf
connect = "host=mysql dbname=${DBNAME} user=${DBNAME} password=${DBPASS}" connect = "host=mysql dbname=${DBNAME} user=${DBNAME} password=${DBPASS}"
map { map {
pattern = priv/quota/storage pattern = priv/quota/storage
@ -27,7 +31,8 @@ map {
} }
EOF EOF
cat <<EOF > /etc/dovecot/sql/dovecot-mysql.conf # Create user and pass dict for Dovecot
cat <<EOF > /usr/local/etc/dovecot/sql/dovecot-mysql.conf
driver = mysql driver = mysql
connect = "host=mysql dbname=${DBNAME} user=${DBNAME} password=${DBPASS}" connect = "host=mysql dbname=${DBNAME} user=${DBNAME} password=${DBPASS}"
default_pass_scheme = SSHA256 default_pass_scheme = SSHA256
@ -36,19 +41,35 @@ user_query = SELECT CONCAT('maildir:/var/vmail/',maildir) AS mail, 5000 AS uid,
iterate_query = SELECT username FROM mailbox WHERE active='1'; iterate_query = SELECT username FROM mailbox WHERE active='1';
EOF EOF
[[ ! -d /var/vmail/sieve ]] && mkdir -p /var/vmail/sieve # Create global sieve_after script
[[ ! -d /etc/sogo ]] && mkdir -p /etc/sogo cat /usr/local/etc/dovecot/sieve_after > /var/vmail/sieve/global.sieve
cat /etc/dovecot/sieve_after > /var/vmail/sieve/global.sieve
sievec /var/vmail/sieve/global.sieve
chown -R vmail:vmail /var/vmail/sieve
# Check permissions of vmail directory.
# Do not do this every start-up, it may take a very long time. So we use a stat check here. # Do not do this every start-up, it may take a very long time. So we use a stat check here.
if [[ $(stat -c %U /var/vmail/) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail ; fi if [[ $(stat -c %U /var/vmail/) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail ; fi
# Create random master for SOGo sieve features # Create random master for SOGo sieve features
RAND_USER=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 16 | head -n 1) RAND_USER=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 16 | head -n 1)
RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 24 | head -n 1) RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 24 | head -n 1)
echo ${RAND_USER}:$(doveadm pw -s SHA1 -p ${RAND_PASS}) > /etc/dovecot/dovecot-master.passwd echo ${RAND_USER}:$(doveadm pw -s SHA1 -p ${RAND_PASS}) > /usr/local/etc/dovecot/dovecot-master.passwd
echo ${RAND_USER}:${RAND_PASS} > /etc/sogo/sieve.creds echo ${RAND_USER}:${RAND_PASS} > /etc/sogo/sieve.creds
# 401 is user dovecot
if [[ ! -f /mail_crypt/ecprivkey.pem || ! -f /mail_crypt/ecpubkey.pem ]]; then
openssl ecparam -name prime256v1 -genkey | openssl pkey -out /mail_crypt/ecprivkey.pem
openssl pkey -in /mail_crypt/ecprivkey.pem -pubout -out /mail_crypt/ecpubkey.pem
chown 401 /mail_crypt/ecprivkey.pem /mail_crypt/ecpubkey.pem
else
chown 401 /mail_crypt/ecprivkey.pem /mail_crypt/ecpubkey.pem
fi
# Compile sieve scripts
sievec /var/vmail/sieve/global.sieve
sievec /usr/local/lib/dovecot/sieve/report-spam.sieve
sievec /usr/local/lib/dovecot/sieve/report-ham.sieve
# Fix permissions
chown -R vmail:vmail /var/vmail/sieve
exec "$@" exec "$@"

View File

@ -21,7 +21,7 @@ open my $file, '<', "/etc/sogo/sieve.creds";
my $creds = <$file>; my $creds = <$file>;
close $file; close $file;
my ($master_user, $master_pass) = split /:/, $creds; my ($master_user, $master_pass) = split /:/, $creds;
my $sth = $dbh->prepare("SELECT id, user1, user2, host1, authmech1, password1, exclude, port1, enc1, delete2duplicates, maxage, subfolder2 FROM imapsync WHERE active = 1 AND (UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(last_run) > mins_interval * 60 OR last_run IS NULL)"); my $sth = $dbh->prepare("SELECT id, user1, user2, host1, authmech1, password1, exclude, port1, enc1, delete2duplicates, maxage, subfolder2, delete1 FROM imapsync WHERE active = 1 AND (UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(last_run) > mins_interval * 60 OR last_run IS NULL)");
$sth->execute(); $sth->execute();
my $row; my $row;
@ -39,6 +39,7 @@ while ($row = $sth->fetchrow_arrayref()) {
$delete2duplicates = @$row[9]; $delete2duplicates = @$row[9];
$maxage = @$row[10]; $maxage = @$row[10];
$subfolder2 = @$row[11]; $subfolder2 = @$row[11];
$delete1 = @$row[12];
if ($enc1 eq "TLS") { $enc1 = "--tls1"; } elsif ($enc1 eq "SSL") { $enc1 = "--ssl1"; } else { undef $enc1; } if ($enc1 eq "TLS") { $enc1 = "--tls1"; } elsif ($enc1 eq "SSL") { $enc1 = "--ssl1"; } else { undef $enc1; }
@ -46,11 +47,12 @@ while ($row = $sth->fetchrow_arrayref()) {
"--timeout1", "10", "--timeout1", "10",
"--tmpdir", "/tmp", "--tmpdir", "/tmp",
"--subscribeall", "--subscribeall",
($exclude eq "" ? () : ("--exclude", $exclude)), ($exclude eq "" ? () : ("--exclude", $exclude)),
($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)), ($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)),
($maxage eq "0" ? () : ('--maxage', $maxage)), ($maxage eq "0" ? () : ('--maxage', $maxage)),
($delete2duplicates ne "1" ? () : ('--delete2duplicates')), ($delete2duplicates ne "1" ? () : ('--delete2duplicates')),
(!defined($enc1) ? () : ($enc1)), ($delete1 ne "1" ? () : ('--delete')),
(!defined($enc1) ? () : ($enc1)),
"--host1", $host1, "--host1", $host1,
"--user1", $user1, "--user1", $user1,
"--password1", $password1, "--password1", $password1,

View File

@ -0,0 +1,11 @@
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
if environment :matches "imap.mailbox" "*" {
set "mailbox" "${1}";
}
if string "${mailbox}" "Trash" {
stop;
}
pipe :copy "rspamd-pipe-ham";

View File

@ -0,0 +1,3 @@
require ["vnd.dovecot.pipe", "copy"];
pipe :copy "rspamd-pipe-spam";

View File

@ -1,8 +0,0 @@
#!/bin/bash
if [[ ${2} == "learn_spam" ]]; then
/usr/bin/curl --data-binary @- http://rspamd:11334/learnspam < /dev/stdin
elif [[ ${2} == "learn_ham" ]]; then
/usr/bin/curl --data-binary @- http://rspamd:11334/learnham < /dev/stdin
fi
# Always return 0 to satisfy Dovecot...
exit 0

View File

@ -0,0 +1,4 @@
#!/bin/bash
/usr/bin/curl -s --data-binary @- http://rspamd:11334/learnham < /dev/stdin
# Always return 0 to satisfy Dovecot...
exit 0

View File

@ -0,0 +1,4 @@
#!/bin/bash
/usr/bin/curl -s --data-binary @- http://rspamd:11334/learnspam < /dev/stdin
# Always return 0 to satisfy Dovecot...
exit 0

View File

@ -8,7 +8,7 @@ autostart=true
stdout_syslog=true stdout_syslog=true
[program:dovecot] [program:dovecot]
command=/usr/sbin/dovecot -F command=/usr/local/sbin/dovecot -F
autorestart=true autorestart=true
[program:logfiles] [program:logfiles]

View File

@ -1,4 +1,4 @@
FROM ubuntu:xenial FROM debian:testing-slim
MAINTAINER Andre Peters <andre.peters@servercow.de> MAINTAINER Andre Peters <andre.peters@servercow.de>
ENV DEBIAN_FRONTEND noninteractive ENV DEBIAN_FRONTEND noninteractive
@ -19,10 +19,19 @@ RUN apt-get install -y --no-install-recommends supervisor \
postfix-pcre \ postfix-pcre \
syslog-ng \ syslog-ng \
syslog-ng-core \ syslog-ng-core \
ca-certificates ca-certificates \
gnupg \
python-gpgme \
sudo \
dirmngr
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 sed -i -E 's/^(\s*)system\(\);/\1unix-stream("\/dev\/log");/' /etc/syslog-ng/syslog-ng.conf RUN sed -i -E 's/^(\s*)system\(\);/\1unix-stream("\/dev\/log");/' /etc/syslog-ng/syslog-ng.conf
COPY zeyple.py /usr/local/bin/zeyple.py
COPY zeyple.conf /etc/zeyple.conf
COPY supervisord.conf /etc/supervisor/supervisord.conf COPY supervisord.conf /etc/supervisor/supervisord.conf
COPY postfix.sh /opt/postfix.sh COPY postfix.sh /opt/postfix.sh

View File

@ -17,7 +17,7 @@ user = ${DBUSER}
password = ${DBPASS} password = ${DBPASS}
hosts = mysql hosts = mysql
dbname = ${DBNAME} dbname = ${DBNAME}
query = SELECT IF( EXISTS( SELECT 'TLS_ACTIVE' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.address 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', 'DUNNO') AS 'tls_enforce_in'; 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 EOF
cat <<EOF > /opt/postfix/conf/sql/mysql_tls_enforce_out_policy.cf cat <<EOF > /opt/postfix/conf/sql/mysql_tls_enforce_out_policy.cf
@ -25,7 +25,7 @@ user = ${DBUSER}
password = ${DBPASS} password = ${DBPASS}
hosts = mysql hosts = mysql
dbname = ${DBNAME} dbname = ${DBNAME}
query = SELECT IF( EXISTS( SELECT 'TLS_ACTIVE' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.address 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:', 'DUNNO') AS 'tls_enforce_out'; 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';
EOF EOF
cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_domain_catchall_maps.cf cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_domain_catchall_maps.cf
@ -92,11 +92,24 @@ dbname = ${DBNAME}
query = SELECT goto FROM spamalias WHERE address='%s' AND validity >= UNIX_TIMESTAMP() query = SELECT goto FROM spamalias WHERE address='%s' AND validity >= UNIX_TIMESTAMP()
EOF EOF
# Reset GPG key permissions
mkdir -p /var/lib/zeyple/keys
chmod 700 /var/lib/zeyple/keys
chown -R 600:600 /var/lib/zeyple/keys
# Fix Postfix permissions
chgrp -R postdrop /var/spool/postfix/public
chgrp -R postdrop /var/spool/postfix/maildrop
postfix set-permissions
# Check Postfix configuration
postconf -c /opt/postfix/conf postconf -c /opt/postfix/conf
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
echo "Postfix configuration error, refusing to start." echo "Postfix configuration error, refusing to start."
exit 1 exit 1
else else
postfix -c /opt/postfix/conf start postfix -c /opt/postfix/conf start
supervisorctl restart postfix-maillog
sleep 126144000 sleep 126144000
fi fi

View File

@ -12,6 +12,17 @@ command=/opt/postfix.sh
autorestart=true autorestart=true
[program:postfix-maillog] [program:postfix-maillog]
command=/usr/bin/tail -f /var/log/mail.log command=/bin/tail -f /var/log/zeyple.log /var/log/mail.log
stdout_logfile=/dev/fd/1 stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0 stdout_logfile_maxbytes=0
[unix_http_server]
file=/var/tmp/supervisord.sock
chmod=0770
chown=nobody:nogroup
[supervisorctl]
serverurl=unix:///var/tmp/supervisord.sock
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

View File

@ -0,0 +1,9 @@
[zeyple]
log_file = /var/log/zeyple.log
[gpg]
home = /var/lib/zeyple/keys
[relay]
host = localhost
port = 10026

View File

@ -0,0 +1,274 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import os
import logging
import email
import email.mime.multipart
import email.mime.application
import email.encoders
import smtplib
import copy
from io import BytesIO
try:
from configparser import SafeConfigParser # Python 3
except ImportError:
from ConfigParser import SafeConfigParser # Python 2
import gpgme
# Boiler plate to avoid dependency on six
# BBB: Python 2.7 support
PY3K = sys.version_info > (3, 0)
def message_from_binary(message):
if PY3K:
return email.message_from_bytes(message)
else:
return email.message_from_string(message)
def as_binary_string(email):
if PY3K:
return email.as_bytes()
else:
return email.as_string()
def encode_string(string):
if isinstance(string, bytes):
return string
else:
return string.encode('utf-8')
__title__ = 'Zeyple'
__version__ = '1.2.0'
__author__ = 'Cédric Félizard'
__license__ = 'AGPLv3+'
__copyright__ = 'Copyright 2012-2016 Cédric Félizard'
class Zeyple:
"""Zeyple Encrypts Your Precious Log Emails"""
def __init__(self, config_fname='zeyple.conf'):
self.config = self.load_configuration(config_fname)
log_file = self.config.get('zeyple', 'log_file')
logging.basicConfig(
filename=log_file, level=logging.DEBUG,
format='%(asctime)s %(process)s %(levelname)s %(message)s'
)
logging.info("Zeyple ready to encrypt outgoing emails")
def load_configuration(self, filename):
"""Reads and parses the config file"""
config = SafeConfigParser()
config.read([
os.path.join('/etc/', filename),
filename,
])
if not config.sections():
raise IOError('Cannot open config file.')
return config
@property
def gpg(self):
protocol = gpgme.PROTOCOL_OpenPGP
if self.config.has_option('gpg', 'executable'):
executable = self.config.get('gpg', 'executable')
else:
executable = None # Default value
home_dir = self.config.get('gpg', 'home')
ctx = gpgme.Context()
ctx.set_engine_info(protocol, executable, home_dir)
ctx.armor = True
return ctx
def process_message(self, message_data, recipients):
"""Encrypts the message with recipient keys"""
message_data = encode_string(message_data)
in_message = message_from_binary(message_data)
logging.info(
"Processing outgoing message %s", in_message['Message-id'])
if not recipients:
logging.warn("Cannot find any recipients, ignoring")
sent_messages = []
for recipient in recipients:
logging.info("Recipient: %s", recipient)
key_id = self._user_key(recipient)
logging.info("Key ID: %s", key_id)
if key_id:
out_message = self._encrypt_message(in_message, key_id)
# Delete Content-Transfer-Encoding if present to default to
# "7bit" otherwise Thunderbird seems to hang in some cases.
del out_message["Content-Transfer-Encoding"]
else:
logging.warn("No keys found, message will be sent unencrypted")
out_message = copy.copy(in_message)
self._add_zeyple_header(out_message)
self._send_message(out_message, recipient)
sent_messages.append(out_message)
return sent_messages
def _get_version_part(self):
ret = email.mime.application.MIMEApplication(
'Version: 1\n',
'pgp-encrypted',
email.encoders.encode_noop,
)
ret.add_header(
'Content-Description',
"PGP/MIME version identification",
)
return ret
def _get_encrypted_part(self, payload):
ret = email.mime.application.MIMEApplication(
payload,
'octet-stream',
email.encoders.encode_noop,
name="encrypted.asc",
)
ret.add_header('Content-Description', "OpenPGP encrypted message")
ret.add_header(
'Content-Disposition',
'inline',
filename='encrypted.asc',
)
return ret
def _encrypt_message(self, in_message, key_id):
if in_message.is_multipart():
# get the body (after the first \n\n)
payload = in_message.as_string().split("\n\n", 1)[1].strip()
# prepend the Content-Type including the boundary
content_type = "Content-Type: " + in_message["Content-Type"]
payload = content_type + "\n\n" + payload
message = email.message.Message()
message.set_payload(payload)
payload = message.get_payload()
else:
payload = in_message.get_payload()
payload = encode_string(payload)
quoted_printable = email.charset.Charset('ascii')
quoted_printable.body_encoding = email.charset.QP
message = email.mime.nonmultipart.MIMENonMultipart(
'text', 'plain', charset='utf-8'
)
message.set_payload(payload, charset=quoted_printable)
mixed = email.mime.multipart.MIMEMultipart(
'mixed',
None,
[message],
)
# remove superfluous header
del mixed['MIME-Version']
payload = as_binary_string(mixed)
encrypted_payload = self._encrypt_payload(payload, [key_id])
version = self._get_version_part()
encrypted = self._get_encrypted_part(encrypted_payload)
out_message = copy.copy(in_message)
out_message.preamble = "This is an OpenPGP/MIME encrypted " \
"message (RFC 4880 and 3156)"
if 'Content-Type' not in out_message:
out_message['Content-Type'] = 'multipart/encrypted'
else:
out_message.replace_header(
'Content-Type',
'multipart/encrypted',
)
out_message.set_param('protocol', 'application/pgp-encrypted')
out_message.set_payload([version, encrypted])
return out_message
def _encrypt_payload(self, payload, key_ids):
"""Encrypts the payload with the given keys"""
payload = encode_string(payload)
plaintext = BytesIO(payload)
ciphertext = BytesIO()
self.gpg.armor = True
recipient = [self.gpg.get_key(key_id) for key_id in key_ids]
self.gpg.encrypt(recipient, gpgme.ENCRYPT_ALWAYS_TRUST,
plaintext, ciphertext)
return ciphertext.getvalue()
def _user_key(self, email):
"""Returns the GPG key for the given email address"""
logging.info("Trying to encrypt for %s", email)
keys = [key for key in self.gpg.keylist(email)]
if keys:
key = keys.pop() # NOTE: looks like keys[0] is the master key
key_id = key.subkeys[0].keyid
return key_id
return None
def _add_zeyple_header(self, message):
if self.config.has_option('zeyple', 'add_header') and \
self.config.getboolean('zeyple', 'add_header'):
message.add_header(
'X-Zeyple',
"processed by {0} v{1}".format(__title__, __version__)
)
def _send_message(self, message, recipient):
"""Sends the given message through the SMTP relay"""
logging.info("Sending message %s", message['Message-id'])
smtp = smtplib.SMTP(self.config.get('relay', 'host'),
self.config.get('relay', 'port'))
smtp.sendmail(message['From'], recipient, message.as_string())
smtp.quit()
logging.info("Message %s sent", message['Message-id'])
if __name__ == '__main__':
recipients = sys.argv[1:]
# BBB: Python 2.7 support
binary_stdin = sys.stdin.buffer if PY3K else sys.stdin
message = binary_stdin.read()
zeyple = Zeyple()
zeyple.process_message(message, recipients)

View File

@ -1,16 +1,11 @@
FROM ubuntu:xenial FROM debian:jessie-slim
MAINTAINER Andre Peters <andre.peters@servercow.de> MAINTAINER Andre Peters <andre.peters@servercow.de>
ENV DEBIAN_FRONTEND noninteractive ENV DEBIAN_FRONTEND noninteractive
ENV LC_ALL C ENV LC_ALL C
RUN dpkg-divert --local --rename --add /sbin/initctl \
&& ln -sf /bin/true /sbin/initctl \
&& dpkg-divert --local --rename --add /usr/bin/ischroot \
&& ln -sf /bin/true /usr/bin/ischroot
RUN apt-key adv --fetch-keys http://rspamd.com/apt-stable/gpg.key \ RUN apt-key adv --fetch-keys http://rspamd.com/apt-stable/gpg.key \
&& echo "deb http://rspamd.com/apt-stable/ xenial main" > /etc/apt/sources.list.d/rspamd.list \ && echo "deb http://rspamd.com/apt-stable/ jessie main" > /etc/apt/sources.list.d/rspamd.list \
&& apt-get update \ && apt-get update \
&& apt-get --no-install-recommends -y --force-yes install rmilter cron syslog-ng syslog-ng-core supervisor && apt-get --no-install-recommends -y --force-yes install rmilter cron syslog-ng syslog-ng-core supervisor

View File

@ -1,16 +1,11 @@
FROM ubuntu:xenial FROM debian:jessie-slim
MAINTAINER Andre Peters <andre.peters@servercow.de> MAINTAINER Andre Peters <andre.peters@servercow.de>
ENV DEBIAN_FRONTEND noninteractive ENV DEBIAN_FRONTEND noninteractive
ENV LC_ALL C ENV LC_ALL C
RUN dpkg-divert --local --rename --add /sbin/initctl \
&& ln -sf /bin/true /sbin/initctl \
&& dpkg-divert --local --rename --add /usr/bin/ischroot \
&& ln -sf /bin/true /usr/bin/ischroot
RUN apt-key adv --fetch-keys http://rspamd.com/apt-stable/gpg.key \ RUN apt-key adv --fetch-keys http://rspamd.com/apt-stable/gpg.key \
&& echo "deb http://rspamd.com/apt-stable/ xenial main" > /etc/apt/sources.list.d/rspamd.list \ && echo "deb http://rspamd.com/apt-stable/ jessie main" > /etc/apt/sources.list.d/rspamd.list \
&& apt-get update \ && apt-get update \
&& apt-get -y install rspamd ca-certificates python-pip && apt-get -y install rspamd ca-certificates python-pip

View File

@ -1,17 +1,12 @@
FROM ubuntu:xenial FROM debian:jessie-slim
MAINTAINER Andre Peters <andre.peters@servercow.de> MAINTAINER Andre Peters <andre.peters@servercow.de>
ENV DEBIAN_FRONTEND noninteractive ENV DEBIAN_FRONTEND noninteractive
ENV LC_ALL C ENV LC_ALL C
ENV GOSU_VERSION 1.9 ENV GOSU_VERSION 1.9
RUN dpkg-divert --local --rename --add /sbin/initctl \
&& ln -sf /bin/true /sbin/initctl \
&& dpkg-divert --local --rename --add /usr/bin/ischroot \
&& ln -sf /bin/true /usr/bin/ischroot
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends apt-transport-https \ && apt-get install -y --no-install-recommends apt-transport-https gnupg \
ca-certificates \ ca-certificates \
wget \ wget \
syslog-ng \ syslog-ng \
@ -29,8 +24,11 @@ RUN apt-get update \
&& chmod +x /usr/local/bin/gosu \ && chmod +x /usr/local/bin/gosu \
&& gosu nobody true && gosu nobody true
RUN mkdir /usr/share/doc/sogo
RUN touch /usr/share/doc/sogo/empty.sh
RUN apt-key adv --keyserver keys.gnupg.net --recv-key 0x810273C4 \ RUN apt-key adv --keyserver keys.gnupg.net --recv-key 0x810273C4 \
&& echo "deb http://packages.inverse.ca/SOGo/nightly/3/ubuntu/ xenial xenial" > /etc/apt/sources.list.d/sogo.list \ && echo "deb http://packages.inverse.ca/SOGo/nightly/3/debian/ jessie jessie" > /etc/apt/sources.list.d/sogo.list \
&& apt-get update \ && apt-get update \
&& apt-get -y --force-yes install sogo sogo-activesync && apt-get -y --force-yes install sogo sogo-activesync
@ -42,10 +40,6 @@ RUN echo '0 0 * * * sogo /usr/sbin/sogo-tool update-autoreply -p /etc/sogo/s
COPY ./reconf-domains.sh / COPY ./reconf-domains.sh /
COPY supervisord.conf /etc/supervisor/supervisord.conf COPY supervisord.conf /etc/supervisor/supervisord.conf
#EXPOSE 20000
#EXPOSE 9191
#EXPOSE 9192
CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

View File

@ -10,9 +10,8 @@ disable_plaintext_auth = yes
login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k" login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k"
mail_home = /var/vmail/%d/%n mail_home = /var/vmail/%d/%n
mail_location = maildir:~/ mail_location = maildir:~/
mail_plugins = quota acl zlib antispam mail_plugins = quota acl zlib #mail_crypt
auth_username_chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@ ssl_protocols = !SSLv3
ssl_protocols = !SSLv3 !SSLv2
ssl_prefer_server_ciphers = yes ssl_prefer_server_ciphers = yes
ssl_cipher_list = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA ssl_cipher_list = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA
ssl_options = no_compression ssl_options = no_compression
@ -24,12 +23,12 @@ auth_master_user_separator = *
mail_prefetch_count = 30 mail_prefetch_count = 30
passdb { passdb {
driver = passwd-file driver = passwd-file
args = /etc/dovecot/dovecot-master.passwd args = /usr/local/etc/dovecot/dovecot-master.passwd
master = yes master = yes
pass = yes pass = yes
} }
passdb { passdb {
args = /etc/dovecot/sql/dovecot-mysql.conf args = /usr/local/etc/dovecot/sql/dovecot-mysql.conf
driver = sql driver = sql
} }
namespace inbox { namespace inbox {
@ -202,15 +201,15 @@ listen = *,[::]
ssl_cert = </etc/ssl/mail/cert.pem ssl_cert = </etc/ssl/mail/cert.pem
ssl_key = </etc/ssl/mail/key.pem ssl_key = </etc/ssl/mail/key.pem
userdb { userdb {
args = /etc/dovecot/sql/dovecot-mysql.conf args = /usr/local/etc/dovecot/sql/dovecot-mysql.conf
driver = sql driver = sql
} }
protocol imap { protocol imap {
mail_plugins = quota imap_quota imap_acl acl zlib imap_zlib antispam mail_plugins = quota imap_quota imap_acl acl zlib imap_zlib imap_sieve #mail_crypt
} }
protocol lmtp { protocol lmtp {
mail_plugins = quota sieve acl zlib mail_plugins = quota sieve acl zlib #mail_crypt
auth_socket_path = /var/run/dovecot/auth-master auth_socket_path = /usr/local/var/run/dovecot/auth-master
} }
protocol sieve { protocol sieve {
managesieve_logout_format = bytes=%i/%o managesieve_logout_format = bytes=%i/%o
@ -221,22 +220,31 @@ plugin {
acl = vfile acl = vfile
quota = dict:Userquota::proxy::sqlquota quota = dict:Userquota::proxy::sqlquota
quota_rule2 = Trash:storage=+100%% quota_rule2 = Trash:storage=+100%%
antispam_backend = mailtrain
antispam_spam = Junk
antispam_trash = Trash
antispam_mail_sendmail = /usr/local/bin/rspamd-pipe
antispam_mail_spam = learn_spam
antispam_mail_notspam = learn_ham
# Do not complain about empty parameter
antispam_mail_sendmail_args = --blind
sieve = /var/vmail/sieve/%u.sieve sieve = /var/vmail/sieve/%u.sieve
sieve_plugins = sieve_imapsieve sieve_extprograms
# From elsewhere to Spam folder
imapsieve_mailbox1_name = Junk
imapsieve_mailbox1_causes = COPY
imapsieve_mailbox1_before = file:/usr/local/lib/dovecot/sieve/report-spam.sieve
# END
# From Spam folder to elsewhere
imapsieve_mailbox2_name = *
imapsieve_mailbox2_from = Junk
imapsieve_mailbox2_causes = COPY
imapsieve_mailbox2_before = file:/usr/local/lib/dovecot/sieve/report-ham.sieve
# END
sieve_pipe_bin_dir = /usr/local/lib/dovecot/sieve
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute
sieve_after = /var/vmail/sieve/global.sieve sieve_after = /var/vmail/sieve/global.sieve
sieve_max_script_size = 1M sieve_max_script_size = 1M
sieve_quota_max_scripts = 0 sieve_quota_max_scripts = 0
sieve_quota_max_storage = 0 sieve_quota_max_storage = 0
#mail_crypt_global_private_key = </mail_crypt/ecprivkey.pem
#mail_crypt_global_public_key = </mail_crypt/ecpubkey.pem
#mail_crypt_save_version = 2
} }
dict { dict {
sqlquota = mysql:/etc/dovecot/sql/dovecot-dict-sql.conf sqlquota = mysql:/usr/local/etc/dovecot/sql/dovecot-dict-sql.conf
} }
remote 127.0.0.1 { remote 127.0.0.1 {
disable_plaintext_auth = no disable_plaintext_auth = no

View File

@ -18,6 +18,11 @@ server {
access_log /var/log/nginx/access.log; access_log /var/log/nginx/access.log;
root /web; root /web;
location /api/v1/ {
try_files $uri $uri/ /json_api.php?$args;
}
rewrite ^/api/v1/([^/]+)/([^/]+)/?$ /json_api.php?action=$1&object=$2? last;
location ^~ /.well-known/acme-challenge/ { location ^~ /.well-known/acme-challenge/ {
allow all; allow all;
default_type "text/plain"; default_type "text/plain";
@ -166,6 +171,11 @@ server {
access_log /var/log/nginx/access.log; access_log /var/log/nginx/access.log;
root /web; root /web;
location /api/v1/ {
try_files $uri $uri/ /json_api.php?$args;
}
rewrite ^/api/v1/([^/]+)/([^/]+)/?$ /json_api.php?action=$1&object=$2? last;
location ^~ /.well-known/acme-challenge/ { location ^~ /.well-known/acme-challenge/ {
allow all; allow all;
default_type "text/plain"; default_type "text/plain";

View File

@ -91,3 +91,4 @@ smtpd_milters = inet:rmilter:9900
non_smtpd_milters = inet:rmilter:9900 non_smtpd_milters = inet:rmilter:9900
milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen} milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
mydestination = localhost.localdomain, localhost mydestination = localhost.localdomain, localhost
#content_filter=zeyple

View File

@ -16,6 +16,7 @@ smtp_enforced_tls unix - - n - - smtp
-o smtp_tls_security_level=encrypt -o smtp_tls_security_level=encrypt
-o syslog_name=enforced-tls-smtp -o syslog_name=enforced-tls-smtp
-o smtp_delivery_status_filter=pcre:/opt/postfix/conf/smtp_dsn_filter -o smtp_delivery_status_filter=pcre:/opt/postfix/conf/smtp_dsn_filter
tlsproxy unix - - n - 0 tlsproxy tlsproxy unix - - n - 0 tlsproxy
dnsblog unix - - n - 0 dnsblog dnsblog unix - - n - 0 dnsblog
pickup fifo n - n 60 1 pickup pickup fifo n - n 60 1 pickup
@ -43,3 +44,14 @@ anvil unix - - n - 1 anvil
scache unix - - n - 1 scache scache unix - - n - 1 scache
maildrop unix - n n - - pipe flags=DRhu maildrop unix - n n - - pipe flags=DRhu
user=vmail argv=/usr/bin/maildrop -d ${recipient} user=vmail argv=/usr/bin/maildrop -d ${recipient}
zeyple unix - n n - - pipe
user=zeyple argv=/usr/local/bin/zeyple.py ${recipient}
127.0.0.1:10026 inet n - n - 10 smtpd
-o content_filter=
-o receive_override_options=no_unknown_recipient_checks,no_header_body_checks,no_milters
-o smtpd_helo_restrictions=
-o smtpd_client_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o mynetworks=127.0.0.0/8
-o smtpd_authorized_xforward_hosts=127.0.0.0/8

View File

@ -350,6 +350,13 @@ elseif (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] ==
</div> </div>
</div> </div>
</div> </div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label><input type="checkbox" name="delete1" checked> <?=$lang['add']['delete1'];?></label>
</div>
</div>
</div>
<div class="form-group"> <div class="form-group">
<div class="col-sm-offset-2 col-sm-10"> <div class="col-sm-offset-2 col-sm-10">
<div class="checkbox"> <div class="checkbox">

View File

@ -81,14 +81,14 @@ $tfa_data = get_tfa();
<div class="panel-body"> <div class="panel-body">
<form method="post"> <form method="post">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped sortable-theme-bootstrap" data-sortable id="domainadminstable"> <table class="table table-striped" id="domainadminstable">
<thead> <thead>
<tr> <tr>
<th class="sort-table" style="min-width: 100px;"><?=$lang['admin']['username'];?></th> <th style="min-width: 100px;"><?=$lang['admin']['username'];?></th>
<th class="sort-table" style="min-width: 166px;"><?=$lang['admin']['admin_domains'];?></th> <th style="min-width: 166px;"><?=$lang['admin']['admin_domains'];?></th>
<th class="sort-table" style="min-width: 76px;"><?=$lang['admin']['active'];?></th> <th style="min-width: 76px;"><?=$lang['admin']['active'];?></th>
<th class="sort-table" style="min-width: 76px;"><?=$lang['tfa']['tfa'];?></th> <th style="min-width: 76px;"><?=$lang['tfa']['tfa'];?></th>
<th style="text-align: right; min-width: 200px;" data-sortable="false"><?=$lang['admin']['action'];?></th> <th style="text-align: right; min-width: 200px;"><?=$lang['admin']['action'];?></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -299,8 +299,14 @@ $tfa_data = get_tfa();
</div> </div>
</div> </div>
</div> <!-- /container --> </div> <!-- /container -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js" integrity="sha384-YWP9O4NjmcGo4oEJFXvvYSEzuHIvey+LbXkBNJ1Kd0yfugEZN9NCQNpRYBVC1RvA" crossorigin="anonymous"></script> <script type='text/javascript'>
<script src="js/sorttable.js"></script> <?php
$lang_admin = json_encode($lang['admin']);
echo "var lang = ". $lang_admin . ";\n";
echo "var pagination_size = '". $PAGINATION_SIZE . "';\n";
?>
</script>
<script src="js/footable.min.js"></script>
<script src="js/admin.js"></script> <script src="js/admin.js"></script>
<?php <?php
require_once("inc/footer.inc.php"); require_once("inc/footer.inc.php");

13
data/web/css/admin.css Normal file
View File

@ -0,0 +1,13 @@
table.footable>tbody>tr.footable-empty>td {
font-size:15px !important;
font-style:italic;
}
.pagination a {
text-decoration: none !important;
}
.panel panel-default {
overflow: visible !important;
}
.table-responsive {
overflow: visible !important;
}

View File

@ -1,19 +1,19 @@
.panel-heading div { table.footable>tbody>tr.footable-empty>td {
margin-top: -18px; font-size:15px !important;
font-size: 15px; font-style:italic;
} }
.panel-heading div span { .pagination a {
margin-left:5px; text-decoration: none !important;
} }
.panel-body { .panel panel-default {
display: none; overflow: visible !important;
} }
.clickable { .table-responsive {
cursor: pointer; overflow: visible !important;
} }
.progress { .footer-add-item {
margin-bottom: 0px; text-align:center;
} font-style: italic;
.table>thead>tr>th { display:block;
vertical-align: top !important; padding: 10px;
} }

View File

@ -1,79 +0,0 @@
ul[id*="sortable"] { word-wrap: break-word; list-style-type: none; float: left; padding: 0 15px 0 0; width: 48%; cursor:move}
ul[id$="sortable-active"] li {cursor:move; }
ul[id$="sortable-inactive"] li {cursor:move }
.list-heading { cursor:default !important}
.ui-state-disabled { cursor:no-drop; color:#ccc; }
.ui-state-highlight {background: #F5F5F5 !important; height: 41px !important; cursor:move }
table[data-sortable] {
border-collapse: collapse;
border-spacing: 0;
}
table[data-sortable] th {
vertical-align: bottom;
font-weight: bold;
}
table[data-sortable] th, table[data-sortable] td {
text-align: left;
padding: 10px;
}
table[data-sortable] th:not([data-sortable="false"]) {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-touch-callout: none;
cursor: pointer;
}
table[data-sortable] th:after {
content: "";
visibility: hidden;
display: inline-block;
vertical-align: inherit;
height: 0;
width: 0;
border-width: 5px;
border-style: solid;
border-color: transparent;
margin-right: 1px;
margin-left: 10px;
float: right;
}
table[data-sortable] th[data-sortable="false"]:after {
display: none;
}
table[data-sortable] th[data-sorted="true"]:after {
visibility: visible;
}
table[data-sortable] th[data-sorted-direction="descending"]:after {
border-top-color: inherit;
margin-top: 8px;
}
table[data-sortable] th[data-sorted-direction="ascending"]:after {
border-bottom-color: inherit;
margin-top: 3px;
}
table[data-sortable].sortable-theme-bootstrap thead th {
border-bottom: 2px solid #e0e0e0;
}
table[data-sortable].sortable-theme-bootstrap th[data-sorted="true"] {
color: #3a87ad;
background: #d9edf7;
border-bottom-color: #bce8f1;
}
table[data-sortable].sortable-theme-bootstrap th[data-sorted="true"][data-sorted-direction="descending"]:after {
border-top-color: #3a87ad;
}
table[data-sortable].sortable-theme-bootstrap th[data-sorted="true"][data-sorted-direction="ascending"]:after {
border-bottom-color: #3a87ad;
}
table[data-sortable].sortable-theme-bootstrap.sortable-theme-bootstrap-striped tbody > tr:nth-child(odd) > td {
background-color: #f9f9f9;
}
#data td, #no-data td {
vertical-align: middle;
}
.sort-table:hover {
border-bottom-color: #00B7DC !important;
}

View File

@ -620,6 +620,20 @@ elseif (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] ==
<input type="text" class="form-control" name="exclude" id="exclude" value="<?=htmlspecialchars($result['exclude'], ENT_QUOTES, 'UTF-8');?>"> <input type="text" class="form-control" name="exclude" id="exclude" value="<?=htmlspecialchars($result['exclude'], ENT_QUOTES, 'UTF-8');?>">
</div> </div>
</div> </div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label><input type="checkbox" name="delete2duplicates" <?=($result['delete2duplicates']=="1") ? "checked" : "";?>> <?=$lang['edit']['delete2duplicates'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label><input type="checkbox" name="delete1" <?=($result['delete1']=="1") ? "checked" : "";?>> <?=$lang['edit']['delete1'];?></label>
</div>
</div>
</div>
<div class="form-group"> <div class="form-group">
<div class="col-sm-offset-2 col-sm-10"> <div class="col-sm-offset-2 col-sm-10">
<div class="checkbox"> <div class="checkbox">

View File

@ -50,11 +50,7 @@ $(document).ready(function() {
type: "GET", type: "GET",
cache: false, cache: false,
dataType: 'script', dataType: 'script',
url: "json_api.php", url: "/api/v1/u2f-authentication/<?=(isset($_SESSION['pending_mailcow_cc_username'])) ? $_SESSION['pending_mailcow_cc_username'] : null;?>",
data: {
'action':'get_u2f_auth_challenge',
'object':'<?=(isset($_SESSION['pending_mailcow_cc_username'])) ? $_SESSION['pending_mailcow_cc_username'] : null;?>',
},
success: function(data){ success: function(data){
data; data;
} }
@ -87,11 +83,7 @@ $(document).ready(function() {
type: "GET", type: "GET",
cache: false, cache: false,
dataType: 'script', dataType: 'script',
url: "json_api.php", url: "/api/v1/u2f-registration/<?=(isset($_SESSION['mailcow_cc_username'])) ? $_SESSION['mailcow_cc_username'] : null;?>",
data: {
'action':'get_u2f_reg_challenge',
'object':'<?=(isset($_SESSION['mailcow_cc_username'])) ? $_SESSION['mailcow_cc_username'] : null;?>',
},
success: function(data){ success: function(data){
data; data;
} }

View File

@ -109,6 +109,11 @@ function init_db_schema() {
if ($num_results == 0) { if ($num_results == 0) {
$pdo->query("ALTER TABLE `mailbox` ADD `multiple_bookings` tinyint(1) NOT NULL DEFAULT '0'"); $pdo->query("ALTER TABLE `mailbox` ADD `multiple_bookings` tinyint(1) NOT NULL DEFAULT '0'");
} }
$stmt = $pdo->query("SHOW COLUMNS FROM `imapsync` LIKE 'delete1'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results == 0) {
$pdo->query("ALTER TABLE `imapsync` ADD `delete1` tinyint(1) NOT NULL DEFAULT '0'");
}
$stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE 'wants_tagged_subject'"); $stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE 'wants_tagged_subject'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results == 0) { if ($num_results == 0) {
@ -1075,6 +1080,7 @@ function add_syncjob($postarray) {
} }
isset($postarray['active']) ? $active = '1' : $active = '0'; isset($postarray['active']) ? $active = '1' : $active = '0';
isset($postarray['delete2duplicates']) ? $delete2duplicates = '1' : $delete2duplicates = '0'; isset($postarray['delete2duplicates']) ? $delete2duplicates = '1' : $delete2duplicates = '0';
isset($postarray['delete1']) ? $delete1 = '1' : $delete1 = '0';
$port1 = $postarray['port1']; $port1 = $postarray['port1'];
$host1 = $postarray['host1']; $host1 = $postarray['host1'];
$password1 = $postarray['password1']; $password1 = $postarray['password1'];
@ -1147,12 +1153,13 @@ function add_syncjob($postarray) {
return false; return false;
} }
try { try {
$stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `maxage`, `subfolder2`, `host1`, `authmech1`, `user1`, `password1`, `mins_interval`, `port1`, `enc1`, `delete2duplicates`, `active`) $stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `delete1`, `maxage`, `subfolder2`, `host1`, `authmech1`, `user1`, `password1`, `mins_interval`, `port1`, `enc1`, `delete2duplicates`, `active`)
VALUES (:user2, :exclude, :maxage, :subfolder2, :host1, :authmech1, :user1, :password1, :mins_interval, :port1, :enc1, :delete2duplicates, :active)"); VALUES (:user2, :exclude, :maxage, :delete1, :subfolder2, :host1, :authmech1, :user1, :password1, :mins_interval, :port1, :enc1, :delete2duplicates, :active)");
$stmt->execute(array( $stmt->execute(array(
':user2' => $username, ':user2' => $username,
':exclude' => $exclude, ':exclude' => $exclude,
':maxage' => $maxage, ':maxage' => $maxage,
':delete1' => $delete1,
':subfolder2' => $subfolder2, ':subfolder2' => $subfolder2,
':host1' => $host1, ':host1' => $host1,
':authmech1' => 'PLAIN', ':authmech1' => 'PLAIN',
@ -1200,6 +1207,7 @@ function edit_syncjob($postarray) {
} }
isset($postarray['active']) ? $active = '1' : $active = '0'; isset($postarray['active']) ? $active = '1' : $active = '0';
isset($postarray['delete2duplicates']) ? $delete2duplicates = '1' : $delete2duplicates = '0'; isset($postarray['delete2duplicates']) ? $delete2duplicates = '1' : $delete2duplicates = '0';
isset($postarray['delete1']) ? $delete1 = '1' : $delete1 = '0';
$id = $postarray['id']; $id = $postarray['id'];
$port1 = $postarray['port1']; $port1 = $postarray['port1'];
$host1 = $postarray['host1']; $host1 = $postarray['host1'];
@ -1273,10 +1281,11 @@ function edit_syncjob($postarray) {
return false; return false;
} }
try { try {
$stmt = $pdo->prepare("UPDATE `imapsync` set `maxage` = :maxage, `subfolder2` = :subfolder2, `exclude` = :exclude, `host1` = :host1, `user1` = :user1, `password1` = :password1, `mins_interval` = :mins_interval, `port1` = :port1, `enc1` = :enc1, `delete2duplicates` = :delete2duplicates, `active` = :active $stmt = $pdo->prepare("UPDATE `imapsync` set `delete1` = :delete1, `maxage` = :maxage, `subfolder2` = :subfolder2, `exclude` = :exclude, `host1` = :host1, `user1` = :user1, `password1` = :password1, `mins_interval` = :mins_interval, `port1` = :port1, `enc1` = :enc1, `delete2duplicates` = :delete2duplicates, `active` = :active
WHERE `user2` = :user2 AND `id` = :id"); WHERE `user2` = :user2 AND `id` = :id");
$stmt->execute(array( $stmt->execute(array(
':user2' => $username, ':user2' => $username,
':delete1' => $delete1,
':id' => $id, ':id' => $id,
':exclude' => $exclude, ':exclude' => $exclude,
':maxage' => $maxage, ':maxage' => $maxage,
@ -1757,6 +1766,7 @@ function get_domain_admin_details($domain_admin) {
try { try {
$stmt = $pdo->prepare("SELECT $stmt = $pdo->prepare("SELECT
`tfa`.`active` AS `tfa_active_int`, `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`.`username`,
`domain_admins`.`created`, `domain_admins`.`created`,
`domain_admins`.`active` AS `active_int`, `domain_admins`.`active` AS `active_int`,
@ -1768,11 +1778,15 @@ function get_domain_admin_details($domain_admin) {
':domain_admin' => $domain_admin ':domain_admin' => $domain_admin
)); ));
$row = $stmt->fetch(PDO::FETCH_ASSOC); $row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)) {
return false;
}
$domainadmindata['username'] = $row['username']; $domainadmindata['username'] = $row['username'];
$domainadmindata['tfa_active'] = $row['tfa_active'];
$domainadmindata['active'] = $row['active']; $domainadmindata['active'] = $row['active'];
$domainadmindata['active_int'] = $row['active_int'];
$domainadmindata['tfa_active_int'] = $row['tfa_active_int']; $domainadmindata['tfa_active_int'] = $row['tfa_active_int'];
$domainadmindata['created'] = $row['created']; $domainadmindata['active_int'] = $row['active_int'];
$domainadmindata['modified'] = $row['created'];
// GET SELECTED // GET SELECTED
$stmt = $pdo->prepare("SELECT `domain` FROM `domain` $stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE `domain` IN ( WHERE `domain` IN (
@ -1793,6 +1807,9 @@ function get_domain_admin_details($domain_admin) {
while($row = array_shift($rows)) { while($row = array_shift($rows)) {
$domainadmindata['unselected_domains'][] = $row['domain']; $domainadmindata['unselected_domains'][] = $row['domain'];
} }
if (!isset($domainadmindata['unselected_domains'])) {
$domainadmindata['unselected_domains'] = "";
}
} }
catch(PDOException $e) { catch(PDOException $e) {
$_SESSION['return'] = array( $_SESSION['return'] = array(
@ -2134,6 +2151,14 @@ function edit_domain_admin($postarray) {
} }
} }
if (empty($postarray['domain'])) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['domain_invalid'])
);
return false;
}
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) { if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
$_SESSION['return'] = array( $_SESSION['return'] = array(
'type' => 'danger', 'type' => 'danger',
@ -2164,7 +2189,7 @@ function edit_domain_admin($postarray) {
return false; return false;
} }
if(isset($postarray['domain'])) { if (isset($postarray['domain'])) {
foreach ($postarray['domain'] as $domain) { foreach ($postarray['domain'] as $domain) {
try { try {
$stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
@ -2519,6 +2544,14 @@ function mailbox_add_domain($postarray) {
return false; return false;
} }
if ($maxquota == "0" || empty($maxquota)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['maxquota_empty'])
);
return false;
}
isset($postarray['active']) ? $active = '1' : $active = '0'; isset($postarray['active']) ? $active = '1' : $active = '0';
isset($postarray['relay_all_recipients']) ? $relay_all_recipients = '1' : $relay_all_recipients = '0'; isset($postarray['relay_all_recipients']) ? $relay_all_recipients = '1' : $relay_all_recipients = '0';
isset($postarray['backupmx']) ? $backupmx = '1' : $backupmx = '0'; isset($postarray['backupmx']) ? $backupmx = '1' : $backupmx = '0';
@ -2623,6 +2656,18 @@ function mailbox_add_alias($postarray) {
return false; return false;
} }
$stmt = $pdo->prepare("SELECT `address` FROM `alias`
WHERE `address`= :address");
$stmt->execute(array(':address' => $address));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['is_alias_or_mailbox'], htmlspecialchars($address))
);
return false;
}
foreach ($addresses as $address) { foreach ($addresses as $address) {
if (empty($address)) { if (empty($address)) {
continue; continue;
@ -2632,6 +2677,15 @@ function mailbox_add_alias($postarray) {
$local_part = strstr($address, '@', true); $local_part = strstr($address, '@', true);
$address = $local_part.'@'.$domain; $address = $local_part.'@'.$domain;
$domaindata = mailbox_get_domain_details($domain);
if ($domaindata['aliases_left'] == 0) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['max_alias_exceeded'])
);
return false;
}
try { try {
$stmt = $pdo->prepare("SELECT `domain` FROM `domain` $stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)"); WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)");
@ -3432,7 +3486,7 @@ function mailbox_edit_domain($postarray) {
// aliases float // aliases float
// mailboxes float // mailboxes float
// maxquota float // maxquota float
// quota float (Byte) // quota float (Byte)
// active int // active int
global $lang; global $lang;
@ -3519,6 +3573,14 @@ function mailbox_edit_domain($postarray) {
return false; return false;
} }
if ($maxquota == "0" || empty($maxquota)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['maxquota_empty'])
);
return false;
}
if ($MailboxData['maxquota'] > $maxquota) { if ($MailboxData['maxquota'] > $maxquota) {
$_SESSION['return'] = array( $_SESSION['return'] = array(
'type' => 'danger', 'type' => 'danger',
@ -4271,6 +4333,10 @@ function mailbox_get_domain_details($domain) {
':domain' => $domain, ':domain' => $domain,
)); ));
$row = $stmt->fetch(PDO::FETCH_ASSOC); $row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)) {
return false;
}
$stmt = $pdo->prepare("SELECT COUNT(*) AS `count`, COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` = :domain"); $stmt = $pdo->prepare("SELECT COUNT(*) AS `count`, COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` = :domain");
$stmt->execute(array(':domain' => $row['domain'])); $stmt->execute(array(':domain' => $row['domain']));
$MailboxDataDomain = $stmt->fetch(PDO::FETCH_ASSOC); $MailboxDataDomain = $stmt->fetch(PDO::FETCH_ASSOC);
@ -4303,8 +4369,9 @@ function mailbox_get_domain_details($domain) {
$stmt->execute(array( $stmt->execute(array(
':domain' => $domain, ':domain' => $domain,
)); ));
$AliasData = $stmt->fetch(PDO::FETCH_ASSOC); $AliasDataDomain = $stmt->fetch(PDO::FETCH_ASSOC);
(isset($AliasData['alias_count'])) ? $domaindata['aliases_in_domain'] = $AliasData['alias_count'] : $domaindata['aliases_in_domain'] = "0"; (isset($AliasDataDomain['alias_count'])) ? $domaindata['aliases_in_domain'] = $AliasDataDomain['alias_count'] : $domaindata['aliases_in_domain'] = "0";
$domaindata['aliases_left'] = $row['aliases'] - $AliasDataDomain['alias_count'];
} }
catch (PDOException $e) { catch (PDOException $e) {
$_SESSION['return'] = array( $_SESSION['return'] = array(

View File

@ -16,11 +16,10 @@
<link rel="stylesheet" href="/css/bootstrap-slider.min.css"> <link rel="stylesheet" href="/css/bootstrap-slider.min.css">
<link rel="stylesheet" href="/css/bootstrap-switch.min.css"> <link rel="stylesheet" href="/css/bootstrap-switch.min.css">
<link rel="stylesheet" href="/css/footable.bootstrap.min.css"> <link rel="stylesheet" href="/css/footable.bootstrap.min.css">
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Source+Sans+Pro:400,600,700&subset=latin,latin-ext">
<link rel="stylesheet" href="/inc/languages.min.css"> <link rel="stylesheet" href="/inc/languages.min.css">
<link rel="stylesheet" href="/css/mailcow.css"> <link rel="stylesheet" href="/css/mailcow.css">
<link rel="stylesheet" href="/css/tables.css">
<?=(preg_match("/mailbox.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/mailbox.css">' : null;?> <?=(preg_match("/mailbox.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/mailbox.css">' : null;?>
<?=(preg_match("/admin.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/admin.css">' : null;?>
<link rel="shortcut icon" href="/favicon.png" type="image/png"> <link rel="shortcut icon" href="/favicon.png" type="image/png">
<link rel="icon" href="/favicon.png" type="image/png"> <link rel="icon" href="/favicon.png" type="image/png">
</head> </head>

View File

@ -2,7 +2,7 @@ $(document).ready(function() {
// add.php // add.php
// Get max. possible quota for a domain when domain field changes // Get max. possible quota for a domain when domain field changes
$('#addSelectDomain').on('change', function() { $('#addSelectDomain').on('change', function() {
$.get("json_api.php", { action:"get_domain_details", object:this.value }, function(data){ $.get("/api/v1/domain/" + this.value, function(data){
var result = jQuery.parseJSON( data ); var result = jQuery.parseJSON( data );
max_new_mailbox_quota = ( result.max_new_mailbox_quota / 1048576); max_new_mailbox_quota = ( result.max_new_mailbox_quota / 1048576);
if (max_new_mailbox_quota != '0') { if (max_new_mailbox_quota != '0') {

View File

@ -1,31 +1,42 @@
$(document).ready(function() { $(document).ready(function() {
// Postfix restrictions, drag and drop functions $.ajax({
$( "[id*=srr-sortable]" ).sortable({ dataType: 'json',
items: "li:not(.list-heading)", url: '/api/v1/domain-admin/all',
cancel: ".ui-state-disabled", jsonp: false,
connectWith: "[id*=srr-sortable]", error: function () {
dropOnEmpty: true, alert('Cannot draw domain administrator table');
placeholder: "ui-state-highlight" },
}); success: function (data) {
$( "[id*=ssr-sortable]" ).sortable({ $.each(data, function (i, item) {
items: "li:not(.list-heading)", item.action = '<div class="btn-group">' +
cancel: ".ui-state-disabled", '<a href="/edit.php?domainadmin=' + encodeURI(item.username) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
connectWith: "[id*=ssr-sortable]", '<a href="/delete.php?domainadmin=' + encodeURI(item.username) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
dropOnEmpty: true, '</div>';
placeholder: "ui-state-highlight" });
}); $('#domainadminstable').footable({
$('#srr_form').submit(function(){ "columns": [
var srr_joined_vals = $("[id^=srr-sortable-active] li").map(function() { {"sorted": true,"name":"username","title":lang.username,"style":{"width":"250px"}},
return $(this).data("value"); {"name":"selected_domains","title":lang.admin_domains,"breakpoints":"xs sm"},
}).get().join(', '); {"name":"tfa_active","title":"TFA", "filterable": false,"style":{"maxWidth":"80px","width":"80px"}},
var input = $("<input>").attr("type", "hidden").attr("name", "srr_value").val(srr_joined_vals); {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active},
$('#srr_form').append($(input)); {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
}); ],
$('#ssr_form').submit(function(){ "rows": data,
var ssr_joined_vals = $("[id^=ssr-sortable-active] li").map(function() { "empty": lang.empty,
return $(this).data("value"); "paging": {
}).get().join(', '); "enabled": true,
var input = $("<input>").attr("type", "hidden").attr("name", "ssr_value").val(ssr_joined_vals); "limit": 5,
$('#ssr_form').append($(input)); "size": pagination_size
}); },
"filtering": {
"enabled": true,
"position": "left",
"placeholder": lang.filter_table
},
"sorting": {
"enabled": true
}
});
}
});
}); });

View File

@ -15,7 +15,7 @@ $(document).ready(function() {
$.ajax({ $.ajax({
dataType: 'json', dataType: 'json',
url: '/json_api.php?action=domain_table_data', url: '/api/v1/domain/all',
jsonp: false, jsonp: false,
error: function () { error: function () {
alert('Cannot draw domain table'); alert('Cannot draw domain table');
@ -70,7 +70,7 @@ $(document).ready(function() {
$.ajax({ $.ajax({
dataType: 'json', dataType: 'json',
url: '/json_api.php?action=mailbox_table_data', url: '/api/v1/mailbox/all',
jsonp: false, jsonp: false,
error: function () { error: function () {
alert('Cannot draw mailbox table'); alert('Cannot draw mailbox table');
@ -102,12 +102,12 @@ $(document).ready(function() {
{"sorted": true,"name":"username","title":lang.username,"style":{"width":"250px"}}, {"sorted": true,"name":"username","title":lang.username,"style":{"width":"250px"}},
{"name":"name","title":lang.fname,"breakpoints":"xs sm"}, {"name":"name","title":lang.fname,"breakpoints":"xs sm"},
{"name":"domain","title":lang.domain,"breakpoints":"xs sm"}, {"name":"domain","title":lang.domain,"breakpoints":"xs sm"},
{"name":"quota","title":lang.domain_quota}, {"name":"quota","style":{"whiteSpace":"nowrap"},"title":lang.domain_quota},
{"name":"spam_aliases","filterable": false,"title":lang.spam_aliases,"breakpoints":"xs sm"}, {"name":"spam_aliases","filterable": false,"title":lang.spam_aliases,"breakpoints":"xs sm md"},
{"name":"in_use","filterable": false,"type":"html","title":lang.in_use}, {"name":"in_use","filterable": false,"style":{"whiteSpace":"nowrap"},"type":"html","title":lang.in_use},
{"name":"messages","filterable": false,"style":{"width":"90px"},"title":lang.msg_num,"breakpoints":"xs sm"}, {"name":"messages","filterable": false,"style":{"whiteSpace":"nowrap"},"title":lang.msg_num,"breakpoints":"xs sm md"},
{"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, {"name":"active","filterable": false,"style":{"whiteSpace":"nowrap"},"title":lang.active},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","width":"290px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} {"name":"action","filterable": false,"sortable": false,"style":{"whiteSpace":"nowrap","text-align":"right","width":"290px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
], ],
"empty": lang.empty, "empty": lang.empty,
"rows": data, "rows": data,
@ -130,7 +130,7 @@ $(document).ready(function() {
$.ajax({ $.ajax({
dataType: 'json', dataType: 'json',
url: '/json_api.php?action=resource_table_data', url: '/api/v1/resource/all',
jsonp: false, jsonp: false,
error: function () { error: function () {
alert('Cannot draw resource table'); alert('Cannot draw resource table');
@ -172,7 +172,7 @@ $(document).ready(function() {
$.ajax({ $.ajax({
dataType: 'json', dataType: 'json',
url: '/json_api.php?action=domain_alias_table_data', url: '/api/v1/alias-domain/all',
jsonp: false, jsonp: false,
error: function () { error: function () {
alert('Cannot draw alias domain table'); alert('Cannot draw alias domain table');
@ -212,7 +212,7 @@ $(document).ready(function() {
$.ajax({ $.ajax({
dataType: 'json', dataType: 'json',
url: '/json_api.php?action=alias_table_data', url: '/api/v1/alias/all',
jsonp: false, jsonp: false,
error: function () { error: function () {
alert('Cannot draw alias table'); alert('Cannot draw alias table');

View File

@ -1,236 +0,0 @@
(function() {
var SELECTOR, addEventListener, clickEvents, numberRegExp, sortable, touchDevice, trimRegExp;
SELECTOR = 'table[data-sortable]';
numberRegExp = /^-?[£$¤]?[\d,.]+%?$/;
trimRegExp = /^\s+|\s+$/g;
clickEvents = ['click'];
touchDevice = 'ontouchstart' in document.documentElement;
if (touchDevice) {
clickEvents.push('touchstart');
}
addEventListener = function(el, event, handler) {
if (el.addEventListener != null) {
return el.addEventListener(event, handler, false);
} else {
return el.attachEvent("on" + event, handler);
}
};
sortable = {
init: function(options) {
var table, tables, _i, _len, _results;
if (options == null) {
options = {};
}
if (options.selector == null) {
options.selector = SELECTOR;
}
tables = document.querySelectorAll(options.selector);
_results = [];
for (_i = 0, _len = tables.length; _i < _len; _i++) {
table = tables[_i];
_results.push(sortable.initTable(table));
}
return _results;
},
initTable: function(table) {
var i, th, ths, _i, _len, _ref;
if (((_ref = table.tHead) != null ? _ref.rows.length : void 0) !== 1) {
return;
}
if (table.getAttribute('data-sortable-initialized') === 'true') {
return;
}
table.setAttribute('data-sortable-initialized', 'true');
ths = table.querySelectorAll('th');
for (i = _i = 0, _len = ths.length; _i < _len; i = ++_i) {
th = ths[i];
if (th.getAttribute('data-sortable') !== 'false') {
sortable.setupClickableTH(table, th, i);
}
}
return table;
},
setupClickableTH: function(table, th, i) {
var eventName, onClick, type, _i, _len, _results;
type = sortable.getColumnType(table, i);
onClick = function(e) {
var compare, item, newSortedDirection, position, row, rowArray, sorted, sortedDirection, tBody, ths, value, _compare, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _m, _ref, _ref1;
if (e.handled !== true) {
e.handled = true;
} else {
return false;
}
sorted = this.getAttribute('data-sorted') === 'true';
sortedDirection = this.getAttribute('data-sorted-direction');
if (sorted) {
newSortedDirection = sortedDirection === 'ascending' ? 'descending' : 'ascending';
} else {
newSortedDirection = type.defaultSortDirection;
}
ths = this.parentNode.querySelectorAll('th');
for (_i = 0, _len = ths.length; _i < _len; _i++) {
th = ths[_i];
th.setAttribute('data-sorted', 'false');
th.removeAttribute('data-sorted-direction');
}
this.setAttribute('data-sorted', 'true');
this.setAttribute('data-sorted-direction', newSortedDirection);
tBody = table.tBodies[0];
rowArray = [];
if (!sorted) {
if (type.compare != null) {
_compare = type.compare;
} else {
_compare = function(a, b) {
return b - a;
};
}
compare = function(a, b) {
if (a[0] === b[0]) {
return a[2] - b[2];
}
if (type.reverse) {
return _compare(b[0], a[0]);
} else {
return _compare(a[0], b[0]);
}
};
_ref = tBody.rows;
for (position = _j = 0, _len1 = _ref.length; _j < _len1; position = ++_j) {
row = _ref[position];
value = sortable.getNodeValue(row.cells[i]);
if (type.comparator != null) {
value = type.comparator(value);
}
rowArray.push([value, row, position]);
}
rowArray.sort(compare);
for (_k = 0, _len2 = rowArray.length; _k < _len2; _k++) {
row = rowArray[_k];
tBody.appendChild(row[1]);
}
} else {
_ref1 = tBody.rows;
for (_l = 0, _len3 = _ref1.length; _l < _len3; _l++) {
item = _ref1[_l];
rowArray.push(item);
}
rowArray.reverse();
for (_m = 0, _len4 = rowArray.length; _m < _len4; _m++) {
row = rowArray[_m];
tBody.appendChild(row);
}
}
if (typeof window['CustomEvent'] === 'function') {
return typeof table.dispatchEvent === "function" ? table.dispatchEvent(new CustomEvent('Sortable.sorted', {
bubbles: true
})) : void 0;
}
};
_results = [];
for (_i = 0, _len = clickEvents.length; _i < _len; _i++) {
eventName = clickEvents[_i];
_results.push(addEventListener(th, eventName, onClick));
}
return _results;
},
getColumnType: function(table, i) {
var row, specified, text, type, _i, _j, _len, _len1, _ref, _ref1, _ref2;
specified = (_ref = table.querySelectorAll('th')[i]) != null ? _ref.getAttribute('data-sortable-type') : void 0;
if (specified != null) {
return sortable.typesObject[specified];
}
_ref1 = table.tBodies[0].rows;
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
row = _ref1[_i];
text = sortable.getNodeValue(row.cells[i]);
_ref2 = sortable.types;
for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
type = _ref2[_j];
if (type.match(text)) {
return type;
}
}
}
return sortable.typesObject.alpha;
},
getNodeValue: function(node) {
var dataValue;
if (!node) {
return '';
}
dataValue = node.getAttribute('data-value');
if (dataValue !== null) {
return dataValue;
}
if (typeof node.innerText !== 'undefined') {
return node.innerText.replace(trimRegExp, '');
}
return node.textContent.replace(trimRegExp, '');
},
setupTypes: function(types) {
var type, _i, _len, _results;
sortable.types = types;
sortable.typesObject = {};
_results = [];
for (_i = 0, _len = types.length; _i < _len; _i++) {
type = types[_i];
_results.push(sortable.typesObject[type.name] = type);
}
return _results;
}
};
sortable.setupTypes([
{
name: 'numeric',
defaultSortDirection: 'descending',
match: function(a) {
return a.match(numberRegExp);
},
comparator: function(a) {
return parseFloat(a.replace(/[^0-9.-]/g, ''), 10) || 0;
}
}, {
name: 'date',
defaultSortDirection: 'ascending',
reverse: true,
match: function(a) {
return !isNaN(Date.parse(a));
},
comparator: function(a) {
return Date.parse(a) || 0;
}
}, {
name: 'alpha',
defaultSortDirection: 'ascending',
match: function() {
return true;
},
compare: function(a, b) {
return a.localeCompare(b);
}
}
]);
setTimeout(sortable.init, 0);
if (typeof define === 'function' && define.amd) {
define(function() {
return sortable;
});
} else if (typeof exports !== 'undefined') {
module.exports = sortable;
} else {
window.Sortable = sortable;
}
}).call(this);

View File

@ -2,168 +2,242 @@
require_once 'inc/prerequisites.inc.php'; require_once 'inc/prerequisites.inc.php';
error_reporting(E_ALL); error_reporting(E_ALL);
if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_username'])) { if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_username'])) {
if (isset($_GET['action'])) { if (isset($_GET['action']) && isset($_GET['object'])) {
$action = $_GET['action']; $action = filter_input(INPUT_GET, 'action', FILTER_SANITIZE_STRING);
$object = filter_input(INPUT_GET, 'object', FILTER_SANITIZE_STRING);
switch ($action) { switch ($action) {
case "domain_table_data": case "domain":
$domains = mailbox_get_domains(); switch ($object) {
if (!empty($domains)) { case "all":
foreach ($domains as $domain) { $domains = mailbox_get_domains();
$data[] = mailbox_get_domain_details($domain); if (!empty($domains)) {
} foreach ($domains as $domain) {
if (!isset($data) || empty($data)) { $data[] = mailbox_get_domain_details($domain);
echo '{}'; }
} if (!isset($data) || empty($data)) {
else { echo '{}';
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); }
} else {
} echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
else {
echo '{}';
}
break;
case "mailbox_table_data":
$domains = mailbox_get_domains();
if (!empty($domains)) {
foreach ($domains as $domain) {
$mailboxes = mailbox_get_mailboxes($domain);
if (!empty($mailboxes)) {
foreach ($mailboxes as $mailbox) {
$data[] = mailbox_get_mailbox_details($mailbox);
} }
} }
} else {
if (!isset($data) || empty($data)) { echo '{}';
echo '{}'; }
} break;
else {
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); default:
} $data = mailbox_get_domain_details($object);
if (!isset($data) || empty($data)) {
echo '{}';
}
else {
echo json_encode(mailbox_get_domain_details($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
break;
} }
else { break;
echo '{}'; case "mailbox":
} switch ($object) {
break; case "all":
case "resource_table_data": $domains = mailbox_get_domains();
$domains = mailbox_get_domains(); if (!empty($domains)) {
if (!empty($domains)) { foreach ($domains as $domain) {
foreach ($domains as $domain) { $mailboxes = mailbox_get_mailboxes($domain);
$resources = mailbox_get_resources($domain); if (!empty($mailboxes)) {
if (!empty($resources)) { foreach ($mailboxes as $mailbox) {
foreach ($resources as $resource) { $data[] = mailbox_get_mailbox_details($mailbox);
$data[] = mailbox_get_resource_details($resource); }
}
}
if (!isset($data) || empty($data)) {
echo '{}';
}
else {
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
} }
} }
} else {
if (!isset($data) || empty($data)) { echo '{}';
echo '{}'; }
} break;
else {
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); default:
} $data = mailbox_get_mailbox_details($object);
if (!isset($data) || empty($data)) {
echo '{}';
}
else {
echo json_encode(mailbox_get_mailbox_details($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
break;
} }
else { break;
echo '{}'; case "resource":
} switch ($object) {
break; case "all":
case "domain_alias_table_data": $domains = mailbox_get_domains();
$domains = mailbox_get_domains(); if (!empty($domains)) {
if (!empty($domains)) { foreach ($domains as $domain) {
foreach ($domains as $domain) { $resources = mailbox_get_resources($domain);
$alias_domains = mailbox_get_alias_domains($domain); if (!empty($resources)) {
if (!empty($alias_domains)) { foreach ($resources as $resource) {
foreach ($alias_domains as $alias_domain) { $data[] = mailbox_get_resource_details($resource);
$data[] = mailbox_get_alias_domain_details($alias_domain); }
}
}
if (!isset($data) || empty($data)) {
echo '{}';
}
else {
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
} }
} }
} else {
if (!isset($data) || empty($data)) { echo '{}';
echo '{}'; }
} break;
else {
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); default:
} $data = mailbox_get_resource_details($object);
if (!isset($data) || empty($data)) {
echo '{}';
}
else {
echo json_encode(mailbox_get_resource_details($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
break;
} }
else { break;
echo '{}'; case "alias-domain":
} switch ($object) {
break; case "all":
case "alias_table_data": $domains = mailbox_get_domains();
$domains = array_merge(mailbox_get_domains(), mailbox_get_alias_domains()); if (!empty($domains)) {
if (!empty($domains)) { foreach ($domains as $domain) {
foreach ($domains as $domain) { $alias_domains = mailbox_get_alias_domains($domain);
$aliases = mailbox_get_aliases($domain); if (!empty($alias_domains)) {
if (!empty($aliases)) { foreach ($alias_domains as $alias_domain) {
foreach ($aliases as $alias) { $data[] = mailbox_get_alias_domain_details($alias_domain);
$data[] = mailbox_get_alias_details($alias); }
}
}
if (!isset($data) || empty($data)) {
echo '{}';
}
else {
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
} }
} }
} else {
if (!isset($data) || empty($data)) { echo '{}';
echo '{}'; }
} break;
else {
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); default:
} $data = mailbox_get_alias_domains($object);
if (!isset($data) || empty($data)) {
echo '{}';
}
else {
echo json_encode(mailbox_get_alias_domains($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
break;
} }
else { break;
echo '{}'; case "alias":
switch ($object) {
case "all":
$domains = array_merge(mailbox_get_domains(), mailbox_get_alias_domains());
if (!empty($domains)) {
foreach ($domains as $domain) {
$aliases = mailbox_get_aliases($domain);
if (!empty($aliases)) {
foreach ($aliases as $alias) {
$data[] = mailbox_get_alias_details($alias);
}
}
}
if (!isset($data) || empty($data)) {
echo '{}';
}
else {
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
}
else {
echo '{}';
}
break;
default:
$data = mailbox_get_alias_details($object);
if (!isset($data) || empty($data)) {
echo '{}';
}
else {
echo json_encode(mailbox_get_alias_details($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
break;
} }
break; break;
case "get_mailbox_details": case "domain-admin":
if (!isset($_GET['object'])) { return false; } switch ($object) {
$object = $_GET['object']; case "all":
$data = mailbox_get_mailbox_details($object); $domain_admins = get_domain_admins();
if (!isset($data) || empty($data)) { if (!empty($domain_admins)) {
echo '{}'; foreach ($domain_admins as $domain_admin) {
$data[] = get_domain_admin_details($domain_admin);
}
if (!isset($data) || empty($data)) {
echo '{}';
}
else {
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
}
else {
echo '{}';
}
break;
default:
$data = get_domain_admin_details($object);
if (!isset($data) || empty($data)) {
echo '{}';
}
else {
echo json_encode(get_domain_admin_details($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
break;
} }
else { break;
echo json_encode(mailbox_get_mailbox_details($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); case "u2f-registration":
} if (($_SESSION["mailcow_cc_role"] == "admin" || $_SESSION["mailcow_cc_role"] == "domainadmin") && $_SESSION["mailcow_cc_username"] == $object) {
break;
case "get_domain_details":
if (!isset($_GET['object'])) { return false; }
$object = $_GET['object'];
$data = mailbox_get_domain_details($object);
if (!isset($data) || empty($data)) {
echo '{}';
}
else {
echo json_encode(mailbox_get_domain_details($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
break;
case "get_u2f_reg_challenge":
if (!isset($_GET['object'])) { return false; }
$object = $_GET['object'];
if (
($_SESSION["mailcow_cc_role"] == "admin" || $_SESSION["mailcow_cc_role"] == "domainadmin")
&&
($_SESSION["mailcow_cc_username"] == $object)
) {
$data = $u2f->getRegisterData(get_u2f_registrations($object)); $data = $u2f->getRegisterData(get_u2f_registrations($object));
list($req, $sigs) = $data; list($req, $sigs) = $data;
$_SESSION['regReq'] = json_encode($req); $_SESSION['regReq'] = json_encode($req);
echo 'var req = ' . json_encode($req) . '; var sigs = ' . json_encode($sigs) . ';'; echo 'var req = ' . json_encode($req) . '; var sigs = ' . json_encode($sigs) . ';';
} }
else { else {
echo '{}'; return;
} }
break; break;
case "get_u2f_auth_challenge": case "u2f-authentication":
if (!isset($_GET['object'])) { return false; }
$object = $_GET['object'];
if (isset($_SESSION['pending_mailcow_cc_username']) && $_SESSION['pending_mailcow_cc_username'] == $object) { if (isset($_SESSION['pending_mailcow_cc_username']) && $_SESSION['pending_mailcow_cc_username'] == $object) {
$reqs = json_encode($u2f->getAuthenticateData(get_u2f_registrations($object))); $reqs = json_encode($u2f->getAuthenticateData(get_u2f_registrations($object)));
$_SESSION['authReq'] = $reqs; $_SESSION['authReq'] = $reqs;
echo 'var req = ' . $reqs . ';'; echo 'var req = ' . $reqs . ';';
} }
else { else {
echo '{}'; return;
} }
break; break;
default: default:
echo '{}'; echo '{}';
break; break;
} }
} }
} }

View File

@ -36,6 +36,7 @@ $lang['danger']['object_exists'] = 'Objekt %s existiert bereits';
$lang['danger']['domain_exists'] = 'Domain %s existiert bereits'; $lang['danger']['domain_exists'] = 'Domain %s existiert bereits';
$lang['danger']['alias_goto_identical'] = 'Alias- und Ziel-Adresse dürfen nicht identisch sein'; $lang['danger']['alias_goto_identical'] = 'Alias- und Ziel-Adresse dürfen nicht identisch sein';
$lang['danger']['aliasd_targetd_identical'] = 'Alias-Domain darf nicht gleich Ziel-Domain sein'; $lang['danger']['aliasd_targetd_identical'] = 'Alias-Domain darf nicht gleich Ziel-Domain sein';
$lang['danger']['maxquota_empty'] = 'Max. Speicherplatz pro Mailbox darf nicht 0 sein.';
$lang['success']['alias_added'] = 'Alias-Adresse(n) wurden angelegt'; $lang['success']['alias_added'] = 'Alias-Adresse(n) wurden angelegt';
$lang['success']['alias_modified'] = 'Änderungen an Alias %s wurden gespeichert'; $lang['success']['alias_modified'] = 'Änderungen an Alias %s wurden gespeichert';
$lang['success']['aliasd_modified'] = 'Änderungen an Alias-Domain %s wurden gespeichert'; $lang['success']['aliasd_modified'] = 'Änderungen an Alias-Domain %s wurden gespeichert';
@ -70,6 +71,7 @@ $lang['danger']['is_spam_alias'] = '%s lautet bereits eine Spam-Alias-Adresse';
$lang['danger']['quota_not_0_not_numeric'] = 'Speicherplatz muss numerisch und >= 0 sein'; $lang['danger']['quota_not_0_not_numeric'] = 'Speicherplatz muss numerisch und >= 0 sein';
$lang['danger']['domain_not_found'] = 'Domain "%s" nicht gefunden.'; $lang['danger']['domain_not_found'] = 'Domain "%s" nicht gefunden.';
$lang['danger']['max_mailbox_exceeded'] = 'Anzahl an Mailboxen überschritten (%d von %d)'; $lang['danger']['max_mailbox_exceeded'] = 'Anzahl an Mailboxen überschritten (%d von %d)';
$lang['danger']['max_alias_exceeded'] = 'Anzahl an Alias-Adressen überschritten';
$lang['danger']['mailbox_quota_exceeded'] = 'Speicherplatz überschreitet das Limit (max. %d MiB)'; $lang['danger']['mailbox_quota_exceeded'] = 'Speicherplatz überschreitet das Limit (max. %d MiB)';
$lang['danger']['mailbox_quota_left_exceeded'] = 'Nicht genügend Speicherplatz vorhanden (Speicherplatz anwendbar: %d MiB)'; $lang['danger']['mailbox_quota_left_exceeded'] = 'Nicht genügend Speicherplatz vorhanden (Speicherplatz anwendbar: %d MiB)';
$lang['success']['mailbox_added'] = 'Mailbox %s wurde angelegt'; $lang['success']['mailbox_added'] = 'Mailbox %s wurde angelegt';
@ -145,7 +147,7 @@ $lang['user']['spamfilter_default_score'] = 'Standardwert:';
$lang['user']['spamfilter_hint'] = 'Der erste Wert beschreibt den "low spam score", der zweite Wert den "high spam score".'; $lang['user']['spamfilter_hint'] = 'Der erste Wert beschreibt den "low spam score", der zweite Wert den "high spam score".';
$lang['user']['spamfilter_table_domain_policy'] = "n.v. (Domainrichtlinie)"; $lang['user']['spamfilter_table_domain_policy'] = "n.v. (Domainrichtlinie)";
$lang['user']['tls_policy_warning'] = '<strong>Vorsicht:</strong> Entscheiden Sie sich unverschlüsselte Verbindungen abzulehnen, kann dies dazu führen, dass Kontakte Sie nicht mehr erreichen.<br />Nachrichten, die die Richtlinie nicht erfüllen, werden durch einen Hard-Fail im Mailsystem abgewiesen.'; $lang['user']['tls_policy_warning'] = '<strong>Vorsicht:</strong> Entscheiden Sie sich unverschlüsselte Verbindungen abzulehnen, kann dies dazu führen, dass Kontakte Sie nicht mehr erreichen.<br />Nachrichten, die die Richtlinie nicht erfüllen, werden durch einen Hard-Fail im Mailsystem abgewiesen.<br />Diese Einstellung ist aktiv für die primäre Mailbox, für alle Alias-Adressen, die dieser Mailbox <b>direkt zugeordnet</b> sind (lediglich eine einzige Ziel-Adresse) und der Adressen, die sich aus Alias-Domains ergeben. Ausgeschlossen sind temporäre Aliasse ("Spam-Alias-Adressen"), Catch-All Alias-Adressen sowie Alias-Adressen mit mehreren Zielen.';
$lang['user']['tls_policy'] = 'Verschlüsselungsrichtlinie'; $lang['user']['tls_policy'] = 'Verschlüsselungsrichtlinie';
$lang['user']['tls_enforce_in'] = 'TLS eingehend erzwingen'; $lang['user']['tls_enforce_in'] = 'TLS eingehend erzwingen';
$lang['user']['tls_enforce_out'] = 'TLS ausgehend erzwingen'; $lang['user']['tls_enforce_out'] = 'TLS ausgehend erzwingen';
@ -246,6 +248,7 @@ $lang['mailbox']['add_domain_alias'] = 'Domain-Alias hinzufügen';
$lang['mailbox']['add_mailbox'] = 'Mailbox hinzufügen'; $lang['mailbox']['add_mailbox'] = 'Mailbox hinzufügen';
$lang['mailbox']['add_resource'] = 'Ressource hinzufügen'; $lang['mailbox']['add_resource'] = 'Ressource hinzufügen';
$lang['mailbox']['add_alias'] = 'Alias hinzufügen'; $lang['mailbox']['add_alias'] = 'Alias hinzufügen';
$lang['mailbox']['empty'] = 'Keine Einträge vorhanden';
$lang['info']['no_action'] = 'Keine Aktion anwendbar'; $lang['info']['no_action'] = 'Keine Aktion anwendbar';
@ -326,6 +329,9 @@ $lang['add']['subfolder2'] = 'Sync into subfolder on destination';
$lang['add']['mins_interval'] = 'Abrufintervall (Minuten)'; $lang['add']['mins_interval'] = 'Abrufintervall (Minuten)';
$lang['add']['exclude'] = 'Elemente ausschließen (Regex)'; $lang['add']['exclude'] = 'Elemente ausschließen (Regex)';
$lang['add']['delete2duplicates'] = 'Lösche Duplikate im Ziel'; $lang['add']['delete2duplicates'] = 'Lösche Duplikate im Ziel';
$lang['add']['delete1'] = 'Lösche Nachricht nach Übertragung vom Quell-Server';
$lang['edit']['delete2duplicates'] = 'Lösche Duplikate im Ziel';
$lang['edit']['delete1'] = 'Lösche Nachricht nach Übertragung vom Quell-Server';
$lang['add']['title'] = 'Objekt anlegen'; $lang['add']['title'] = 'Objekt anlegen';
$lang['add']['domain'] = 'Domain'; $lang['add']['domain'] = 'Domain';
@ -449,4 +455,6 @@ $lang['admin']['site_not_found'] = 'Kann mailcow Site-Konfiguration nicht finden
$lang['admin']['public_folder_empty'] = 'Public folder name must not be empty'; // NEEDS TRANSLATION $lang['admin']['public_folder_empty'] = 'Public folder name must not be empty'; // NEEDS TRANSLATION
$lang['admin']['set_rr_failed'] = 'Kann Postfix Restriktionen nicht setzen'; $lang['admin']['set_rr_failed'] = 'Kann Postfix Restriktionen nicht setzen';
$lang['admin']['no_record'] = 'Kein Eintrag'; $lang['admin']['no_record'] = 'Kein Eintrag';
$lang['admin']['filter_table'] = 'Tabelle Filtern';
$lang['admin']['empty'] = 'Keine Einträge vorhanden';
?> ?>

View File

@ -38,6 +38,7 @@ $lang['danger']['object_exists'] = "Object %s already exists";
$lang['danger']['domain_exists'] = "Domain %s already exists"; $lang['danger']['domain_exists'] = "Domain %s already exists";
$lang['danger']['alias_goto_identical'] = "Alias and goto address must not be identical"; $lang['danger']['alias_goto_identical'] = "Alias and goto address must not be identical";
$lang['danger']['aliasd_targetd_identical'] = "Alias domain must not be equal to target domain"; $lang['danger']['aliasd_targetd_identical'] = "Alias domain must not be equal to target domain";
$lang['danger']['maxquota_empty'] = 'Max. quota per mailbox must not be 0.';
$lang['success']['alias_added'] = "Alias address/es has/have been added"; $lang['success']['alias_added'] = "Alias address/es has/have been added";
$lang['success']['alias_modified'] = "Changes to alias have been saved"; $lang['success']['alias_modified'] = "Changes to alias have been saved";
$lang['success']['aliasd_modified'] = "Changes to alias domain have been saved"; $lang['success']['aliasd_modified'] = "Changes to alias domain have been saved";
@ -72,6 +73,7 @@ $lang['danger']['is_spam_alias'] = "%s is already known as a spam alias address"
$lang['danger']['quota_not_0_not_numeric'] = "Quota must be numeric and >= 0"; $lang['danger']['quota_not_0_not_numeric'] = "Quota must be numeric and >= 0";
$lang['danger']['domain_not_found'] = "Domain not found."; $lang['danger']['domain_not_found'] = "Domain not found.";
$lang['danger']['max_mailbox_exceeded'] = "Max. mailboxes exceeded (%d of %d)"; $lang['danger']['max_mailbox_exceeded'] = "Max. mailboxes exceeded (%d of %d)";
$lang['danger']['max_alias_exceeded'] = 'Max. aliases exceeded';
$lang['danger']['mailbox_quota_exceeded'] = "Quota exceeds the domain limit (max. %d MiB)"; $lang['danger']['mailbox_quota_exceeded'] = "Quota exceeds the domain limit (max. %d MiB)";
$lang['danger']['mailbox_quota_left_exceeded'] = "Not enough space left (space left: %d MiB)"; $lang['danger']['mailbox_quota_left_exceeded'] = "Not enough space left (space left: %d MiB)";
$lang['success']['mailbox_added'] = "Mailbox %s has been added"; $lang['success']['mailbox_added'] = "Mailbox %s has been added";
@ -147,7 +149,7 @@ $lang['user']['spamfilter_default_score'] = 'Default values:';
$lang['user']['spamfilter_hint'] = 'The first value describes the "low spam score", the second represents the "high spam score".'; $lang['user']['spamfilter_hint'] = 'The first value describes the "low spam score", the second represents the "high spam score".';
$lang['user']['spamfilter_table_domain_policy'] = "n/a (domain policy)"; $lang['user']['spamfilter_table_domain_policy'] = "n/a (domain policy)";
$lang['user']['tls_policy_warning'] = '<strong>Warning:</strong> If you decide to enforce encrypted mail transfer, you may lose emails.<br />Messages to not satisfy the policy will be bounced with a hard fail by the mail system.'; $lang['user']['tls_policy_warning'] = '<strong>Warning:</strong> If you decide to enforce encrypted mail transfer, you may lose emails.<br />Messages to not satisfy the policy will be bounced with a hard fail by the mail system.<br />This option applies to your primary email address (login name), all addresses derived from alias domains as well as alias addresses <b>with only this single mailbox</b> as target.';
$lang['user']['tls_policy'] = 'Encryption policy'; $lang['user']['tls_policy'] = 'Encryption policy';
$lang['user']['tls_enforce_in'] = 'Enforce TLS incoming'; $lang['user']['tls_enforce_in'] = 'Enforce TLS incoming';
$lang['user']['tls_enforce_out'] = 'Enforce TLS outgoing'; $lang['user']['tls_enforce_out'] = 'Enforce TLS outgoing';
@ -249,6 +251,7 @@ $lang['mailbox']['add_mailbox'] = 'Add mailbox';
$lang['mailbox']['add_resource'] = 'Add resource'; $lang['mailbox']['add_resource'] = 'Add resource';
$lang['mailbox']['add_alias'] = 'Add alias'; $lang['mailbox']['add_alias'] = 'Add alias';
$lang['mailbox']['add_domain_record_first'] = 'Please add a domain first'; $lang['mailbox']['add_domain_record_first'] = 'Please add a domain first';
$lang['mailbox']['empty'] = 'No results';
$lang['info']['no_action'] = 'No action applicable'; $lang['info']['no_action'] = 'No action applicable';
@ -330,6 +333,9 @@ $lang['add']['maxage'] = 'Maximum age of messages that will be polled from remot
$lang['add']['subfolder2'] = 'Sync into subfolder on destination'; $lang['add']['subfolder2'] = 'Sync into subfolder on destination';
$lang['add']['exclude'] = 'Exclude objects (regex)'; $lang['add']['exclude'] = 'Exclude objects (regex)';
$lang['add']['delete2duplicates'] = 'Delete duplicates on destination'; $lang['add']['delete2duplicates'] = 'Delete duplicates on destination';
$lang['add']['delete1'] = 'Delete from source when completed';
$lang['edit']['delete2duplicates'] = 'Delete duplicates on destination';
$lang['edit']['delete1'] = 'Delete from source when completed';
$lang['add']['title'] = 'Add object'; $lang['add']['title'] = 'Add object';
$lang['add']['domain'] = 'Domain'; $lang['add']['domain'] = 'Domain';
@ -460,4 +466,6 @@ $lang['admin']['site_not_found'] = 'Cannot locate mailcow site configuration';
$lang['admin']['public_folder_empty'] = 'Public folder name must not be empty'; $lang['admin']['public_folder_empty'] = 'Public folder name must not be empty';
$lang['admin']['set_rr_failed'] = 'Cannot set Postfix restrictions'; $lang['admin']['set_rr_failed'] = 'Cannot set Postfix restrictions';
$lang['admin']['no_record'] = 'No record'; $lang['admin']['no_record'] = 'No record';
$lang['admin']['filter_table'] = 'Filter table';
$lang['admin']['empty'] = 'No results';
?> ?>

View File

@ -321,6 +321,7 @@ $lang['add']['maxage'] = 'Maximum age of messages that will be polled from remot
$lang['add']['subfolder2'] = "Синхронизировать в подпапку по назначению"; $lang['add']['subfolder2'] = "Синхронизировать в подпапку по назначению";
$lang['add']['exclude'] = "Исключить объекты (regex)"; $lang['add']['exclude'] = "Исключить объекты (regex)";
$lang['add']['delete2duplicates'] = "Удалить дубликаты в получателях"; $lang['add']['delete2duplicates'] = "Удалить дубликаты в получателях";
$lang['edit']['delete2duplicates'] = "Удалить дубликаты в получателях";
$lang['add']['title'] = "Добавить объект"; $lang['add']['title'] = "Добавить объект";
$lang['add']['domain'] = "Домен"; $lang['add']['domain'] = "Домен";
$lang['add']['active'] = "Активный"; $lang['add']['active'] = "Активный";

View File

@ -5,33 +5,11 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
require_once "inc/header.inc.php"; require_once "inc/header.inc.php";
$_SESSION['return_to'] = $_SERVER['REQUEST_URI']; $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
?> ?>
<style>
table.footable>tbody>tr.footable-empty>td {
font-size:15px !important;
font-style:italic;
}
.pagination a {
text-decoration: none !important;
}
.panel panel-default {
overflow: visible !important;
}
.table-responsive {
overflow: visible !important;
}
.footer-add-item {
text-align:center;
font-style: italic;
display:block;
padding: 10px;
}
</style>
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title"><?=$lang['mailbox']['domains'];?></h3>
<div class="pull-right"> <div class="pull-right">
<?php <?php
if ($_SESSION['mailcow_cc_role'] == "admin"): if ($_SESSION['mailcow_cc_role'] == "admin"):
@ -41,6 +19,7 @@ table.footable>tbody>tr.footable-empty>td {
endif; endif;
?> ?>
</div> </div>
<h3 class="panel-title"><?=$lang['mailbox']['domains'];?></h3>
</div> </div>
<div class="table-responsive"> <div class="table-responsive">
<table id="domain_table" class="table table-striped"></table> <table id="domain_table" class="table table-striped"></table>
@ -49,14 +28,15 @@ table.footable>tbody>tr.footable-empty>td {
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title"><?=$lang['mailbox']['mailboxes'];?></h3>
<div class="pull-right"> <div class="pull-right">
<a href="/add.php?mailbox"><span class="glyphicon glyphicon-plus"></span></a> <a href="/add.php?mailbox"><span class="glyphicon glyphicon-plus"></span></a>
</div> </div>
<h3 class="panel-title"><?=$lang['mailbox']['mailboxes'];?></h3>
</div> </div>
<div class="table-responsive"> <div class="table-responsive">
<table id="mailbox_table" class="table table-striped"></table> <table id="mailbox_table" class="table table-striped"></table>
@ -65,14 +45,15 @@ table.footable>tbody>tr.footable-empty>td {
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title"><?=$lang['mailbox']['resources'];?></h3>
<div class="pull-right"> <div class="pull-right">
<a href="/add.php?resource"><span class="glyphicon glyphicon-plus"></span></a> <a href="/add.php?resource"><span class="glyphicon glyphicon-plus"></span></a>
</div> </div>
<h3 class="panel-title"><?=$lang['mailbox']['resources'];?></h3>
</div> </div>
<div class="table-responsive"> <div class="table-responsive">
<table id="resources_table" class="table table-striped"></table> <table id="resources_table" class="table table-striped"></table>
@ -80,14 +61,15 @@ table.footable>tbody>tr.footable-empty>td {
<span class="footer-add-item"><a href="/add.php?resource"><?=$lang['mailbox']['add_resource'];?></a></span> </div> <span class="footer-add-item"><a href="/add.php?resource"><?=$lang['mailbox']['add_resource'];?></a></span> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title"><?=$lang['mailbox']['domain_aliases'];?></h3>
<div class="pull-right"> <div class="pull-right">
<a href="/add.php?aliasdomain"><span class="glyphicon glyphicon-plus"></span></a> <a href="/add.php?aliasdomain"><span class="glyphicon glyphicon-plus"></span></a>
</div> </div>
<h3 class="panel-title"><?=$lang['mailbox']['domain_aliases'];?></h3>
</div> </div>
<div class="table-responsive"> <div class="table-responsive">
<table id="aliasdomain_table" class="table table-striped"></table> <table id="aliasdomain_table" class="table table-striped"></table>
@ -100,10 +82,10 @@ table.footable>tbody>tr.footable-empty>td {
<div class="col-md-12"> <div class="col-md-12">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title"><?=$lang['mailbox']['aliases'];?></h3>
<div class="pull-right"> <div class="pull-right">
<a href="/add.php?alias"><span class="glyphicon glyphicon-plus"></span></a> <a href="/add.php?alias"><span class="glyphicon glyphicon-plus"></span></a>
</div> </div>
<h3 class="panel-title"><?=$lang['mailbox']['aliases'];?></h3>
</div> </div>
<div class="table-responsive"> <div class="table-responsive">
<table id="alias_table" class="table table-striped"></table> <table id="alias_table" class="table table-striped"></table>

View File

@ -405,7 +405,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
</div> </div>
<div role="tabpanel" class="tab-pane" id="Syncjobs"> <div role="tabpanel" class="tab-pane" id="Syncjobs">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped sortable-theme-bootstrap" data-sortable id="timelimitedaliases"> <table class="table table-striped" id="timelimitedaliases">
<thead> <thead>
<tr> <tr>
<th class="sort-table" style="min-width: 96px;">Server:Port</th> <th class="sort-table" style="min-width: 96px;">Server:Port</th>
@ -416,7 +416,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
<th class="sort-table" style="min-width: 35px;"><?=$lang['user']['last_run'];?></th> <th class="sort-table" style="min-width: 35px;"><?=$lang['user']['last_run'];?></th>
<th class="sort-table" style="min-width: 35px;">Log</th> <th class="sort-table" style="min-width: 35px;">Log</th>
<th class="sort-table" style="max-width: 95px;"><?=$lang['user']['active'];?></th> <th class="sort-table" style="max-width: 95px;"><?=$lang['user']['active'];?></th>
<th style="text-align: right; min-width: 200px;" data-sortable="false"><?=$lang['user']['action'];?></th> <th style="text-align: right; min-width: 200px;"><?=$lang['user']['action'];?></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@ -151,10 +151,11 @@ services:
depends_on: depends_on:
- bind9-mailcow - bind9-mailcow
volumes: volumes:
- ./data/conf/dovecot:/etc/dovecot - ./data/conf/dovecot:/usr/local/etc/dovecot
- ./data/assets/ssl:/etc/ssl/mail/:ro - ./data/assets/ssl:/etc/ssl/mail/:ro
- ./data/conf/sogo/:/etc/sogo/ - ./data/conf/sogo/:/etc/sogo/
- vmail-vol-1:/var/vmail - vmail-vol-1:/var/vmail
- crypt-vol-1:/mail_crypt/
environment: environment:
- DBNAME=${DBNAME} - DBNAME=${DBNAME}
- DBUSER=${DBUSER} - DBUSER=${DBUSER}
@ -184,6 +185,7 @@ services:
- ./data/conf/postfix:/opt/postfix/conf - ./data/conf/postfix:/opt/postfix/conf
- ./data/assets/ssl:/etc/ssl/mail/:ro - ./data/assets/ssl:/etc/ssl/mail/:ro
- postfix-vol-1:/var/spool/postfix - postfix-vol-1:/var/spool/postfix
- crypt-vol-1:/var/lib/zeyple
environment: environment:
- DBNAME=${DBNAME} - DBNAME=${DBNAME}
- DBUSER=${DBUSER} - DBUSER=${DBUSER}
@ -266,3 +268,4 @@ volumes:
redis-vol-1: redis-vol-1:
rspamd-vol-1: rspamd-vol-1:
postfix-vol-1: postfix-vol-1:
crypt-vol-1:

View File

@ -136,6 +136,39 @@ server {
} }
``` ```
## Optional: Setup a relayhost
Insert these lines to `data/conf/postfix/main.cf`. "relayhost" does already exist (empty), just change its value.
```
relayhost = [your-relayhost]:587
smtp_sasl_password_maps = hash:/opt/postfix/conf/smarthost_passwd
smtp_sasl_auth_enable = yes
```
Create the credentials file:
```
echo "your-relayhost username:password" > data/conf/postfix/smarthost_passwd
```
Run:
```
docker-compose exec postfix-mailcow postmap /opt/postfix/conf/smarthost_passwd
docker-compose exec postfix-mailcow chown root:postfix /opt/postfix/conf/smarthost_passwd /opt/postfix/conf/smarthost_passwd.db
docker-compose exec postfix-mailcow chmod 660 /opt/postfix/conf/smarthost_passwd /opt/postfix/conf/smarthost_passwd.db
docker-compose exec postfix-mailcow postfix reload
```
## Install a local MTA
The easiest option would be to disable the listener on port 25/tcp.
**Postfix** users disable the listener by commenting the following line (starting with `smtp` or `25`) in `/etc/postfix/master.cf`:
```
#smtp inet n - - - - smtpd
```
Restart Postfix after applying your changes.
## Sender and receiver model ## Sender and receiver model
When a mailbox is created, a user is allowed to send mail from and receive mail for his own mailbox address. When a mailbox is created, a user is allowed to send mail from and receive mail for his own mailbox address.

View File

@ -1,5 +1,7 @@
## Install mailcow ## Install mailcow
**WARNING**: Please use Ubuntu 16.04 instead of Debian 8 or [switch to the kernel 4.9 from jessie backports](https://packages.debian.org/jessie-backports/linux-image-amd64) because there is a bug (kernel panic) with the kernel 3.16 when running docker containers with healthchecks! Full details here: [github.com/docker/docker/issues/30402](https://github.com/docker/docker/issues/30402) and [forum.mailcow.email/t/solved-mailcow-docker-causes-kernel-panic-edit/448](https://forum.mailcow.email/t/solved-mailcow-docker-causes-kernel-panic-edit/448)
You need Docker and Docker Compose. You need Docker and Docker Compose.
1\. Learn how to install [Docker](https://docs.docker.com/engine/installation/linux/) and [Docker Compose](https://docs.docker.com/compose/install/). 1\. Learn how to install [Docker](https://docs.docker.com/engine/installation/linux/) and [Docker Compose](https://docs.docker.com/compose/install/).
@ -35,6 +37,8 @@ nano mailcow.conf
``` ```
If you plan to use a reverse proxy, you can, for example, bind HTTPS to 127.0.0.1 on port 8443 and HTTP to 127.0.0.1 on port 8080. If you plan to use a reverse proxy, you can, for example, bind HTTPS to 127.0.0.1 on port 8443 and HTTP to 127.0.0.1 on port 8080.
You may need to stop an existing pre-installed MTA which blocks port 25/tcp. See [this chapter](https://andryyy.github.io/mailcow-dockerized/first_steps/#install-a-local-mta) to learn how to reconfigure Postfix to run besides mailcow after a successful installation.
5\. Pull the images and run the composer file. The paramter `-d` will start mailcow: dockerized detached: 5\. Pull the images and run the composer file. The paramter `-d` will start mailcow: dockerized detached:
``` ```
docker-compose pull docker-compose pull

View File

@ -215,6 +215,51 @@ source mailcow.conf
docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME} < backup_file.sql docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME} < backup_file.sql
``` ```
### Reset MySQL passwords
Stop the stack by running `docker-compose stop`.
When the containers came to a stop, run this command:
```
docker-compose run --rm --entrypoint '/bin/sh -c "gosu mysql mysqld --skip-grant-tables & sleep 10 && mysql -hlocalhost -uroot && exit 0"' mysql-mailcow
```
**1\. Find database name**
```
MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mailcow_database | <=====
| mysql |
| performance_schema |
+--------------------+
4 rows in set (0.00 sec)
```
**2\. Reset one or more users**
Both "password" and "authentication_string" exist. Currently "password" is used, but better set both.
```
MariaDB [(none)]> SELECT user FROM mysql.user;
+--------------+
| user |
+--------------+
| mailcow_user | <=====
| root |
+--------------+
2 rows in set (0.00 sec)
MariaDB [(none)]> FLUSH PRIVILEGES;
MariaDB [(none)]> UPDATE mysql.user SET authentication_string = PASSWORD('gotr00t'), password = PASSWORD('gotr00t') WHERE User = 'root' AND Host = '%';
MariaDB [(none)]> UPDATE mysql.user SET authentication_string = PASSWORD('mookuh'), password = PASSWORD('mookuh') WHERE User = 'mailcow' AND Host = '%';
MariaDB [(none)]> FLUSH PRIVILEGES;
```
## Debugging ## Debugging
You can use `docker-compose logs $service-name` for all containers. You can use `docker-compose logs $service-name` for all containers.
@ -223,6 +268,8 @@ Run `docker-compose logs` for all logs at once.
Follow the log output by running docker-compose with `logs -f`. Follow the log output by running docker-compose with `logs -f`.
Limit the output by calling logs with `--tail=300` like `docker-compose logs --tail=300 mysql-mailcow`.
## Redirect port 80 to 443 ## Redirect port 80 to 443
Since February the 28th 2017 mailcow does come with port 80 and 443 enabled. Since February the 28th 2017 mailcow does come with port 80 and 443 enabled.