commit
9633a34f9f
@ -1,33 +1,30 @@
|
||||
FROM ubuntu:xenial
|
||||
FROM debian:stretch-slim
|
||||
#ubuntu:xenial
|
||||
MAINTAINER Andre Peters <andre.peters@servercow.de>
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ENV LC_ALL C
|
||||
ENV DOVECOT_VERSION 2.2.28
|
||||
ENV PIGEONHOLE_VERSION 0.4.17
|
||||
|
||||
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 -y install dovecot-common \
|
||||
dovecot-core \
|
||||
dovecot-imapd \
|
||||
dovecot-lmtpd \
|
||||
dovecot-managesieved \
|
||||
dovecot-sieve \
|
||||
dovecot-mysql \
|
||||
dovecot-pop3d \
|
||||
dovecot-dev \
|
||||
RUN apt-get update \
|
||||
&& apt-get -y install libpam-dev \
|
||||
default-libmysqlclient-dev \
|
||||
lzma-dev \
|
||||
liblz-dev \
|
||||
libbz2-dev \
|
||||
liblz4-dev \
|
||||
liblzma-dev \
|
||||
build-essential \
|
||||
autotools-dev \
|
||||
automake \
|
||||
syslog-ng \
|
||||
syslog-ng-core \
|
||||
ca-certificates \
|
||||
supervisor \
|
||||
wget \
|
||||
curl \
|
||||
build-essential \
|
||||
autotools-dev \
|
||||
automake \
|
||||
libssl-dev \
|
||||
libauthen-ntlm-perl \
|
||||
libcrypt-ssleay-perl \
|
||||
libdigest-hmac-perl \
|
||||
@ -52,36 +49,57 @@ RUN apt-get -y install dovecot-common \
|
||||
make \
|
||||
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 cpanm Data::Uniqid Mail::IMAPClient String::Util
|
||||
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
|
||||
|
||||
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 ./postlogin.sh /usr/local/bin/postlogin.sh
|
||||
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 ./supervisord.conf /etc/supervisor/supervisord.conf
|
||||
|
||||
RUN chmod +x /usr/local/bin/rspamd-pipe
|
||||
RUN chmod +x /usr/local/bin/imapsync_cron.pl
|
||||
RUN chmod +x /usr/local/lib/dovecot/sieve/rspamd-pipe-ham \
|
||||
/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 useradd -g vmail -u 5000 vmail -d /var/vmail
|
||||
RUN groupadd -g 5000 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
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
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
|
||||
|
@ -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 "/^\$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
|
||||
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}"
|
||||
map {
|
||||
pattern = priv/quota/storage
|
||||
@ -27,7 +31,8 @@ map {
|
||||
}
|
||||
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
|
||||
connect = "host=mysql dbname=${DBNAME} user=${DBNAME} password=${DBPASS}"
|
||||
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';
|
||||
EOF
|
||||
|
||||
[[ ! -d /var/vmail/sieve ]] && mkdir -p /var/vmail/sieve
|
||||
[[ ! -d /etc/sogo ]] && mkdir -p /etc/sogo
|
||||
cat /etc/dovecot/sieve_after > /var/vmail/sieve/global.sieve
|
||||
sievec /var/vmail/sieve/global.sieve
|
||||
chown -R vmail:vmail /var/vmail/sieve
|
||||
# Create global sieve_after script
|
||||
cat /usr/local/etc/dovecot/sieve_after > /var/vmail/sieve/global.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.
|
||||
if [[ $(stat -c %U /var/vmail/) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail ; fi
|
||||
|
||||
# Create random master for SOGo sieve features
|
||||
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)
|
||||
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
|
||||
|
||||
# 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 "$@"
|
||||
|
@ -21,7 +21,7 @@ open my $file, '<', "/etc/sogo/sieve.creds";
|
||||
my $creds = <$file>;
|
||||
close $file;
|
||||
my ($master_user, $master_pass) = split /:/, $creds;
|
||||
my $sth = $dbh->prepare("SELECT id, user1, user2, host1, authmech1, password1, exclude, port1, enc1, delete2duplicates, maxage, subfolder2 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();
|
||||
my $row;
|
||||
|
||||
@ -39,6 +39,7 @@ while ($row = $sth->fetchrow_arrayref()) {
|
||||
$delete2duplicates = @$row[9];
|
||||
$maxage = @$row[10];
|
||||
$subfolder2 = @$row[11];
|
||||
$delete1 = @$row[12];
|
||||
|
||||
if ($enc1 eq "TLS") { $enc1 = "--tls1"; } elsif ($enc1 eq "SSL") { $enc1 = "--ssl1"; } else { undef $enc1; }
|
||||
|
||||
@ -50,6 +51,7 @@ while ($row = $sth->fetchrow_arrayref()) {
|
||||
($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)),
|
||||
($maxage eq "0" ? () : ('--maxage', $maxage)),
|
||||
($delete2duplicates ne "1" ? () : ('--delete2duplicates')),
|
||||
($delete1 ne "1" ? () : ('--delete')),
|
||||
(!defined($enc1) ? () : ($enc1)),
|
||||
"--host1", $host1,
|
||||
"--user1", $user1,
|
||||
|
11
data/Dockerfiles/dovecot/report-ham.sieve
Normal file
11
data/Dockerfiles/dovecot/report-ham.sieve
Normal 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";
|
3
data/Dockerfiles/dovecot/report-spam.sieve
Normal file
3
data/Dockerfiles/dovecot/report-spam.sieve
Normal file
@ -0,0 +1,3 @@
|
||||
require ["vnd.dovecot.pipe", "copy"];
|
||||
|
||||
pipe :copy "rspamd-pipe-spam";
|
@ -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
|
4
data/Dockerfiles/dovecot/rspamd-pipe-ham
Executable file
4
data/Dockerfiles/dovecot/rspamd-pipe-ham
Executable 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
|
4
data/Dockerfiles/dovecot/rspamd-pipe-spam
Executable file
4
data/Dockerfiles/dovecot/rspamd-pipe-spam
Executable 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
|
@ -8,7 +8,7 @@ autostart=true
|
||||
stdout_syslog=true
|
||||
|
||||
[program:dovecot]
|
||||
command=/usr/sbin/dovecot -F
|
||||
command=/usr/local/sbin/dovecot -F
|
||||
autorestart=true
|
||||
|
||||
[program:logfiles]
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM ubuntu:xenial
|
||||
FROM debian:testing-slim
|
||||
MAINTAINER Andre Peters <andre.peters@servercow.de>
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
@ -19,10 +19,19 @@ RUN apt-get install -y --no-install-recommends supervisor \
|
||||
postfix-pcre \
|
||||
syslog-ng \
|
||||
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
|
||||
|
||||
COPY zeyple.py /usr/local/bin/zeyple.py
|
||||
COPY zeyple.conf /etc/zeyple.conf
|
||||
COPY supervisord.conf /etc/supervisor/supervisord.conf
|
||||
COPY postfix.sh /opt/postfix.sh
|
||||
|
||||
|
@ -17,7 +17,7 @@ user = ${DBUSER}
|
||||
password = ${DBPASS}
|
||||
hosts = mysql
|
||||
dbname = ${DBNAME}
|
||||
query = SELECT IF( EXISTS( SELECT 'TLS_ACTIVE' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.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
|
||||
|
||||
cat <<EOF > /opt/postfix/conf/sql/mysql_tls_enforce_out_policy.cf
|
||||
@ -25,7 +25,7 @@ user = ${DBUSER}
|
||||
password = ${DBPASS}
|
||||
hosts = mysql
|
||||
dbname = ${DBNAME}
|
||||
query = SELECT IF( EXISTS( SELECT 'TLS_ACTIVE' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.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
|
||||
|
||||
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()
|
||||
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
|
||||
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Postfix configuration error, refusing to start."
|
||||
exit 1
|
||||
else
|
||||
postfix -c /opt/postfix/conf start
|
||||
supervisorctl restart postfix-maillog
|
||||
sleep 126144000
|
||||
fi
|
||||
|
@ -12,6 +12,17 @@ command=/opt/postfix.sh
|
||||
autorestart=true
|
||||
|
||||
[program:postfix-maillog]
|
||||
command=/usr/bin/tail -f /var/log/mail.log
|
||||
stdout_logfile=/dev/fd/1
|
||||
command=/bin/tail -f /var/log/zeyple.log /var/log/mail.log
|
||||
stdout_logfile=/dev/stdout
|
||||
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
|
||||
|
9
data/Dockerfiles/postfix/zeyple.conf
Normal file
9
data/Dockerfiles/postfix/zeyple.conf
Normal file
@ -0,0 +1,9 @@
|
||||
[zeyple]
|
||||
log_file = /var/log/zeyple.log
|
||||
|
||||
[gpg]
|
||||
home = /var/lib/zeyple/keys
|
||||
|
||||
[relay]
|
||||
host = localhost
|
||||
port = 10026
|
274
data/Dockerfiles/postfix/zeyple.py
Executable file
274
data/Dockerfiles/postfix/zeyple.py
Executable 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)
|
@ -1,16 +1,11 @@
|
||||
FROM ubuntu:xenial
|
||||
FROM debian:jessie-slim
|
||||
MAINTAINER Andre Peters <andre.peters@servercow.de>
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
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 \
|
||||
&& 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 --no-install-recommends -y --force-yes install rmilter cron syslog-ng syslog-ng-core supervisor
|
||||
|
||||
|
@ -1,16 +1,11 @@
|
||||
FROM ubuntu:xenial
|
||||
FROM debian:jessie-slim
|
||||
MAINTAINER Andre Peters <andre.peters@servercow.de>
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
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 \
|
||||
&& 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 -y install rspamd ca-certificates python-pip
|
||||
|
||||
|
@ -1,17 +1,12 @@
|
||||
FROM ubuntu:xenial
|
||||
FROM debian:jessie-slim
|
||||
MAINTAINER Andre Peters <andre.peters@servercow.de>
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ENV LC_ALL C
|
||||
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 \
|
||||
&& apt-get install -y --no-install-recommends apt-transport-https \
|
||||
&& apt-get install -y --no-install-recommends apt-transport-https gnupg \
|
||||
ca-certificates \
|
||||
wget \
|
||||
syslog-ng \
|
||||
@ -29,8 +24,11 @@ RUN apt-get update \
|
||||
&& chmod +x /usr/local/bin/gosu \
|
||||
&& 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 \
|
||||
&& 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 -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 supervisord.conf /etc/supervisor/supervisord.conf
|
||||
|
||||
#EXPOSE 20000
|
||||
#EXPOSE 9191
|
||||
#EXPOSE 9192
|
||||
|
||||
CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
|
||||
|
||||
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
@ -10,9 +10,8 @@ disable_plaintext_auth = yes
|
||||
login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k"
|
||||
mail_home = /var/vmail/%d/%n
|
||||
mail_location = maildir:~/
|
||||
mail_plugins = quota acl zlib antispam
|
||||
auth_username_chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@
|
||||
ssl_protocols = !SSLv3 !SSLv2
|
||||
mail_plugins = quota acl zlib #mail_crypt
|
||||
ssl_protocols = !SSLv3
|
||||
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_options = no_compression
|
||||
@ -24,12 +23,12 @@ auth_master_user_separator = *
|
||||
mail_prefetch_count = 30
|
||||
passdb {
|
||||
driver = passwd-file
|
||||
args = /etc/dovecot/dovecot-master.passwd
|
||||
args = /usr/local/etc/dovecot/dovecot-master.passwd
|
||||
master = yes
|
||||
pass = yes
|
||||
}
|
||||
passdb {
|
||||
args = /etc/dovecot/sql/dovecot-mysql.conf
|
||||
args = /usr/local/etc/dovecot/sql/dovecot-mysql.conf
|
||||
driver = sql
|
||||
}
|
||||
namespace inbox {
|
||||
@ -202,15 +201,15 @@ listen = *,[::]
|
||||
ssl_cert = </etc/ssl/mail/cert.pem
|
||||
ssl_key = </etc/ssl/mail/key.pem
|
||||
userdb {
|
||||
args = /etc/dovecot/sql/dovecot-mysql.conf
|
||||
args = /usr/local/etc/dovecot/sql/dovecot-mysql.conf
|
||||
driver = sql
|
||||
}
|
||||
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 {
|
||||
mail_plugins = quota sieve acl zlib
|
||||
auth_socket_path = /var/run/dovecot/auth-master
|
||||
mail_plugins = quota sieve acl zlib #mail_crypt
|
||||
auth_socket_path = /usr/local/var/run/dovecot/auth-master
|
||||
}
|
||||
protocol sieve {
|
||||
managesieve_logout_format = bytes=%i/%o
|
||||
@ -221,22 +220,31 @@ plugin {
|
||||
acl = vfile
|
||||
quota = dict:Userquota::proxy::sqlquota
|
||||
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_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_max_script_size = 1M
|
||||
sieve_quota_max_scripts = 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 {
|
||||
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 {
|
||||
disable_plaintext_auth = no
|
||||
|
@ -18,6 +18,11 @@ server {
|
||||
access_log /var/log/nginx/access.log;
|
||||
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/ {
|
||||
allow all;
|
||||
default_type "text/plain";
|
||||
@ -166,6 +171,11 @@ server {
|
||||
access_log /var/log/nginx/access.log;
|
||||
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/ {
|
||||
allow all;
|
||||
default_type "text/plain";
|
||||
|
@ -91,3 +91,4 @@ smtpd_milters = inet:rmilter:9900
|
||||
non_smtpd_milters = inet:rmilter:9900
|
||||
milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
|
||||
mydestination = localhost.localdomain, localhost
|
||||
#content_filter=zeyple
|
||||
|
@ -16,6 +16,7 @@ smtp_enforced_tls unix - - n - - smtp
|
||||
-o smtp_tls_security_level=encrypt
|
||||
-o syslog_name=enforced-tls-smtp
|
||||
-o smtp_delivery_status_filter=pcre:/opt/postfix/conf/smtp_dsn_filter
|
||||
|
||||
tlsproxy unix - - n - 0 tlsproxy
|
||||
dnsblog unix - - n - 0 dnsblog
|
||||
pickup fifo n - n 60 1 pickup
|
||||
@ -43,3 +44,14 @@ anvil unix - - n - 1 anvil
|
||||
scache unix - - n - 1 scache
|
||||
maildrop unix - n n - - pipe flags=DRhu
|
||||
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
|
||||
|
@ -350,6 +350,13 @@ elseif (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] ==
|
||||
</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="col-sm-offset-2 col-sm-10">
|
||||
<div class="checkbox">
|
||||
|
@ -81,14 +81,14 @@ $tfa_data = get_tfa();
|
||||
<div class="panel-body">
|
||||
<form method="post">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped sortable-theme-bootstrap" data-sortable id="domainadminstable">
|
||||
<table class="table table-striped" id="domainadminstable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sort-table" style="min-width: 100px;"><?=$lang['admin']['username'];?></th>
|
||||
<th class="sort-table" style="min-width: 166px;"><?=$lang['admin']['admin_domains'];?></th>
|
||||
<th class="sort-table" style="min-width: 76px;"><?=$lang['admin']['active'];?></th>
|
||||
<th class="sort-table" 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="min-width: 100px;"><?=$lang['admin']['username'];?></th>
|
||||
<th style="min-width: 166px;"><?=$lang['admin']['admin_domains'];?></th>
|
||||
<th style="min-width: 76px;"><?=$lang['admin']['active'];?></th>
|
||||
<th style="min-width: 76px;"><?=$lang['tfa']['tfa'];?></th>
|
||||
<th style="text-align: right; min-width: 200px;"><?=$lang['admin']['action'];?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -299,8 +299,14 @@ $tfa_data = get_tfa();
|
||||
</div>
|
||||
</div>
|
||||
</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 src="js/sorttable.js"></script>
|
||||
<script type='text/javascript'>
|
||||
<?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>
|
||||
<?php
|
||||
require_once("inc/footer.inc.php");
|
||||
|
13
data/web/css/admin.css
Normal file
13
data/web/css/admin.css
Normal 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;
|
||||
}
|
@ -1,19 +1,19 @@
|
||||
.panel-heading div {
|
||||
margin-top: -18px;
|
||||
font-size: 15px;
|
||||
table.footable>tbody>tr.footable-empty>td {
|
||||
font-size:15px !important;
|
||||
font-style:italic;
|
||||
}
|
||||
.panel-heading div span {
|
||||
margin-left:5px;
|
||||
.pagination a {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
.panel-body {
|
||||
display: none;
|
||||
.panel panel-default {
|
||||
overflow: visible !important;
|
||||
}
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
.table-responsive {
|
||||
overflow: visible !important;
|
||||
}
|
||||
.progress {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.table>thead>tr>th {
|
||||
vertical-align: top !important;
|
||||
.footer-add-item {
|
||||
text-align:center;
|
||||
font-style: italic;
|
||||
display:block;
|
||||
padding: 10px;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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');?>">
|
||||
</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="col-sm-offset-2 col-sm-10">
|
||||
<div class="checkbox">
|
||||
|
@ -50,11 +50,7 @@ $(document).ready(function() {
|
||||
type: "GET",
|
||||
cache: false,
|
||||
dataType: 'script',
|
||||
url: "json_api.php",
|
||||
data: {
|
||||
'action':'get_u2f_auth_challenge',
|
||||
'object':'<?=(isset($_SESSION['pending_mailcow_cc_username'])) ? $_SESSION['pending_mailcow_cc_username'] : null;?>',
|
||||
},
|
||||
url: "/api/v1/u2f-authentication/<?=(isset($_SESSION['pending_mailcow_cc_username'])) ? $_SESSION['pending_mailcow_cc_username'] : null;?>",
|
||||
success: function(data){
|
||||
data;
|
||||
}
|
||||
@ -87,11 +83,7 @@ $(document).ready(function() {
|
||||
type: "GET",
|
||||
cache: false,
|
||||
dataType: 'script',
|
||||
url: "json_api.php",
|
||||
data: {
|
||||
'action':'get_u2f_reg_challenge',
|
||||
'object':'<?=(isset($_SESSION['mailcow_cc_username'])) ? $_SESSION['mailcow_cc_username'] : null;?>',
|
||||
},
|
||||
url: "/api/v1/u2f-registration/<?=(isset($_SESSION['mailcow_cc_username'])) ? $_SESSION['mailcow_cc_username'] : null;?>",
|
||||
success: function(data){
|
||||
data;
|
||||
}
|
||||
|
@ -109,6 +109,11 @@ function init_db_schema() {
|
||||
if ($num_results == 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'");
|
||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
if ($num_results == 0) {
|
||||
@ -1075,6 +1080,7 @@ function add_syncjob($postarray) {
|
||||
}
|
||||
isset($postarray['active']) ? $active = '1' : $active = '0';
|
||||
isset($postarray['delete2duplicates']) ? $delete2duplicates = '1' : $delete2duplicates = '0';
|
||||
isset($postarray['delete1']) ? $delete1 = '1' : $delete1 = '0';
|
||||
$port1 = $postarray['port1'];
|
||||
$host1 = $postarray['host1'];
|
||||
$password1 = $postarray['password1'];
|
||||
@ -1147,12 +1153,13 @@ function add_syncjob($postarray) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `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)");
|
||||
$stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `delete1`, `maxage`, `subfolder2`, `host1`, `authmech1`, `user1`, `password1`, `mins_interval`, `port1`, `enc1`, `delete2duplicates`, `active`)
|
||||
VALUES (:user2, :exclude, :maxage, :delete1, :subfolder2, :host1, :authmech1, :user1, :password1, :mins_interval, :port1, :enc1, :delete2duplicates, :active)");
|
||||
$stmt->execute(array(
|
||||
':user2' => $username,
|
||||
':exclude' => $exclude,
|
||||
':maxage' => $maxage,
|
||||
':delete1' => $delete1,
|
||||
':subfolder2' => $subfolder2,
|
||||
':host1' => $host1,
|
||||
':authmech1' => 'PLAIN',
|
||||
@ -1200,6 +1207,7 @@ function edit_syncjob($postarray) {
|
||||
}
|
||||
isset($postarray['active']) ? $active = '1' : $active = '0';
|
||||
isset($postarray['delete2duplicates']) ? $delete2duplicates = '1' : $delete2duplicates = '0';
|
||||
isset($postarray['delete1']) ? $delete1 = '1' : $delete1 = '0';
|
||||
$id = $postarray['id'];
|
||||
$port1 = $postarray['port1'];
|
||||
$host1 = $postarray['host1'];
|
||||
@ -1273,10 +1281,11 @@ function edit_syncjob($postarray) {
|
||||
return false;
|
||||
}
|
||||
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");
|
||||
$stmt->execute(array(
|
||||
':user2' => $username,
|
||||
':delete1' => $delete1,
|
||||
':id' => $id,
|
||||
':exclude' => $exclude,
|
||||
':maxage' => $maxage,
|
||||
@ -1757,6 +1766,7 @@ function get_domain_admin_details($domain_admin) {
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT
|
||||
`tfa`.`active` AS `tfa_active_int`,
|
||||
CASE `tfa`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `tfa_active`,
|
||||
`domain_admins`.`username`,
|
||||
`domain_admins`.`created`,
|
||||
`domain_admins`.`active` AS `active_int`,
|
||||
@ -1768,11 +1778,15 @@ function get_domain_admin_details($domain_admin) {
|
||||
':domain_admin' => $domain_admin
|
||||
));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (empty($row)) {
|
||||
return false;
|
||||
}
|
||||
$domainadmindata['username'] = $row['username'];
|
||||
$domainadmindata['tfa_active'] = $row['tfa_active'];
|
||||
$domainadmindata['active'] = $row['active'];
|
||||
$domainadmindata['active_int'] = $row['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
|
||||
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
|
||||
WHERE `domain` IN (
|
||||
@ -1793,6 +1807,9 @@ function get_domain_admin_details($domain_admin) {
|
||||
while($row = array_shift($rows)) {
|
||||
$domainadmindata['unselected_domains'][] = $row['domain'];
|
||||
}
|
||||
if (!isset($domainadmindata['unselected_domains'])) {
|
||||
$domainadmindata['unselected_domains'] = "";
|
||||
}
|
||||
}
|
||||
catch(PDOException $e) {
|
||||
$_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))) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
@ -2519,6 +2544,14 @@ function mailbox_add_domain($postarray) {
|
||||
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['relay_all_recipients']) ? $relay_all_recipients = '1' : $relay_all_recipients = '0';
|
||||
isset($postarray['backupmx']) ? $backupmx = '1' : $backupmx = '0';
|
||||
@ -2623,6 +2656,18 @@ function mailbox_add_alias($postarray) {
|
||||
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) {
|
||||
if (empty($address)) {
|
||||
continue;
|
||||
@ -2632,6 +2677,15 @@ function mailbox_add_alias($postarray) {
|
||||
$local_part = strstr($address, '@', true);
|
||||
$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 {
|
||||
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
|
||||
WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)");
|
||||
@ -3519,6 +3573,14 @@ function mailbox_edit_domain($postarray) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($maxquota == "0" || empty($maxquota)) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => sprintf($lang['danger']['maxquota_empty'])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($MailboxData['maxquota'] > $maxquota) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
@ -4271,6 +4333,10 @@ function mailbox_get_domain_details($domain) {
|
||||
':domain' => $domain,
|
||||
));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (empty($row)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) AS `count`, COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` = :domain");
|
||||
$stmt->execute(array(':domain' => $row['domain']));
|
||||
$MailboxDataDomain = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
@ -4303,8 +4369,9 @@ function mailbox_get_domain_details($domain) {
|
||||
$stmt->execute(array(
|
||||
':domain' => $domain,
|
||||
));
|
||||
$AliasData = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
(isset($AliasData['alias_count'])) ? $domaindata['aliases_in_domain'] = $AliasData['alias_count'] : $domaindata['aliases_in_domain'] = "0";
|
||||
$AliasDataDomain = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
(isset($AliasDataDomain['alias_count'])) ? $domaindata['aliases_in_domain'] = $AliasDataDomain['alias_count'] : $domaindata['aliases_in_domain'] = "0";
|
||||
$domaindata['aliases_left'] = $row['aliases'] - $AliasDataDomain['alias_count'];
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
|
@ -16,11 +16,10 @@
|
||||
<link rel="stylesheet" href="/css/bootstrap-slider.min.css">
|
||||
<link rel="stylesheet" href="/css/bootstrap-switch.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="/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("/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="icon" href="/favicon.png" type="image/png">
|
||||
</head>
|
||||
|
@ -2,7 +2,7 @@ $(document).ready(function() {
|
||||
// add.php
|
||||
// Get max. possible quota for a domain when domain field changes
|
||||
$('#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 );
|
||||
max_new_mailbox_quota = ( result.max_new_mailbox_quota / 1048576);
|
||||
if (max_new_mailbox_quota != '0') {
|
||||
|
@ -1,31 +1,42 @@
|
||||
$(document).ready(function() {
|
||||
// Postfix restrictions, drag and drop functions
|
||||
$( "[id*=srr-sortable]" ).sortable({
|
||||
items: "li:not(.list-heading)",
|
||||
cancel: ".ui-state-disabled",
|
||||
connectWith: "[id*=srr-sortable]",
|
||||
dropOnEmpty: true,
|
||||
placeholder: "ui-state-highlight"
|
||||
$.ajax({
|
||||
dataType: 'json',
|
||||
url: '/api/v1/domain-admin/all',
|
||||
jsonp: false,
|
||||
error: function () {
|
||||
alert('Cannot draw domain administrator table');
|
||||
},
|
||||
success: function (data) {
|
||||
$.each(data, function (i, item) {
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="/edit.php?domainadmin=' + encodeURI(item.username) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
|
||||
'<a href="/delete.php?domainadmin=' + encodeURI(item.username) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
});
|
||||
$( "[id*=ssr-sortable]" ).sortable({
|
||||
items: "li:not(.list-heading)",
|
||||
cancel: ".ui-state-disabled",
|
||||
connectWith: "[id*=ssr-sortable]",
|
||||
dropOnEmpty: true,
|
||||
placeholder: "ui-state-highlight"
|
||||
$('#domainadminstable').footable({
|
||||
"columns": [
|
||||
{"sorted": true,"name":"username","title":lang.username,"style":{"width":"250px"}},
|
||||
{"name":"selected_domains","title":lang.admin_domains,"breakpoints":"xs sm"},
|
||||
{"name":"tfa_active","title":"TFA", "filterable": false,"style":{"maxWidth":"80px","width":"80px"}},
|
||||
{"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active},
|
||||
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
|
||||
],
|
||||
"rows": data,
|
||||
"empty": lang.empty,
|
||||
"paging": {
|
||||
"enabled": true,
|
||||
"limit": 5,
|
||||
"size": pagination_size
|
||||
},
|
||||
"filtering": {
|
||||
"enabled": true,
|
||||
"position": "left",
|
||||
"placeholder": lang.filter_table
|
||||
},
|
||||
"sorting": {
|
||||
"enabled": true
|
||||
}
|
||||
});
|
||||
$('#srr_form').submit(function(){
|
||||
var srr_joined_vals = $("[id^=srr-sortable-active] li").map(function() {
|
||||
return $(this).data("value");
|
||||
}).get().join(', ');
|
||||
var input = $("<input>").attr("type", "hidden").attr("name", "srr_value").val(srr_joined_vals);
|
||||
$('#srr_form').append($(input));
|
||||
});
|
||||
$('#ssr_form').submit(function(){
|
||||
var ssr_joined_vals = $("[id^=ssr-sortable-active] li").map(function() {
|
||||
return $(this).data("value");
|
||||
}).get().join(', ');
|
||||
var input = $("<input>").attr("type", "hidden").attr("name", "ssr_value").val(ssr_joined_vals);
|
||||
$('#ssr_form').append($(input));
|
||||
}
|
||||
});
|
||||
});
|
@ -15,7 +15,7 @@ $(document).ready(function() {
|
||||
|
||||
$.ajax({
|
||||
dataType: 'json',
|
||||
url: '/json_api.php?action=domain_table_data',
|
||||
url: '/api/v1/domain/all',
|
||||
jsonp: false,
|
||||
error: function () {
|
||||
alert('Cannot draw domain table');
|
||||
@ -70,7 +70,7 @@ $(document).ready(function() {
|
||||
|
||||
$.ajax({
|
||||
dataType: 'json',
|
||||
url: '/json_api.php?action=mailbox_table_data',
|
||||
url: '/api/v1/mailbox/all',
|
||||
jsonp: false,
|
||||
error: function () {
|
||||
alert('Cannot draw mailbox table');
|
||||
@ -102,12 +102,12 @@ $(document).ready(function() {
|
||||
{"sorted": true,"name":"username","title":lang.username,"style":{"width":"250px"}},
|
||||
{"name":"name","title":lang.fname,"breakpoints":"xs sm"},
|
||||
{"name":"domain","title":lang.domain,"breakpoints":"xs sm"},
|
||||
{"name":"quota","title":lang.domain_quota},
|
||||
{"name":"spam_aliases","filterable": false,"title":lang.spam_aliases,"breakpoints":"xs sm"},
|
||||
{"name":"in_use","filterable": false,"type":"html","title":lang.in_use},
|
||||
{"name":"messages","filterable": false,"style":{"width":"90px"},"title":lang.msg_num,"breakpoints":"xs sm"},
|
||||
{"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active},
|
||||
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","width":"290px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
|
||||
{"name":"quota","style":{"whiteSpace":"nowrap"},"title":lang.domain_quota},
|
||||
{"name":"spam_aliases","filterable": false,"title":lang.spam_aliases,"breakpoints":"xs sm md"},
|
||||
{"name":"in_use","filterable": false,"style":{"whiteSpace":"nowrap"},"type":"html","title":lang.in_use},
|
||||
{"name":"messages","filterable": false,"style":{"whiteSpace":"nowrap"},"title":lang.msg_num,"breakpoints":"xs sm md"},
|
||||
{"name":"active","filterable": false,"style":{"whiteSpace":"nowrap"},"title":lang.active},
|
||||
{"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,
|
||||
"rows": data,
|
||||
@ -130,7 +130,7 @@ $(document).ready(function() {
|
||||
|
||||
$.ajax({
|
||||
dataType: 'json',
|
||||
url: '/json_api.php?action=resource_table_data',
|
||||
url: '/api/v1/resource/all',
|
||||
jsonp: false,
|
||||
error: function () {
|
||||
alert('Cannot draw resource table');
|
||||
@ -172,7 +172,7 @@ $(document).ready(function() {
|
||||
|
||||
$.ajax({
|
||||
dataType: 'json',
|
||||
url: '/json_api.php?action=domain_alias_table_data',
|
||||
url: '/api/v1/alias-domain/all',
|
||||
jsonp: false,
|
||||
error: function () {
|
||||
alert('Cannot draw alias domain table');
|
||||
@ -212,7 +212,7 @@ $(document).ready(function() {
|
||||
|
||||
$.ajax({
|
||||
dataType: 'json',
|
||||
url: '/json_api.php?action=alias_table_data',
|
||||
url: '/api/v1/alias/all',
|
||||
jsonp: false,
|
||||
error: function () {
|
||||
alert('Cannot draw alias table');
|
||||
|
@ -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);
|
@ -2,10 +2,13 @@
|
||||
require_once 'inc/prerequisites.inc.php';
|
||||
error_reporting(E_ALL);
|
||||
if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_username'])) {
|
||||
if (isset($_GET['action'])) {
|
||||
$action = $_GET['action'];
|
||||
if (isset($_GET['action']) && isset($_GET['object'])) {
|
||||
$action = filter_input(INPUT_GET, 'action', FILTER_SANITIZE_STRING);
|
||||
$object = filter_input(INPUT_GET, 'object', FILTER_SANITIZE_STRING);
|
||||
switch ($action) {
|
||||
case "domain_table_data":
|
||||
case "domain":
|
||||
switch ($object) {
|
||||
case "all":
|
||||
$domains = mailbox_get_domains();
|
||||
if (!empty($domains)) {
|
||||
foreach ($domains as $domain) {
|
||||
@ -22,7 +25,21 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||
echo '{}';
|
||||
}
|
||||
break;
|
||||
case "mailbox_table_data":
|
||||
|
||||
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;
|
||||
}
|
||||
break;
|
||||
case "mailbox":
|
||||
switch ($object) {
|
||||
case "all":
|
||||
$domains = mailbox_get_domains();
|
||||
if (!empty($domains)) {
|
||||
foreach ($domains as $domain) {
|
||||
@ -44,7 +61,22 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||
echo '{}';
|
||||
}
|
||||
break;
|
||||
case "resource_table_data":
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
break;
|
||||
case "resource":
|
||||
switch ($object) {
|
||||
case "all":
|
||||
$domains = mailbox_get_domains();
|
||||
if (!empty($domains)) {
|
||||
foreach ($domains as $domain) {
|
||||
@ -66,7 +98,22 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||
echo '{}';
|
||||
}
|
||||
break;
|
||||
case "domain_alias_table_data":
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
break;
|
||||
case "alias-domain":
|
||||
switch ($object) {
|
||||
case "all":
|
||||
$domains = mailbox_get_domains();
|
||||
if (!empty($domains)) {
|
||||
foreach ($domains as $domain) {
|
||||
@ -88,7 +135,21 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||
echo '{}';
|
||||
}
|
||||
break;
|
||||
case "alias_table_data":
|
||||
|
||||
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;
|
||||
}
|
||||
break;
|
||||
case "alias":
|
||||
switch ($object) {
|
||||
case "all":
|
||||
$domains = array_merge(mailbox_get_domains(), mailbox_get_alias_domains());
|
||||
if (!empty($domains)) {
|
||||
foreach ($domains as $domain) {
|
||||
@ -110,55 +171,68 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
|
||||
echo '{}';
|
||||
}
|
||||
break;
|
||||
case "get_mailbox_details":
|
||||
if (!isset($_GET['object'])) { return false; }
|
||||
$object = $_GET['object'];
|
||||
$data = mailbox_get_mailbox_details($object);
|
||||
|
||||
default:
|
||||
$data = mailbox_get_alias_details($object);
|
||||
if (!isset($data) || empty($data)) {
|
||||
echo '{}';
|
||||
}
|
||||
else {
|
||||
echo json_encode(mailbox_get_mailbox_details($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
echo json_encode(mailbox_get_alias_details($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
}
|
||||
break;
|
||||
case "get_domain_details":
|
||||
if (!isset($_GET['object'])) { return false; }
|
||||
$object = $_GET['object'];
|
||||
$data = mailbox_get_domain_details($object);
|
||||
}
|
||||
break;
|
||||
case "domain-admin":
|
||||
switch ($object) {
|
||||
case "all":
|
||||
$domain_admins = get_domain_admins();
|
||||
if (!empty($domain_admins)) {
|
||||
foreach ($domain_admins as $domain_admin) {
|
||||
$data[] = get_domain_admin_details($domain_admin);
|
||||
}
|
||||
if (!isset($data) || empty($data)) {
|
||||
echo '{}';
|
||||
}
|
||||
else {
|
||||
echo json_encode(mailbox_get_domain_details($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
}
|
||||
}
|
||||
else {
|
||||
echo '{}';
|
||||
}
|
||||
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)
|
||||
) {
|
||||
|
||||
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;
|
||||
}
|
||||
break;
|
||||
case "u2f-registration":
|
||||
if (($_SESSION["mailcow_cc_role"] == "admin" || $_SESSION["mailcow_cc_role"] == "domainadmin") && $_SESSION["mailcow_cc_username"] == $object) {
|
||||
$data = $u2f->getRegisterData(get_u2f_registrations($object));
|
||||
list($req, $sigs) = $data;
|
||||
$_SESSION['regReq'] = json_encode($req);
|
||||
echo 'var req = ' . json_encode($req) . '; var sigs = ' . json_encode($sigs) . ';';
|
||||
}
|
||||
else {
|
||||
echo '{}';
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "get_u2f_auth_challenge":
|
||||
if (!isset($_GET['object'])) { return false; }
|
||||
$object = $_GET['object'];
|
||||
case "u2f-authentication":
|
||||
if (isset($_SESSION['pending_mailcow_cc_username']) && $_SESSION['pending_mailcow_cc_username'] == $object) {
|
||||
$reqs = json_encode($u2f->getAuthenticateData(get_u2f_registrations($object)));
|
||||
$_SESSION['authReq'] = $reqs;
|
||||
echo 'var req = ' . $reqs . ';';
|
||||
}
|
||||
else {
|
||||
echo '{}';
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -36,6 +36,7 @@ $lang['danger']['object_exists'] = 'Objekt %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']['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_modified'] = 'Änderungen an Alias %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']['domain_not_found'] = 'Domain "%s" nicht gefunden.';
|
||||
$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_left_exceeded'] = 'Nicht genügend Speicherplatz vorhanden (Speicherplatz anwendbar: %d MiB)';
|
||||
$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_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_enforce_in'] = 'TLS eingehend 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_resource'] = 'Ressource hinzufügen';
|
||||
$lang['mailbox']['add_alias'] = 'Alias hinzufügen';
|
||||
$lang['mailbox']['empty'] = 'Keine Einträge vorhanden';
|
||||
|
||||
$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']['exclude'] = 'Elemente ausschließen (Regex)';
|
||||
$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']['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']['set_rr_failed'] = 'Kann Postfix Restriktionen nicht setzen';
|
||||
$lang['admin']['no_record'] = 'Kein Eintrag';
|
||||
$lang['admin']['filter_table'] = 'Tabelle Filtern';
|
||||
$lang['admin']['empty'] = 'Keine Einträge vorhanden';
|
||||
?>
|
||||
|
@ -38,6 +38,7 @@ $lang['danger']['object_exists'] = "Object %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']['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_modified'] = "Changes to alias 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']['domain_not_found'] = "Domain not found.";
|
||||
$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_left_exceeded'] = "Not enough space left (space left: %d MiB)";
|
||||
$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_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_enforce_in'] = 'Enforce TLS incoming';
|
||||
$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_alias'] = 'Add alias';
|
||||
$lang['mailbox']['add_domain_record_first'] = 'Please add a domain first';
|
||||
$lang['mailbox']['empty'] = 'No results';
|
||||
|
||||
$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']['exclude'] = 'Exclude objects (regex)';
|
||||
$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']['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']['set_rr_failed'] = 'Cannot set Postfix restrictions';
|
||||
$lang['admin']['no_record'] = 'No record';
|
||||
$lang['admin']['filter_table'] = 'Filter table';
|
||||
$lang['admin']['empty'] = 'No results';
|
||||
?>
|
||||
|
@ -321,6 +321,7 @@ $lang['add']['maxage'] = 'Maximum age of messages that will be polled from remot
|
||||
$lang['add']['subfolder2'] = "Синхронизировать в подпапку по назначению";
|
||||
$lang['add']['exclude'] = "Исключить объекты (regex)";
|
||||
$lang['add']['delete2duplicates'] = "Удалить дубликаты в получателях";
|
||||
$lang['edit']['delete2duplicates'] = "Удалить дубликаты в получателях";
|
||||
$lang['add']['title'] = "Добавить объект";
|
||||
$lang['add']['domain'] = "Домен";
|
||||
$lang['add']['active'] = "Активный";
|
||||
|
@ -5,33 +5,11 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
require_once "inc/header.inc.php";
|
||||
$_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="row">
|
||||
<div class="col-md-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><?=$lang['mailbox']['domains'];?></h3>
|
||||
<div class="pull-right">
|
||||
<?php
|
||||
if ($_SESSION['mailcow_cc_role'] == "admin"):
|
||||
@ -41,6 +19,7 @@ table.footable>tbody>tr.footable-empty>td {
|
||||
endif;
|
||||
?>
|
||||
</div>
|
||||
<h3 class="panel-title"><?=$lang['mailbox']['domains'];?></h3>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table id="domain_table" class="table table-striped"></table>
|
||||
@ -49,14 +28,15 @@ table.footable>tbody>tr.footable-empty>td {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><?=$lang['mailbox']['mailboxes'];?></h3>
|
||||
<div class="pull-right">
|
||||
<a href="/add.php?mailbox"><span class="glyphicon glyphicon-plus"></span></a>
|
||||
</div>
|
||||
<h3 class="panel-title"><?=$lang['mailbox']['mailboxes'];?></h3>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table id="mailbox_table" class="table table-striped"></table>
|
||||
@ -65,14 +45,15 @@ table.footable>tbody>tr.footable-empty>td {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><?=$lang['mailbox']['resources'];?></h3>
|
||||
<div class="pull-right">
|
||||
<a href="/add.php?resource"><span class="glyphicon glyphicon-plus"></span></a>
|
||||
</div>
|
||||
<h3 class="panel-title"><?=$lang['mailbox']['resources'];?></h3>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><?=$lang['mailbox']['domain_aliases'];?></h3>
|
||||
<div class="pull-right">
|
||||
<a href="/add.php?aliasdomain"><span class="glyphicon glyphicon-plus"></span></a>
|
||||
</div>
|
||||
<h3 class="panel-title"><?=$lang['mailbox']['domain_aliases'];?></h3>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<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="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><?=$lang['mailbox']['aliases'];?></h3>
|
||||
<div class="pull-right">
|
||||
<a href="/add.php?alias"><span class="glyphicon glyphicon-plus"></span></a>
|
||||
</div>
|
||||
<h3 class="panel-title"><?=$lang['mailbox']['aliases'];?></h3>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table id="alias_table" class="table table-striped"></table>
|
||||
|
@ -405,7 +405,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="Syncjobs">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped sortable-theme-bootstrap" data-sortable id="timelimitedaliases">
|
||||
<table class="table table-striped" id="timelimitedaliases">
|
||||
<thead>
|
||||
<tr>
|
||||
<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;">Log</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>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -151,10 +151,11 @@ services:
|
||||
depends_on:
|
||||
- bind9-mailcow
|
||||
volumes:
|
||||
- ./data/conf/dovecot:/etc/dovecot
|
||||
- ./data/conf/dovecot:/usr/local/etc/dovecot
|
||||
- ./data/assets/ssl:/etc/ssl/mail/:ro
|
||||
- ./data/conf/sogo/:/etc/sogo/
|
||||
- vmail-vol-1:/var/vmail
|
||||
- crypt-vol-1:/mail_crypt/
|
||||
environment:
|
||||
- DBNAME=${DBNAME}
|
||||
- DBUSER=${DBUSER}
|
||||
@ -184,6 +185,7 @@ services:
|
||||
- ./data/conf/postfix:/opt/postfix/conf
|
||||
- ./data/assets/ssl:/etc/ssl/mail/:ro
|
||||
- postfix-vol-1:/var/spool/postfix
|
||||
- crypt-vol-1:/var/lib/zeyple
|
||||
environment:
|
||||
- DBNAME=${DBNAME}
|
||||
- DBUSER=${DBUSER}
|
||||
@ -266,3 +268,4 @@ volumes:
|
||||
redis-vol-1:
|
||||
rspamd-vol-1:
|
||||
postfix-vol-1:
|
||||
crypt-vol-1:
|
||||
|
@ -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
|
||||
|
||||
When a mailbox is created, a user is allowed to send mail from and receive mail for his own mailbox address.
|
||||
|
@ -1,5 +1,7 @@
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
```
|
||||
docker-compose pull
|
||||
|
@ -215,6 +215,51 @@ source mailcow.conf
|
||||
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
|
||||
|
||||
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`.
|
||||
|
||||
Limit the output by calling logs with `--tail=300` like `docker-compose logs --tail=300 mysql-mailcow`.
|
||||
|
||||
## Redirect port 80 to 443
|
||||
|
||||
Since February the 28th 2017 mailcow does come with port 80 and 443 enabled.
|
||||
|
Loading…
Reference in New Issue
Block a user