Fix conflicts

master
André 2017-11-14 10:48:04 +01:00
commit 7bb1e2e40e
81 changed files with 4149 additions and 773 deletions

View File

@ -2,6 +2,12 @@
set -o pipefail set -o pipefail
exec 5>&1 exec 5>&1
if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
log_f "SKIP_LETS_ENCRYPT=y, skipping Let's Encrypt..."
sleep 365d
exec $(readlink -f "$0")
fi
ACME_BASE=/var/lib/acme ACME_BASE=/var/lib/acme
SSL_EXAMPLE=/var/lib/ssl-example SSL_EXAMPLE=/var/lib/ssl-example
@ -102,11 +108,6 @@ while ! mysqladmin ping --host mysql -u${DBUSER} -p${DBPASS} --silent; do
done done
while true; do while true; do
if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
log_f "SKIP_LETS_ENCRYPT=y, skipping Let's Encrypt..."
sleep 365d
exec $(readlink -f "$0")
fi
if [[ "${SKIP_IP_CHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then if [[ "${SKIP_IP_CHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
SKIP_IP_CHECK=y SKIP_IP_CHECK=y
fi fi

View File

@ -1,6 +1,7 @@
from flask import Flask from flask import Flask
from flask_restful import Resource, Api from flask_restful import Resource, Api
from flask import jsonify from flask import jsonify
from flask import request
from threading import Thread from threading import Thread
import docker import docker
import signal import signal
@ -13,17 +14,23 @@ api = Api(app)
class containers_get(Resource): class containers_get(Resource):
def get(self): def get(self):
containers = {} containers = {}
for container in docker_client.containers.list(all=True): try:
containers.update({container.attrs['Id']: container.attrs}) for container in docker_client.containers.list(all=True):
return containers containers.update({container.attrs['Id']: container.attrs})
return containers
except Exception as e:
return jsonify(type='danger', msg=e)
class container_get(Resource): class container_get(Resource):
def get(self, container_id): def get(self, container_id):
if container_id and container_id.isalnum(): if container_id and container_id.isalnum():
for container in docker_client.containers.list(all=True, filters={"id": container_id}): try:
return container.attrs for container in docker_client.containers.list(all=True, filters={"id": container_id}):
return container.attrs
except Exception as e:
return jsonify(type='danger', msg=e)
else: else:
return jsonify(message='No or invalid id defined') return jsonify(type='danger', msg='no or invalid id defined')
class container_post(Resource): class container_post(Resource):
def post(self, container_id, post_action): def post(self, container_id, post_action):
@ -32,30 +39,51 @@ class container_post(Resource):
try: try:
for container in docker_client.containers.list(all=True, filters={"id": container_id}): for container in docker_client.containers.list(all=True, filters={"id": container_id}):
container.stop() container.stop()
except: return jsonify(type='success', msg='command completed successfully')
return 'Error' except Exception as e:
else: return jsonify(type='danger', msg=e)
return 'OK'
elif post_action == 'start': elif post_action == 'start':
try: try:
for container in docker_client.containers.list(all=True, filters={"id": container_id}): for container in docker_client.containers.list(all=True, filters={"id": container_id}):
container.start() container.start()
except: return jsonify(type='success', msg='command completed successfully')
return 'Error' except Exception as e:
else: return jsonify(type='danger', msg=e)
return 'OK'
elif post_action == 'restart': elif post_action == 'restart':
try: try:
for container in docker_client.containers.list(all=True, filters={"id": container_id}): for container in docker_client.containers.list(all=True, filters={"id": container_id}):
container.restart() container.restart()
except: return jsonify(type='success', msg='command completed successfully')
return 'Error' except Exception as e:
return jsonify(type='danger', msg=e)
elif post_action == 'exec':
if not request.json or not 'cmd' in request.json:
return jsonify(type='danger', msg='cmd is missing')
if request.json['cmd'] == 'sieve_list' and request.json['username']:
try:
for container in docker_client.containers.list(filters={"id": container_id}):
return container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve list -u '" + request.json['username'].replace("'", "'\\''") + "'"], user='vmail')
except Exception as e:
return jsonify(type='danger', msg=e)
elif request.json['cmd'] == 'sieve_print' and request.json['script_name'] and request.json['username']:
try:
for container in docker_client.containers.list(filters={"id": container_id}):
return container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve get -u '" + request.json['username'].replace("'", "'\\''") + "' '" + request.json['script_name'].replace("'", "'\\''") + "'"], user='vmail')
except Exception as e:
return jsonify(type='danger', msg=e)
else: else:
return 'OK' return jsonify(type='danger', msg='Unknown command')
else: else:
return jsonify(message='Invalid action') return jsonify(type='danger', msg='invalid action')
else: else:
return jsonify(message='Invalid container id or missing action') return jsonify(type='danger', msg='invalid container id or missing action')
class GracefulKiller: class GracefulKiller:
kill_now = False kill_now = False

View File

@ -3,8 +3,8 @@ LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ENV LC_ALL C ENV LC_ALL C
ENV DOVECOT_VERSION 2.2.32 ENV DOVECOT_VERSION 2.2.33.2
ENV PIGEONHOLE_VERSION 0.4.20 ENV PIGEONHOLE_VERSION 0.4.21
RUN apt-get update && apt-get -y install \ RUN apt-get update && apt-get -y install \
automake \ automake \
@ -40,10 +40,11 @@ RUN apt-get update && apt-get -y install \
libtest-pod-perl \ libtest-pod-perl \
libtest-simple-perl \ libtest-simple-perl \
libunicode-string-perl \ libunicode-string-perl \
libproc-processtable-perl \ libproc-processtable-perl \
liburi-perl \ liburi-perl \
lzma-dev \ lzma-dev \
make \ make \
procps \
supervisor \ supervisor \
syslog-ng \ syslog-ng \
syslog-ng-core \ syslog-ng-core \
@ -64,7 +65,8 @@ RUN curl https://www.dovecot.org/releases/2.2/dovecot-$DOVECOT_VERSION.tar.gz |
&& make -j3 \ && make -j3 \
&& make install \ && make install \
&& make clean \ && make clean \
&& cd .. && rm -rf dovecot-2.2-pigeonhole-$PIGEONHOLE_VERSION && cd .. \
&& rm -rf dovecot-2.2-pigeonhole-$PIGEONHOLE_VERSION
RUN cpanm Data::Uniqid Mail::IMAPClient String::Util RUN cpanm Data::Uniqid Mail::IMAPClient String::Util
RUN echo '* * * * * root /usr/local/bin/imapsync_cron.pl' > /etc/cron.d/imapsync RUN echo '* * * * * root /usr/local/bin/imapsync_cron.pl' > /etc/cron.d/imapsync
@ -98,8 +100,6 @@ RUN touch /etc/default/locale
RUN apt-get purge -y build-essential automake autotools-dev \ RUN apt-get purge -y build-essential automake autotools-dev \
&& apt-get autoremove --purge -y && apt-get autoremove --purge -y
EXPOSE 24 10001
ENTRYPOINT ["/docker-entrypoint.sh"] ENTRYPOINT ["/docker-entrypoint.sh"]
CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf

View File

@ -15,7 +15,7 @@ sed -i "/^\$DBNAME/c\\\$DBNAME='${DBNAME}';" /usr/local/bin/imapsync_cron.pl
DBPASS=$(echo ${DBPASS} | sed 's/"/\\"/g') DBPASS=$(echo ${DBPASS} | sed 's/"/\\"/g')
# Create quota dict for Dovecot # Create quota dict for Dovecot
cat <<EOF > /usr/local/etc/dovecot/sql/dovecot-dict-sql.conf cat <<EOF > /usr/local/etc/dovecot/sql/dovecot-dict-sql-quota.conf
connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
map { map {
pattern = priv/quota/storage pattern = priv/quota/storage
@ -31,8 +31,54 @@ map {
} }
EOF EOF
# Create dict used for sieve pre and postfilters
cat <<EOF > /usr/local/etc/dovecot/sql/dovecot-dict-sql-sieve_before.conf
connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
map {
pattern = priv/sieve/name/\$script_name
table = sieve_before
username_field = username
value_field = id
fields {
script_name = \$script_name
}
}
map {
pattern = priv/sieve/data/\$id
table = sieve_before
username_field = username
value_field = script_data
fields {
id = \$id
}
}
EOF
cat <<EOF > /usr/local/etc/dovecot/sql/dovecot-dict-sql-sieve_after.conf
connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
map {
pattern = priv/sieve/name/\$script_name
table = sieve_after
username_field = username
value_field = id
fields {
script_name = \$script_name
}
}
map {
pattern = priv/sieve/data/\$id
table = sieve_after
username_field = username
value_field = script_data
fields {
id = \$id
}
}
EOF
# Create user and pass dict for Dovecot # Create user and pass dict for Dovecot
cat <<EOF > /usr/local/etc/dovecot/sql/dovecot-mysql.conf cat <<EOF > /usr/local/etc/dovecot/sql/dovecot-dict-sql-passdb.conf
driver = mysql driver = mysql
connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
default_pass_scheme = SSHA256 default_pass_scheme = SSHA256

View File

@ -54,6 +54,10 @@ while ($row = $sth->fetchrow_arrayref()) {
$delete1 = @$row[12]; $delete1 = @$row[12];
$delete2 = @$row[13]; $delete2 = @$row[13];
$is_running = $dbh->prepare("UPDATE imapsync SET is_running = 1 WHERE id = ?");
$is_running->bind_param( 1, ${id} );
$is_running->execute();
if ($enc1 eq "TLS") { $enc1 = "--tls1"; } elsif ($enc1 eq "SSL") { $enc1 = "--ssl1"; } else { undef $enc1; } if ($enc1 eq "TLS") { $enc1 = "--tls1"; } elsif ($enc1 eq "SSL") { $enc1 = "--ssl1"; } else { undef $enc1; }
my $template = $run_dir . '/imapsync.XXXXXXX'; my $template = $run_dir . '/imapsync.XXXXXXX';
@ -83,7 +87,7 @@ while ($row = $sth->fetchrow_arrayref()) {
"--passfile2", $passfile2->filename, "--passfile2", $passfile2->filename,
'--no-modulesversion'], ">", \my $stdout; '--no-modulesversion'], ">", \my $stdout;
$update = $dbh->prepare("UPDATE imapsync SET returned_text = ?, last_run = NOW() WHERE id = ?"); $update = $dbh->prepare("UPDATE imapsync SET returned_text = ?, last_run = NOW(), is_running = 0 WHERE id = ?");
$update->bind_param( 1, ${stdout} ); $update->bind_param( 1, ${stdout} );
$update->bind_param( 2, ${id} ); $update->bind_param( 2, ${id} );
$update->execute(); $update->execute();

View File

@ -1,5 +1,6 @@
[supervisord] [supervisord]
nodaemon=true nodaemon=true
user=root
[program:syslog-ng] [program:syslog-ng]
command=/usr/sbin/syslog-ng --foreground --no-caps command=/usr/sbin/syslog-ng --foreground --no-caps

View File

@ -147,6 +147,9 @@ def watch():
result = re.search(rule_regex, item['data']) result = re.search(rule_regex, item['data'])
if result: if result:
addr = result.group(1) addr = result.group(1)
ip = ipaddress.ip_address(addr.decode('ascii'))
if ip.is_private or ip.is_loopback:
continue
print "%s matched rule id %d" % (addr, rule_id) print "%s matched rule id %d" % (addr, rule_id)
log['time'] = int(round(time.time())) log['time'] = int(round(time.time()))
log['priority'] = "warn" log['priority'] = "warn"

View File

@ -32,17 +32,20 @@ RUN apk add -U --no-cache libxml2-dev \
imagemagick-dev \ imagemagick-dev \
imagemagick \ imagemagick \
libtool \ libtool \
gettext-dev \
openldap-dev \
librsvg \ librsvg \
&& pear install channel://pear.php.net/Net_IDNA2-0.2.0 \ && pear install channel://pear.php.net/Net_IDNA2-0.2.0 \
channel://pear.php.net/Auth_SASL-1.1.0 \ channel://pear.php.net/Auth_SASL-1.1.0 \
Net_IMAP \ Net_IMAP \
Net_Sieve \
NET_SMTP \ NET_SMTP \
Mail_mime \ Mail_mime \
&& pecl install redis-${REDIS_PECL} memcached-${MEMCACHED_PECL} APCu-${APCU_PECL} imagick-${IMAGICK_PECL} \ && pecl install redis-${REDIS_PECL} memcached-${MEMCACHED_PECL} APCu-${APCU_PECL} imagick-${IMAGICK_PECL} \
&& docker-php-ext-enable redis apcu memcached imagick \ && docker-php-ext-enable redis apcu memcached imagick \
&& pecl clear-cache \ && pecl clear-cache \
&& docker-php-ext-configure intl \ && docker-php-ext-configure intl \
&& docker-php-ext-install -j 4 intl pdo pdo_mysql xmlrpc gd zip pcntl opcache \ && docker-php-ext-install -j 4 intl gettext ldap sockets soap pdo pdo_mysql xmlrpc gd zip pcntl opcache \
&& docker-php-ext-configure imap --with-imap --with-imap-ssl \ && docker-php-ext-configure imap --with-imap --with-imap-ssl \
&& docker-php-ext-install -j 4 imap \ && docker-php-ext-install -j 4 imap \
&& apk del --purge autoconf g++ make libxml2-dev icu-dev imap-dev openssl-dev cyrus-sasl-dev pcre-dev libpng-dev libpng-dev libjpeg-turbo-dev libwebp-dev zlib-dev imagemagick-dev \ && apk del --purge autoconf g++ make libxml2-dev icu-dev imap-dev openssl-dev cyrus-sasl-dev pcre-dev libpng-dev libpng-dev libjpeg-turbo-dev libwebp-dev zlib-dev imagemagick-dev \
@ -56,7 +59,6 @@ RUN apk add -U --no-cache libxml2-dev \
echo 'opcache.revalidate_freq=1'; \ echo 'opcache.revalidate_freq=1'; \
} > /usr/local/etc/php/conf.d/opcache-recommended.ini } > /usr/local/etc/php/conf.d/opcache-recommended.ini
COPY ./docker-entrypoint.sh / COPY ./docker-entrypoint.sh /
EXPOSE 9000 EXPOSE 9000

View File

@ -28,13 +28,19 @@ progress() {
[[ ${CURRENT} -gt ${TOTAL} ]] && return [[ ${CURRENT} -gt ${TOTAL} ]] && return
[[ ${CURRENT} -lt 0 ]] && CURRENT=0 [[ ${CURRENT} -lt 0 ]] && CURRENT=0
PERCENT=$(( 200 * ${CURRENT} / ${TOTAL} % 2 + 100 * ${CURRENT} / ${TOTAL} )) PERCENT=$(( 200 * ${CURRENT} / ${TOTAL} % 2 + 100 * ${CURRENT} / ${TOTAL} ))
echo -ne "$(date) - ${SERVICE} health level: \e[7m${PERCENT}%\e[0m (${CURRENT}/${TOTAL}), health trend: " log_msg "${SERVICE} health level: ${PERCENT}% (${CURRENT}/${TOTAL}), health trend: ${DIFF}"
[[ ${DIFF} =~ ^-[1-9] ]] && echo -en '[\e[41m \e[0m] ' || echo -en '[\e[42m \e[0m] ' log_data "$(printf "%d,%d,%d,%d" ${PERCENT} ${CURRENT} ${TOTAL} ${DIFF})" "${SERVICE}"
echo "(${DIFF})"
} }
log_to_redis() { log_msg() {
redis-cli -h redis LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}")\"}" redis-cli -h redis LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}")\"}" > /dev/null
echo $(date) $(printf '%s\n' "${1}")
}
log_data() {
[[ -z ${1} ]] && return 1
[[ -z ${2} ]] && return 2
redis-cli -h redis LPUSH WATCHDOG_DATA "{\"time\":\"$(date +%s)\",\"service\":\"data\",\"$(printf '%s' "${2}")\":\"$(printf '%s' "${1}")\"}" > /dev/null
} }
function mail_error() { function mail_error() {
@ -43,8 +49,7 @@ function mail_error() {
RCPT_DOMAIN=$(echo ${1} | awk -F @ {'print $NF'}) RCPT_DOMAIN=$(echo ${1} | awk -F @ {'print $NF'})
RCPT_MX=$(dig +short ${RCPT_DOMAIN} mx | sort -n | awk '{print $2; exit}') RCPT_MX=$(dig +short ${RCPT_DOMAIN} mx | sort -n | awk '{print $2; exit}')
if [[ -z ${RCPT_MX} ]]; then if [[ -z ${RCPT_MX} ]]; then
log_to_redis "Cannot determine MX for ${1}, skipping email notification..." log_msg "Cannot determine MX for ${1}, skipping email notification..."
echo "Cannot determine MX for ${1}"
return 1 return 1
fi fi
./smtp-cli --missing-modules-ok \ ./smtp-cli --missing-modules-ok \
@ -54,6 +59,7 @@ function mail_error() {
--from="watchdog@${MAILCOW_HOSTNAME}" \ --from="watchdog@${MAILCOW_HOSTNAME}" \
--server="${RCPT_MX}" \ --server="${RCPT_MX}" \
--hello-host=${MAILCOW_HOSTNAME} --hello-host=${MAILCOW_HOSTNAME}
log_msg "Sent notification email to ${1}"
} }
@ -66,8 +72,8 @@ get_container_ip() {
sleep 1 sleep 1
CONTAINER_ID=$(curl --silent http://dockerapi:8080/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${1}\")) | .id") CONTAINER_ID=$(curl --silent http://dockerapi:8080/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${1}\")) | .id")
if [[ ! -z ${CONTAINER_ID} ]]; then if [[ ! -z ${CONTAINER_ID} ]]; then
CONTAINER_IP=$(curl --silent http://dockerapi:8080/containers/${CONTAINER_ID}/json | jq -r '.NetworkSettings.Networks[].IPAddress') CONTAINER_IP=$(curl --silent http://dockerapi:8080/containers/${CONTAINER_ID}/json | jq -r '.NetworkSettings.Networks[].IPAddress')
fi fi
LOOP_C=$((LOOP_C + 1)) LOOP_C=$((LOOP_C + 1))
done done
[[ ${LOOP_C} -gt 5 ]] && echo 240.0.0.0 || echo ${CONTAINER_IP} [[ ${LOOP_C} -gt 5 ]] && echo 240.0.0.0 || echo ${CONTAINER_IP}
@ -253,9 +259,8 @@ dns_checks() {
( (
while true; do while true; do
if ! nginx_checks; then if ! nginx_checks; then
log_to_redis "Nginx hit error limit" log_msg "Nginx hit error limit"
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "nginx-mailcow" [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "nginx-mailcow"
echo -e "\e[31m$(date) - Nginx hit error limit\e[0m"
echo nginx-mailcow > /tmp/com_pipe echo nginx-mailcow > /tmp/com_pipe
fi fi
done done
@ -265,9 +270,8 @@ BACKGROUND_TASKS+=($!)
( (
while true; do while true; do
if ! mysql_checks; then if ! mysql_checks; then
log_to_redis "MySQL hit error limit" log_msg "MySQL hit error limit"
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "mysql-mailcow" [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "mysql-mailcow"
echo -e "\e[31m$(date) - MySQL hit error limit\e[0m"
echo mysql-mailcow > /tmp/com_pipe echo mysql-mailcow > /tmp/com_pipe
fi fi
done done
@ -277,9 +281,8 @@ BACKGROUND_TASKS+=($!)
( (
while true; do while true; do
if ! phpfpm_checks; then if ! phpfpm_checks; then
log_to_redis "PHP-FPM hit error limit" log_msg "PHP-FPM hit error limit"
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "php-fpm-mailcow" [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "php-fpm-mailcow"
echo -e "\e[31m$(date) - PHP-FPM hit error limit\e[0m"
echo php-fpm-mailcow > /tmp/com_pipe echo php-fpm-mailcow > /tmp/com_pipe
fi fi
done done
@ -289,9 +292,8 @@ BACKGROUND_TASKS+=($!)
( (
while true; do while true; do
if ! sogo_checks; then if ! sogo_checks; then
log_to_redis "SOGo hit error limit" log_msg "SOGo hit error limit"
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "sogo-mailcow" [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "sogo-mailcow"
echo -e "\e[31m$(date) - SOGo hit error limit\e[0m"
echo sogo-mailcow > /tmp/com_pipe echo sogo-mailcow > /tmp/com_pipe
fi fi
done done
@ -301,9 +303,8 @@ BACKGROUND_TASKS+=($!)
( (
while true; do while true; do
if ! postfix_checks; then if ! postfix_checks; then
log_to_redis "Postfix hit error limit" log_msg "Postfix hit error limit"
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "postfix-mailcow" [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "postfix-mailcow"
echo -e "\e[31m$(date) - Postfix hit error limit\e[0m"
echo postfix-mailcow > /tmp/com_pipe echo postfix-mailcow > /tmp/com_pipe
fi fi
done done
@ -313,9 +314,8 @@ BACKGROUND_TASKS+=($!)
( (
while true; do while true; do
if ! dovecot_checks; then if ! dovecot_checks; then
log_to_redis "Dovecot hit error limit" log_msg "Dovecot hit error limit"
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "dovecot-mailcow" [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "dovecot-mailcow"
echo -e "\e[31m$(date) - Dovecot hit error limit\e[0m"
echo dovecot-mailcow > /tmp/com_pipe echo dovecot-mailcow > /tmp/com_pipe
fi fi
done done
@ -325,9 +325,8 @@ BACKGROUND_TASKS+=($!)
( (
while true; do while true; do
if ! dns_checks; then if ! dns_checks; then
log_to_redis "Unbound hit error limit" log_msg "Unbound hit error limit"
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "unbound-mailcow" [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "unbound-mailcow"
echo -e "\e[31m$(date) - Unbound hit error limit\e[0m"
#echo unbound-mailcow > /tmp/com_pipe #echo unbound-mailcow > /tmp/com_pipe
fi fi
done done
@ -337,9 +336,8 @@ BACKGROUND_TASKS+=($!)
( (
while true; do while true; do
if ! rspamd_checks; then if ! rspamd_checks; then
log_to_redis "Rspamd hit error limit" log_msg "Rspamd hit error limit"
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "rspamd-mailcow" [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "rspamd-mailcow"
echo -e "\e[31m$(date) - Rspamd hit error limit\e[0m"
echo rspamd-mailcow > /tmp/com_pipe echo rspamd-mailcow > /tmp/com_pipe
fi fi
done done
@ -351,8 +349,7 @@ BACKGROUND_TASKS+=($!)
while true; do while true; do
for bg_task in ${BACKGROUND_TASKS[*]}; do for bg_task in ${BACKGROUND_TASKS[*]}; do
if ! kill -0 ${bg_task} 1>&2; then if ! kill -0 ${bg_task} 1>&2; then
echo "Worker ${bg_task} died, stopping watchdog and waiting for respawn..." log_msg "Worker ${bg_task} died, stopping watchdog and waiting for respawn..."
log_to_redis "Worker ${bg_task} died, stopping watchdog and waiting for respawn..."
kill -TERM 1 kill -TERM 1
fi fi
sleep 10 sleep 10
@ -366,7 +363,7 @@ while true; do
while nc -z dockerapi 8080; do while nc -z dockerapi 8080; do
sleep 3 sleep 3
done done
echo "Cannot find dockerapi-mailcow, waiting to recover..." log_msg "Cannot find dockerapi-mailcow, waiting to recover..."
kill -STOP ${BACKGROUND_TASKS[*]} kill -STOP ${BACKGROUND_TASKS[*]}
until nc -z dockerapi 8080; do until nc -z dockerapi 8080; do
sleep 3 sleep 3
@ -385,11 +382,10 @@ while true; do
sleep 3 sleep 3
CONTAINER_ID=$(curl --silent http://dockerapi:8080/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${com_pipe_answer}\")) | .id") CONTAINER_ID=$(curl --silent http://dockerapi:8080/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${com_pipe_answer}\")) | .id")
if [[ ! -z ${CONTAINER_ID} ]]; then if [[ ! -z ${CONTAINER_ID} ]]; then
log_to_redis "Sending restart command to ${CONTAINER_ID}..." log_msg "Sending restart command to ${CONTAINER_ID}..."
echo "Sending restart command to ${CONTAINER_ID}..."
curl --silent -XPOST http://dockerapi:8080/containers/${CONTAINER_ID}/restart curl --silent -XPOST http://dockerapi:8080/containers/${CONTAINER_ID}/restart
fi fi
echo "Wait for restarted container to settle and continue watching..." log_msg "Wait for restarted container to settle and continue watching..."
sleep 30s sleep 30s
kill -CONT ${BACKGROUND_TASKS[*]} kill -CONT ${BACKGROUND_TASKS[*]}
kill -USR1 ${BACKGROUND_TASKS[*]} kill -USR1 ${BACKGROUND_TASKS[*]}

View File

@ -32,7 +32,7 @@ passdb {
pass = yes pass = yes
} }
passdb { passdb {
args = /usr/local/etc/dovecot/sql/dovecot-mysql.conf args = /usr/local/etc/dovecot/sql/dovecot-dict-sql-passdb.conf
driver = sql driver = sql
} }
# Set doveadm_password=your-secret-password in data/conf/dovecot/extra.conf (create if missing) # Set doveadm_password=your-secret-password in data/conf/dovecot/extra.conf (create if missing)
@ -173,6 +173,9 @@ service dict {
group = vmail group = vmail
} }
} }
service log {
user = dovenull
}
service auth { service auth {
inet_listener auth-inet { inet_listener auth-inet {
port = 10001 port = 10001
@ -185,7 +188,6 @@ service auth {
mode = 0600 mode = 0600
user = vmail user = vmail
} }
user = root
} }
service managesieve-login { service managesieve-login {
inet_listener sieve { inet_listener sieve {
@ -193,10 +195,19 @@ service managesieve-login {
} }
service_count = 1 service_count = 1
process_min_avail = 2 process_min_avail = 2
vsz_limit = 128M vsz_limit = 64M
}
service imap-login {
service_count = 1
vsz_limit = 64M
user = dovenull
}
service pop3-login {
service_count = 1
} }
service imap { service imap {
executable = imap imap-postlogin executable = imap imap-postlogin
user = dovenull
} }
service managesieve { service managesieve {
process_limit = 256 process_limit = 256
@ -211,7 +222,7 @@ listen = *,[::]
ssl_cert = </etc/ssl/mail/cert.pem ssl_cert = </etc/ssl/mail/cert.pem
ssl_key = </etc/ssl/mail/key.pem ssl_key = </etc/ssl/mail/key.pem
userdb { userdb {
args = /usr/local/etc/dovecot/sql/dovecot-mysql.conf args = /usr/local/etc/dovecot/sql/dovecot-dict-sql-passdb.conf
driver = sql driver = sql
} }
protocol imap { protocol imap {
@ -245,17 +256,21 @@ plugin {
# END # END
sieve_pipe_bin_dir = /usr/local/lib/dovecot/sieve sieve_pipe_bin_dir = /usr/local/lib/dovecot/sieve
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute
sieve_after = /var/vmail/sieve/global.sieve
sieve_max_script_size = 1M sieve_max_script_size = 1M
sieve_quota_max_scripts = 0 sieve_quota_max_scripts = 0
sieve_quota_max_storage = 0 sieve_quota_max_storage = 0
listescape_char = "\\" listescape_char = "\\"
sieve_before = dict:proxy::sieve_before;name=active;bindir=/var/vmail/sieve_before_bindir
sieve_after = dict:proxy::sieve_after;name=active;bindir=/var/vmail/sieve_after_bindir
sieve_after2 = /var/vmail/sieve/global.sieve
#mail_crypt_global_private_key = </mail_crypt/ecprivkey.pem #mail_crypt_global_private_key = </mail_crypt/ecprivkey.pem
#mail_crypt_global_public_key = </mail_crypt/ecpubkey.pem #mail_crypt_global_public_key = </mail_crypt/ecpubkey.pem
#mail_crypt_save_version = 2 #mail_crypt_save_version = 2
} }
dict { dict {
sqlquota = mysql:/usr/local/etc/dovecot/sql/dovecot-dict-sql.conf sqlquota = mysql:/usr/local/etc/dovecot/sql/dovecot-dict-sql-quota.conf
sieve_after = mysql:/usr/local/etc/dovecot/sql/dovecot-dict-sql-sieve_after.conf
sieve_before = mysql:/usr/local/etc/dovecot/sql/dovecot-dict-sql-sieve_before.conf
} }
remote 127.0.0.1 { remote 127.0.0.1 {
disable_plaintext_auth = no disable_plaintext_auth = no

View File

@ -1,7 +1,7 @@
dns { dns {
enable_dnssec = true; enable_dnssec = true;
} }
map_watch_interval = 60s; map_watch_interval = 30s;
dns { dns {
timeout = 4s; timeout = 4s;
retransmits = 5; retransmits = 5;

View File

@ -43,7 +43,9 @@
SOGoInternalSyncInterval = 30; SOGoInternalSyncInterval = 30;
SOGoMaximumSyncInterval = 354; SOGoMaximumSyncInterval = 354;
// 100 seems to break some Android clients
//SOGoMaximumSyncWindowSize = 100; //SOGoMaximumSyncWindowSize = 100;
// This should do the trick for Outlook 2016
SOGoMaximumSyncResponseSize = 2048; SOGoMaximumSyncResponseSize = 2048;
WOWatchDogRequestTimeout = 10; WOWatchDogRequestTimeout = 10;

View File

@ -457,12 +457,11 @@ $tfa_data = get_tfa();
<div role="tabpanel" class="tab-pane" id="tab-postfix-logs"> <div role="tabpanel" class="tab-pane" id="tab-postfix-logs">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading">Postfix <div class="panel-heading">Postfix <span class="badge badge-info log-lines"></span>
<div class="btn-group pull-right"> <div class="btn-group pull-right">
<a class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['admin']['action'];?> <span class="caret"></span></a> <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="postfix_log" data-log-url="postfix" data-nrows="100">+ 100</button>
<ul class="dropdown-menu"> <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="postfix_log" data-log-url="postfix" data-nrows="1000">+ 1000</button>
<li><a href="#" id="refresh_postfix_log"><?=$lang['admin']['refresh'];?></a></li> <button class="btn btn-xs btn-default" id="refresh_postfix_log"><?=$lang['admin']['refresh'];?></button>
</ul>
</div> </div>
</div> </div>
<div class="panel-body"> <div class="panel-body">
@ -475,12 +474,11 @@ $tfa_data = get_tfa();
<div role="tabpanel" class="tab-pane" id="tab-dovecot-logs"> <div role="tabpanel" class="tab-pane" id="tab-dovecot-logs">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading">Dovecot <div class="panel-heading">Dovecot <span class="badge badge-info log-lines"></span>
<div class="btn-group pull-right"> <div class="btn-group pull-right">
<a class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['admin']['action'];?> <span class="caret"></span></a> <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="dovecot_log" data-log-url="dovecot" data-nrows="100">+ 100</button>
<ul class="dropdown-menu"> <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="dovecot_log" data-log-url="dovecot" data-nrows="1000">+ 1000</button>
<li><a href="#" id="refresh_dovecot_log"><?=$lang['admin']['refresh'];?></a></li> <button class="btn btn-xs btn-default" id="refresh_dovecot_log"><?=$lang['admin']['refresh'];?></button>
</ul>
</div> </div>
</div> </div>
<div class="panel-body"> <div class="panel-body">
@ -493,12 +491,11 @@ $tfa_data = get_tfa();
<div role="tabpanel" class="tab-pane" id="tab-sogo-logs"> <div role="tabpanel" class="tab-pane" id="tab-sogo-logs">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading">SOGo <div class="panel-heading">SOGo <span class="badge badge-info log-lines"></span>
<div class="btn-group pull-right"> <div class="btn-group pull-right">
<a class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['admin']['action'];?> <span class="caret"></span></a> <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="sogo_log" data-log-url="sogo" data-nrows="100">+ 100</button>
<ul class="dropdown-menu"> <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="sogo_log" data-log-url="sogo" data-nrows="1000">+ 1000</button>
<li><a href="#" id="refresh_sogo_log"><?=$lang['admin']['refresh'];?></a></li> <button class="btn btn-xs btn-default" id="refresh_sogo_log"><?=$lang['admin']['refresh'];?></button>
</ul>
</div> </div>
</div> </div>
<div class="panel-body"> <div class="panel-body">
@ -511,12 +508,11 @@ $tfa_data = get_tfa();
<div role="tabpanel" class="tab-pane" id="tab-fail2ban-logs"> <div role="tabpanel" class="tab-pane" id="tab-fail2ban-logs">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading">Fail2ban <div class="panel-heading">Fail2ban <span class="badge badge-info log-lines"></span>
<div class="btn-group pull-right"> <div class="btn-group pull-right">
<a class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['admin']['action'];?> <span class="caret"></span></a> <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="fail2ban_log" data-log-url="fail2ban" data-nrows="100">+ 100</button>
<ul class="dropdown-menu"> <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="fail2ban_log" data-log-url="fail2ban" data-nrows="1000">+ 1000</button>
<li><a href="#" id="refresh_fail2ban_log"><?=$lang['admin']['refresh'];?></a></li> <button class="btn btn-xs btn-default" id="refresh_fail2ban_log"><?=$lang['admin']['refresh'];?></button>
</ul>
</div> </div>
</div> </div>
<div class="panel-body"> <div class="panel-body">
@ -529,17 +525,16 @@ $tfa_data = get_tfa();
<div role="tabpanel" class="tab-pane" id="tab-rspamd-history"> <div role="tabpanel" class="tab-pane" id="tab-rspamd-history">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading">Rspamd history <div class="panel-heading">Rspamd history <span class="badge badge-info log-lines"></span>
<div class="btn-group pull-right"> <div class="btn-group pull-right">
<a class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['admin']['action'];?> <span class="caret"></span></a> <button class="btn btn-xs btn-default add_log_lines" data-post-process="rspamd_history" data-table="rspamd_history" data-log-url="rspamd-history" data-nrows="100">+ 100</button>
<ul class="dropdown-menu"> <button class="btn btn-xs btn-default add_log_lines" data-post-process="rspamd_history" data-table="rspamd_history" data-log-url="rspamd-history" data-nrows="1000">+ 1000</button>
<li><a href="#" id="refresh_rspamd_history"><?=$lang['admin']['refresh'];?></a></li> <button class="btn btn-xs btn-default" id="refresh_rspamd_history"><?=$lang['admin']['refresh'];?></button>
</ul>
</div> </div>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-condensed" id="rspamd_history"></table> <table class="table table-striped table-condensed log-table" id="rspamd_history"></table>
</div> </div>
</div> </div>
</div> </div>
@ -547,12 +542,11 @@ $tfa_data = get_tfa();
<div role="tabpanel" class="tab-pane" id="tab-autodiscover-logs"> <div role="tabpanel" class="tab-pane" id="tab-autodiscover-logs">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading">Autodiscover <div class="panel-heading">Autodiscover <span class="badge badge-info log-lines"></span>
<div class="btn-group pull-right"> <div class="btn-group pull-right">
<a class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['admin']['action'];?> <span class="caret"></span></a> <button class="btn btn-xs btn-default add_log_lines" data-post-process="autodiscover_log" data-table="autodiscover_log" data-log-url="autodiscover" data-nrows="100">+ 100</button>
<ul class="dropdown-menu"> <button class="btn btn-xs btn-default add_log_lines" data-post-process="autodiscover_log" data-table="autodiscover_log" data-log-url="autodiscover" data-nrows="1000">+ 1000</button>
<li><a href="#" id="refresh_autodiscover_log"><?=$lang['admin']['refresh'];?></a></li> <button class="btn btn-xs btn-default" id="refresh_autodiscover_log"><?=$lang['admin']['refresh'];?></button>
</ul>
</div> </div>
</div> </div>
<div class="panel-body"> <div class="panel-body">

View File

@ -35,7 +35,8 @@ $opt = [
]; ];
$pdo = new PDO($dsn, $database_user, $database_pass, $opt); $pdo = new PDO($dsn, $database_user, $database_pass, $opt);
$login_user = strtolower(trim($_SERVER['PHP_AUTH_USER'])); $login_user = strtolower(trim($_SERVER['PHP_AUTH_USER']));
$login_role = check_login($login_user, $_SERVER['PHP_AUTH_PW']); $login_pass = trim(htmlspecialchars_decode($_SERVER['PHP_AUTH_PW']));
$login_role = check_login($login_user, $login_pass);
if (!isset($_SERVER['PHP_AUTH_USER']) OR $login_role !== "user") { if (!isset($_SERVER['PHP_AUTH_USER']) OR $login_role !== "user") {
try { try {

File diff suppressed because one or more lines are too long

View File

@ -34,4 +34,4 @@ table.footable>tbody>tr.footable-empty>td {
} }
.inputMissingAttr { .inputMissingAttr {
border-color: #FF4136; border-color: #FF4136;
} }

View File

@ -0,0 +1 @@
div.numberedtextarea-wrapper{position:relative}div.numberedtextarea-wrapper textarea{display:block;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}div.numberedtextarea-line-numbers{position:absolute;top:0;left:0;right:0;bottom:0;width:50px;border-right:none;color:rgba(0,0,0,.4);overflow:hidden}div.numberedtextarea-number{padding-right:6px;text-align:right}textarea#script_data{font-family:Monospace}

View File

@ -30,3 +30,10 @@ table.footable>tbody>tr.footable-empty>td {
.inputMissingAttr { .inputMissingAttr {
border-color: #FF4136; border-color: #FF4136;
} }
#logText {
white-space: pre-wrap;
white-space: -moz-pre-wrap;
white-space: -pre-wrap;
white-space: -o-pre-wrap;
word-wrap: break-word;
}

View File

@ -0,0 +1,241 @@
<?php
require_once 'inc/prerequisites.inc.php';
require_once 'inc/spf.inc.php';
define('state_good', "&#10003;");
define('state_missing', "&#x2717;");
define('state_nomatch', "?");
define('state_optional', "(optional)");
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") {
require_once("inc/header.inc.php");
$ch = curl_init('http://ip4.mailcow.email');
curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
curl_setopt($ch, CURLOPT_VERBOSE, false);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);
$ip = curl_exec($ch);
curl_close($ch);
$ch = curl_init('http://ip6.mailcow.email');
curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
curl_setopt($ch, CURLOPT_VERBOSE, false);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);
$ip6 = curl_exec($ch);
curl_close($ch);
$ptr = implode('.', array_reverse(explode('.', $ip))) . '.in-addr.arpa';
if (!empty($ip6)) {
$ip6_full = str_replace('::', str_repeat(':', 9-substr_count($ip6, ':')), $ip6);
$ip6_full = str_replace('::', ':0:', $ip6_full);
$ip6_full = str_replace('::', ':0:', $ip6_full);
$ptr6 = '';
foreach (explode(':', $ip6_full) as $part) {
$ptr6 .= str_pad($part, 4, '0', STR_PAD_LEFT);
}
$ptr6 = implode('.', array_reverse(str_split($ptr6, 1))) . '.ip6.arpa';
}
$https_port = strpos($_SERVER['HTTP_HOST'], ':');
if ($https_port === FALSE) {
$https_port = 443;
} else {
$https_port = substr($_SERVER['HTTP_HOST'], $https_port+1);
}
$records = array();
$records[] = array($mailcow_hostname, 'A', $ip);
$records[] = array($ptr, 'PTR', $mailcow_hostname);
if (!empty($ip6)) {
$records[] = array($mailcow_hostname, 'AAAA', $ip6);
$records[] = array($ptr6, 'PTR', $mailcow_hostname);
}
$domains = mailbox('get', 'domains');
foreach(mailbox('get', 'domains') as $domain) {
$domains = array_merge($domains, mailbox('get', 'alias_domains', $domain));
}
if (!isset($autodiscover_config['sieve'])) {
$autodiscover_config['sieve'] = array('server' => $mailcow_hostname, 'port' => array_pop(explode(':', getenv('SIEVE_PORT'))));
}
$records[] = array('_25._tcp.' . $autodiscover_config['smtp']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['smtp']['server'], 25, 1));
$records[] = array('_' . $https_port . '._tcp.' . $mailcow_hostname, 'TLSA', generate_tlsa_digest($mailcow_hostname, $https_port));
$records[] = array('_' . $autodiscover_config['pop3']['tlsport'] . '._tcp.' . $autodiscover_config['pop3']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['pop3']['server'], $autodiscover_config['pop3']['tlsport'], 1));
$records[] = array('_' . $autodiscover_config['imap']['tlsport'] . '._tcp.' . $autodiscover_config['imap']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['imap']['server'], $autodiscover_config['imap']['tlsport'], 1));
$records[] = array('_' . $autodiscover_config['smtp']['port'] . '._tcp.' . $autodiscover_config['smtp']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['smtp']['server'], $autodiscover_config['smtp']['port']));
$records[] = array('_' . $autodiscover_config['smtp']['tlsport'] . '._tcp.' . $autodiscover_config['smtp']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['smtp']['server'], $autodiscover_config['smtp']['tlsport'], 1));
$records[] = array('_' . $autodiscover_config['imap']['port'] . '._tcp.' . $autodiscover_config['imap']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['imap']['server'], $autodiscover_config['imap']['port']));
$records[] = array('_' . $autodiscover_config['pop3']['port'] . '._tcp.' . $autodiscover_config['pop3']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['pop3']['server'], $autodiscover_config['pop3']['port']));
$records[] = array('_' . $autodiscover_config['sieve']['port'] . '._tcp.' . $autodiscover_config['sieve']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['sieve']['server'], $autodiscover_config['sieve']['port'], 1));
foreach ($domains as $domain) {
$records[] = array($domain, 'MX', $mailcow_hostname);
$records[] = array('autodiscover.' . $domain, 'CNAME', $mailcow_hostname);
$records[] = array('_autodiscover._tcp.' . $domain, 'SRV', $mailcow_hostname . ' ' . $https_port);
$records[] = array('autoconfig.' . $domain, 'CNAME', $mailcow_hostname);
$records[] = array($domain, 'TXT', '<a href="http://www.openspf.org/SPF_Record_Syntax" target="_blank">SPF Record Syntax</a>', state_optional);
$records[] = array('_dmarc.' . $domain, 'TXT', '<a href="http://www.kitterman.com/dmarc/assistant.html" target="_blank">DMARC Assistant</a>', state_optional);
if (!empty($dkim = dkim('details', $domain))) {
$records[] = array($dkim['dkim_selector'] . '._domainkey.' . $domain, 'TXT', $dkim['dkim_txt']);
}
$current_records = dns_get_record('_pop3._tcp.' . $domain, DNS_SRV);
if (count($current_records) == 0 || $current_records[0]['target'] != '') {
if ($autodiscover_config['pop3']['tlsport'] != '110') {
$records[] = array('_pop3._tcp.' . $domain, 'SRV', $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['tlsport']);
}
} else {
$records[] = array('_pop3._tcp.' . $domain, 'SRV', '. 0');
}
$current_records = dns_get_record('_pop3s._tcp.' . $domain, DNS_SRV);
if (count($current_records) == 0 || $current_records[0]['target'] != '') {
if ($autodiscover_config['pop3']['port'] != '995') {
$records[] = array('_pop3s._tcp.' . $domain, 'SRV', $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['port']);
}
} else {
$records[] = array('_pop3s._tcp.' . $domain, 'SRV', '. 0');
}
if ($autodiscover_config['imap']['tlsport'] != '143') {
$records[] = array('_imap._tcp.' . $domain, 'SRV', $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['tlsport']);
}
if ($autodiscover_config['imap']['port'] != '993') {
$records[] = array('_imaps._tcp.' . $domain, 'SRV', $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['port']);
}
if ($autodiscover_config['smtp']['tlsport'] != '587') {
$records[] = array('_submission._tcp.' . $domain, 'SRV', $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['tlsport']);
}
if ($autodiscover_config['smtp']['port'] != '465') {
$records[] = array('_smtps._tcp.' . $domain, 'SRV', $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['port']);
}
if ($autodiscover_config['sieve']['port'] != '4190') {
$records[] = array('_sieve._tcp.' . $domain, 'SRV', $autodiscover_config['sieve']['server'] . ' ' . $autodiscover_config['sieve']['port']);
}
}
$record_types = array(
'A' => DNS_A,
'AAAA' => DNS_AAAA,
'CNAME' => DNS_CNAME,
'MX' => DNS_MX,
'PTR' => DNS_PTR,
'SRV' => DNS_SRV,
'TXT' => DNS_TXT,
);
$data_field = array(
'A' => 'ip',
'AAAA' => 'ipv6',
'CNAME' => 'target',
'MX' => 'target',
'PTR' => 'target',
'SRV' => 'data',
'TLSA' => 'data',
'TXT' => 'txt',
);
?>
<div class="container">
<h3><?=$lang['diagnostics']['dns_records'];?></h3>
<p><?=$lang['diagnostics']['dns_records_24hours'];?></p>
<div class="table-responsive" id="dnstable">
<table class="table table-striped">
<tr> <th><?=$lang['diagnostics']['dns_records_name'];?></th> <th><?=$lang['diagnostics']['dns_records_type'];?></th> <th><?=$lang['diagnostics']['dns_records_data'];?></th ><th><?=$lang['diagnostics']['dns_records_status'];?></th> </tr>
<?php
foreach ($records as $record)
{
$record[1] = strtoupper($record[1]);
$state = state_missing;
if ($record[1] == 'TLSA') {
$currents = dns_get_record($record[0], 52, $_, $_, TRUE);
foreach ($currents as &$current) {
$current['type'] = 'TLSA';
$current['cert_usage'] = hexdec(bin2hex($current['data']{0}));
$current['selector'] = hexdec(bin2hex($current['data']{1}));
$current['match_type'] = hexdec(bin2hex($current['data']{2}));
$current['cert_data'] = bin2hex(substr($current['data'], 3));
$current['data'] = $current['cert_usage'] . ' ' . $current['selector'] . ' ' . $current['match_type'] . ' ' . $current['cert_data'];
}
unset($current);
}
else {
$currents = dns_get_record($record[0], $record_types[$record[1]]);
if ($record[1] == 'SRV') {
foreach ($currents as &$current) {
if ($current['target'] == '') {
$current['target'] = '.';
$current['port'] = '0';
}
$current['data'] = $current['target'] . ' ' . $current['port'];
}
unset($current);
}
}
if ($record[1] == 'CNAME' && count($currents) == 0) {
// A and AAAA are also valid instead of CNAME
$a = dns_get_record($record[0], DNS_A);
$cname = dns_get_record($record[2], DNS_A);
if (count($a) > 0 && count($cname) > 0) {
if ($a[0]['ip'] == $cname[0]['ip']) {
$currents = array(array('host' => $record[0], 'class' => 'IN', 'type' => 'CNAME', 'target' => $record[2]));
$aaaa = dns_get_record($record[0], DNS_AAAA);
$cname = dns_get_record($record[2], DNS_AAAA);
if (count($aaaa) == 0 || count($cname) == 0 || $aaaa[0]['ipv6'] != $cname[0]['ipv6']) {
$currents[0]['target'] = $aaaa[0]['ipv6'];
}
} else {
$currents = array(array('host' => $record[0], 'class' => 'IN', 'type' => 'CNAME', 'target' => $a[0]['ip']));
}
}
}
foreach ($currents as $current) {
$current['type'] == strtoupper($current['type']);
if ($current['type'] != $record[1])
{
continue;
}
elseif ($current['type'] == 'TXT' && strpos($record[0], '_dmarc.') === 0) {
$state = state_optional . '<br />' . $current[$data_field[$current['type']]];
}
else if ($current['type'] == 'TXT' && strpos($current['txt'], 'v=spf1') === 0) {
$state = state_optional . '<br />' . $current[$data_field[$current['type']]];
}
else if ($current['type'] != 'TXT' && isset($data_field[$current['type']]) && $state != state_good) {
$state = state_nomatch;
if ($current[$data_field[$current['type']]] == $record[2])
$state = state_good;
}
}
if (isset($record[3]) && $record[3] == state_optional && ($state == state_missing || $state == state_nomatch)) {
$state = state_optional;
}
if ($state == state_nomatch) {
$state = array();
foreach ($currents as $current) {
$state[] = $current[$data_field[$current['type']]];
}
$state = implode('<br />', $state);
}
echo sprintf('<tr><td>%s</td><td>%s</td><td style="max-width: 300px; word-break: break-all">%s</td><td style="max-width: 150px; word-break: break-all">%s</td></tr>', $record[0], $record[1], $record[2], $state);
}
?>
</table>
</div>
</div>
<?php
require_once("inc/footer.inc.php");
} else {
header('Location: index.php');
exit();
}
?>

View File

@ -484,19 +484,25 @@ if (isset($_SESSION['mailcow_cc_role'])) {
</form> </form>
<hr> <hr>
<form data-id="mboxratelimit" class="form-inline well" method="post"> <form data-id="mboxratelimit" class="form-inline well" method="post">
<div class="form-group"> <div class="row">
<label class="control-label">Ratelimit</label> <div class="col-sm-1">
<input name="rl_value" id="rl_value" type="number" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" class="form-control" placeholder="disabled"> <p class="help-block">Ratelimit</p>
</div> </div>
<div class="form-group"> <div class="col-sm-10">
<select name="rl_frame" id="rl_frame" class="form-control"> <div class="form-group">
<option value="s" <?=(isset($rl['frame']) && $rl['frame'] == 's') ? 'selected' : null;?>>msgs / second</option> <input name="rl_value" id="rl_value" type="number" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" class="form-control" placeholder="disabled">
<option value="m" <?=(isset($rl['frame']) && $rl['frame'] == 'm') ? 'selected' : null;?>>msgs / minute</option> </div>
<option value="h" <?=(isset($rl['frame']) && $rl['frame'] == 'h') ? 'selected' : null;?>>msgs / hour</option> <div class="form-group">
</select> <select name="rl_frame" id="rl_frame" class="form-control">
</div> <option value="s" <?=(isset($rl['frame']) && $rl['frame'] == 's') ? 'selected' : null;?>>msgs / second</option>
<div class="form-group"> <option value="m" <?=(isset($rl['frame']) && $rl['frame'] == 'm') ? 'selected' : null;?>>msgs / minute</option>
<button class="btn btn-default" id="edit_selected" data-id="mboxratelimit" data-item="<?=$mailbox;?>" data-api-url='edit/ratelimit' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button> <option value="h" <?=(isset($rl['frame']) && $rl['frame'] == 'h') ? 'selected' : null;?>>msgs / hour</option>
</select>
</div>
<div class="form-group">
<button class="btn btn-default" id="edit_selected" data-id="mboxratelimit" data-item="<?=$mailbox;?>" data-api-url='edit/ratelimit' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
</div>
</div>
</div> </div>
</form> </form>
<?php <?php
@ -717,6 +723,59 @@ if (isset($_SESSION['mailcow_cc_role'])) {
} }
} }
} }
if ($_SESSION['mailcow_cc_role'] == "admin" || $_SESSION['mailcow_cc_role'] == "domainadmin" || $_SESSION['mailcow_cc_role'] == "user") {
if (isset($_GET['filter']) &&
is_numeric($_GET['filter'])) {
$id = $_GET["filter"];
$result = mailbox('get', 'filter_details', $id);
if (!empty($result)) {
?>
<h4>Filter</h4>
<form class="form-horizontal" data-id="editfilter" role="form" method="post">
<input type="hidden" value="0" name="active">
<div class="form-group">
<label class="control-label col-sm-2" for="script_desc"><?=$lang['edit']['sieve_desc'];?>:</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="script_desc" id="script_desc" value="<?=htmlspecialchars($result['script_desc'], ENT_QUOTES, 'UTF-8');?>" required maxlength="255">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="filter_type"><?=$lang['edit']['sieve_type'];?>:</label>
<div class="col-sm-10">
<select id="addFilterType" name="filter_type" id="filter_type" required>
<option value="prefilter" <?=($result['filter_type'] == 'prefilter') ? 'selected' : null;?>>Prefilter</option>
<option value="postfilter" <?=($result['filter_type'] == 'postfilter') ? 'selected' : null;?>>Postfilter</option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="script_data">Script:</label>
<div class="col-sm-10">
<textarea spellcheck="false" autocorrect="off" autocapitalize="none" class="form-control" rows="20" id="script_data" name="script_data" required><?=$result['script_data'];?></textarea>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label><input type="checkbox" value="1" name="active" <?=($result['active_int']=="1") ? "checked" : "";?>> <?=$lang['edit']['active'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-success" id="edit_selected" data-id="editfilter" data-item="<?=htmlspecialchars($result['id']);?>" data-api-url='edit/filter' data-api-attr='{}' href="#"><?=$lang['edit']['validate_save'];?></button>
</div>
</div>
</form>
<?php
}
else {
?>
<div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
<?php
}
}
}
} }
else { else {
?> ?>

View File

@ -0,0 +1,23 @@
<?php
session_start();
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
header('Content-Type: application/json');
if (!isset($_SESSION['mailcow_cc_role'])) {
exit();
}
if (isset($_GET['script'])) {
$sieve = new Sieve\SieveParser();
try {
if (empty($_GET['script'])) {
echo json_encode(array('type' => 'danger', 'msg' => 'Script cannot be empty'));
exit();
}
$sieve->parse($_GET['script']);
}
catch (Exception $e) {
echo json_encode(array('type' => 'danger', 'msg' => $e->getMessage()));
exit();
}
echo json_encode(array('type' => 'success', 'msg' => $lang['add']['validation_success']));
}
?>

View File

@ -0,0 +1,39 @@
<?php
session_start();
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
header('Content-Type: text/html; charset=utf-8');
if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != 'admin') {
exit();
}
if ($_GET['ACTION'] == "start") {
$retry = 0;
while (docker('sogo-mailcow', 'info')['State']['Running'] != 1 && $retry <= 3) {
$response = docker('sogo-mailcow', 'post', 'start');
$response = json_decode($response, true);
$last_response = ($response['type'] == "success") ? '<b><span class="pull-right text-success">OK</span></b>' : '<b><span class="pull-right text-danger">Error: ' . $response['msg'] . '</span></b>';
if ($response['type'] == "success") {
break;
}
usleep(1500000);
$retry++;
}
echo (!isset($last_response)) ? '<b><span class="pull-right text-warning">Already running</span></b>' : $last_response;
}
if ($_GET['ACTION'] == "stop") {
$retry = 0;
while (docker('sogo-mailcow', 'info')['State']['Running'] == 1 && $retry <= 3) {
$response = docker('sogo-mailcow', 'post', 'stop');
$response = json_decode($response, true);
$last_response = ($response['type'] == "success") ? '<b><span class="pull-right text-success">OK</span></b>' : '<b><span class="pull-right text-danger">Error: ' . $response['msg'] . '</span></b>';
if ($response['type'] == "success") {
break;
}
usleep(1500000);
$retry++;
}
echo (!isset($last_response)) ? '<b><span class="pull-right text-warning">Not running</span></b>' : $last_response;
}
?>

View File

@ -0,0 +1,15 @@
<?php
session_start();
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
header('Content-Type: text/plain');
if (!isset($_SESSION['mailcow_cc_role'])) {
exit();
}
if (isset($_GET['id']) && is_numeric($_GET['id'])) {
if ($details = mailbox('get', 'syncjob_details', intval($_GET['id']))) {
echo (empty($details['log'])) ? '-' : $details['log'];
}
}
?>

View File

@ -8,6 +8,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/footer.php';
<script src="/js/bootstrap-select.min.js"></script> <script src="/js/bootstrap-select.min.js"></script>
<script src="/js/bootstrap-filestyle.min.js"></script> <script src="/js/bootstrap-filestyle.min.js"></script>
<script src="/js/notifications.min.js"></script> <script src="/js/notifications.min.js"></script>
<script src="/js/numberedtextarea.min.js"></script>
<script src="/js/u2f-api.js"></script> <script src="/js/u2f-api.js"></script>
<script src="/js/api.js"></script> <script src="/js/api.js"></script>
<script> <script>
@ -21,12 +22,17 @@ function setLang(sel) {
} }
$(document).ready(function() { $(document).ready(function() {
function mailcow_alert_box(message, type) { window.mailcow_alert_box = function(message, type) {
msg = $('<span/>').html(message).text(); msg = $('<span/>').html(message).text();
$.notify({message: msg},{type: type,placement: {from: "bottom",align: "right"},animate: {enter: 'animated fadeInUp',exit: 'animated fadeOutDown'}}); if (type == 'danger') {
auto_hide = 0;
} else {
auto_hide = 5000;
}
$.notify({message: msg},{z_index: 20000, delay: auto_hide, type: type,placement: {from: "bottom",align: "right"},animate: {enter: 'animated fadeInUp',exit: 'animated fadeOutDown'}});
} }
<?php if (isset($_SESSION['return'])): ?> <?php if (isset($_SESSION['return'])): ?>
mailcow_alert_box("<?= $_SESSION['return']['msg']; ?>", "<?= $_SESSION['return']['type']; ?>"); mailcow_alert_box(<?=json_encode($_SESSION['return']['msg']); ?>, "<?= $_SESSION['return']['type']; ?>");
<?php endif; unset($_SESSION['return']); ?> <?php endif; unset($_SESSION['return']); ?>
// Confirm TFA modal // Confirm TFA modal
<?php if (isset($_SESSION['pending_tfa_method'])):?> <?php if (isset($_SESSION['pending_tfa_method'])):?>
@ -170,7 +176,7 @@ $(document).ready(function() {
$('#statusTriggerRestartSogo').text('Stopping SOGo workers, this may take a while... '); $('#statusTriggerRestartSogo').text('Stopping SOGo workers, this may take a while... ');
$.ajax({ $.ajax({
method: 'get', method: 'get',
url: '/inc/call_sogo_ctrl.php', url: '/inc/ajax/sogo_ctrl.php',
data: { data: {
'ajax': true, 'ajax': true,
'ACTION': 'stop' 'ACTION': 'stop'
@ -180,7 +186,7 @@ $(document).ready(function() {
$('#statusTriggerRestartSogo').append('<br>Starting SOGo...'); $('#statusTriggerRestartSogo').append('<br>Starting SOGo...');
$.ajax({ $.ajax({
method: 'get', method: 'get',
url: '/inc/call_sogo_ctrl.php', url: '/inc/ajax/sogo_ctrl.php',
data: { data: {
'ajax': true, 'ajax': true,
'ACTION': 'start' 'ACTION': 'start'

View File

@ -1,11 +1,4 @@
<?php <?php
session_start();
$AuthUsers = array("admin");
if (!isset($_SESSION['mailcow_cc_role']) OR !in_array($_SESSION['mailcow_cc_role'], $AuthUsers)) {
echo "Not allowed." . PHP_EOL;
exit();
}
function docker($service_name, $action, $post_action = null, $post_fields = null) { function docker($service_name, $action, $post_action = null, $post_fields = null) {
$curl = curl_init(); $curl = curl_init();
curl_setopt($curl, CURLOPT_HTTPHEADER,array( 'Content-Type: application/json' )); curl_setopt($curl, CURLOPT_HTTPHEADER,array( 'Content-Type: application/json' ));
@ -88,34 +81,4 @@ function docker($service_name, $action, $post_action = null, $post_fields = null
} }
break; break;
} }
} }
if ($_GET['ACTION'] == "start") {
$retry = 0;
while (docker('sogo-mailcow', 'info')['State']['Running'] != 1 && $retry <= 3) {
$response = docker('sogo-mailcow', 'post', 'start');
$last_response = (trim($response) == "\"OK\"") ? '<b><span class="pull-right text-success">OK</span></b>' : '<b><span class="pull-right text-danger">Error: ' . $response . '</span></b>';
if (trim($response) == "\"OK\"") {
break;
}
usleep(1500000);
$retry++;
}
echo (!isset($last_response)) ? '<b><span class="pull-right text-warning">Already running</span></b>' : $last_response;
}
if ($_GET['ACTION'] == "stop") {
$retry = 0;
while (docker('sogo-mailcow', 'info')['State']['Running'] == 1 && $retry <= 3) {
$response = docker('sogo-mailcow', 'post', 'stop');
$last_response = (trim($response) == "\"OK\"") ? '<b><span class="pull-right text-success">OK</span></b>' : '<b><span class="pull-right text-danger">Error: ' . $response . '</span></b>';
if (trim($response) == "\"OK\"") {
break;
}
usleep(1500000);
$retry++;
}
echo (!isset($last_response)) ? '<b><span class="pull-right text-warning">Not running</span></b>' : $last_response;
}
?>

View File

@ -878,15 +878,24 @@ function get_u2f_registrations($username) {
$sel->execute(array($username)); $sel->execute(array($username));
return $sel->fetchAll(PDO::FETCH_OBJ); return $sel->fetchAll(PDO::FETCH_OBJ);
} }
function get_logs($container, $lines = 100) { function get_logs($container, $lines = false) {
if ($lines === false) {
$lines = $GLOBALS['LOG_LINES'] - 1;
}
global $lang; global $lang;
global $redis; global $redis;
if ($_SESSION['mailcow_cc_role'] != "admin") { if ($_SESSION['mailcow_cc_role'] != "admin") {
return false; return false;
} }
$lines = intval($lines);
if ($container == "dovecot-mailcow") { if ($container == "dovecot-mailcow") {
if ($data = $redis->lRange('DOVECOT_MAILLOG', 0, $lines)) { if (!is_numeric($lines)) {
list ($from, $to) = explode('-', $lines);
$data = $redis->lRange('DOVECOT_MAILLOG', intval($from), intval($to));
}
else {
$data = $redis->lRange('DOVECOT_MAILLOG', 0, intval($lines));
}
if ($data) {
foreach ($data as $json_line) { foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true); $data_array[] = json_decode($json_line, true);
} }
@ -894,7 +903,14 @@ function get_logs($container, $lines = 100) {
} }
} }
if ($container == "postfix-mailcow") { if ($container == "postfix-mailcow") {
if ($data = $redis->lRange('POSTFIX_MAILLOG', 0, $lines)) { if (!is_numeric($lines)) {
list ($from, $to) = explode('-', $lines);
$data = $redis->lRange('POSTFIX_MAILLOG', intval($from), intval($to));
}
else {
$data = $redis->lRange('POSTFIX_MAILLOG', 0, intval($lines));
}
if ($data) {
foreach ($data as $json_line) { foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true); $data_array[] = json_decode($json_line, true);
} }
@ -902,7 +918,14 @@ function get_logs($container, $lines = 100) {
} }
} }
if ($container == "sogo-mailcow") { if ($container == "sogo-mailcow") {
if ($data = $redis->lRange('SOGO_LOG', 0, $lines)) { if (!is_numeric($lines)) {
list ($from, $to) = explode('-', $lines);
$data = $redis->lRange('SOGO_LOG', intval($from), intval($to));
}
else {
$data = $redis->lRange('SOGO_LOG', 0, intval($lines));
}
if ($data) {
foreach ($data as $json_line) { foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true); $data_array[] = json_decode($json_line, true);
} }
@ -910,7 +933,14 @@ function get_logs($container, $lines = 100) {
} }
} }
if ($container == "fail2ban-mailcow") { if ($container == "fail2ban-mailcow") {
if ($data = $redis->lRange('F2B_LOG', 0, $lines)) { if (!is_numeric($lines)) {
list ($from, $to) = explode('-', $lines);
$data = $redis->lRange('F2B_LOG', intval($from), intval($to));
}
else {
$data = $redis->lRange('F2B_LOG', 0, intval($lines));
}
if ($data) {
foreach ($data as $json_line) { foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true); $data_array[] = json_decode($json_line, true);
} }
@ -918,7 +948,14 @@ function get_logs($container, $lines = 100) {
} }
} }
if ($container == "autodiscover-mailcow") { if ($container == "autodiscover-mailcow") {
if ($data = $redis->lRange('AUTODISCOVER_LOG', 0, $lines)) { if (!is_numeric($lines)) {
list ($from, $to) = explode('-', $lines);
$data = $redis->lRange('AUTODISCOVER_LOG', intval($from), intval($to));
}
else {
$data = $redis->lRange('AUTODISCOVER_LOG', 0, intval($lines));
}
if ($data) {
foreach ($data as $json_line) { foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true); $data_array[] = json_decode($json_line, true);
} }
@ -927,7 +964,13 @@ function get_logs($container, $lines = 100) {
} }
if ($container == "rspamd-history") { if ($container == "rspamd-history") {
$curl = curl_init(); $curl = curl_init();
curl_setopt($curl, CURLOPT_URL,"http://rspamd-mailcow:11334/history"); if (!is_numeric($lines)) {
list ($from, $to) = explode('-', $lines);
curl_setopt($curl, CURLOPT_URL,"http://rspamd-mailcow:11334/history?from=" . intval($from) . "&to=" . intval($to));
}
else {
curl_setopt($curl, CURLOPT_URL,"http://rspamd-mailcow:11334/history?to=" . intval($lines));
}
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$history = curl_exec($curl); $history = curl_exec($curl);
if (!curl_errno($curl)) { if (!curl_errno($curl)) {
@ -940,4 +983,4 @@ function get_logs($container, $lines = 100) {
} }
return false; return false;
} }
?> ?>

View File

@ -1,5 +1,5 @@
<?php <?php
function mailbox($_action, $_type, $_data = null) { function mailbox($_action, $_type, $_data = null, $attr = null) {
global $pdo; global $pdo;
global $redis; global $redis;
global $lang; global $lang;
@ -72,6 +72,116 @@ function mailbox($_action, $_type, $_data = null) {
'msg' => sprintf($lang['success']['mailbox_modified'], htmlspecialchars($usernames)) 'msg' => sprintf($lang['success']['mailbox_modified'], htmlspecialchars($usernames))
); );
break; break;
case 'filter':
$sieve = new Sieve\SieveParser();
if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
if (isset($_data['username']) && filter_var($_data['username'], FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data['username'])) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
else {
$username = $_data['username'];
}
}
elseif ($_SESSION['mailcow_cc_role'] == "user") {
$username = $_SESSION['mailcow_cc_username'];
}
else {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'No user defined'
);
return false;
}
$active = intval($_data['active']);
$script_data = $_data['script_data'];
$script_desc = $_data['script_desc'];
$filter_type = $_data['filter_type'];
if (empty($script_data)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Script cannot be empty'
);
return false;
}
try {
$sieve->parse($script_data);
}
catch (Exception $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Sieve parser error: ' . $e->getMessage()
);
return false;
}
if (empty($script_data) || empty($script_desc)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Please define values for all fields'
);
return false;
}
if ($filter_type != 'postfilter' && $filter_type != 'prefilter') {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Wrong filter type'
);
return false;
}
if (!empty($active)) {
$script_name = 'active';
try {
$stmt = $pdo->prepare("UPDATE `sieve_filters` SET `script_name` = 'inactive' WHERE `username` = :username AND `filter_type` = :filter_type");
$stmt->execute(array(
':username' => $username,
':filter_type' => $filter_type
));
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
else {
$script_name = 'inactive';
}
try {
$stmt = $pdo->prepare("INSERT INTO `sieve_filters` (`username`, `script_data`, `script_desc`, `script_name`, `filter_type`)
VALUES (:username, :script_data, :script_desc, :script_name, :filter_type)");
$stmt->execute(array(
':username' => $username,
':script_data' => $script_data,
':script_desc' => $script_desc,
':script_name' => $script_name,
':filter_type' => $filter_type
));
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['mailbox_modified'], $username)
);
return true;
break;
case 'syncjob': case 'syncjob':
if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) { if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) {
$_SESSION['return'] = array( $_SESSION['return'] = array(
@ -544,13 +654,13 @@ function mailbox($_action, $_type, $_data = null) {
} }
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `alias_domain`= :alias_domain $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `alias_domain`= :alias_domain
UNION UNION
SELECT `alias_domain` FROM `alias_domain` WHERE `alias_domain`= :alias_domain_in_domain"); SELECT `domain` FROM `domain` WHERE `domain`= :alias_domain_in_domain");
$stmt->execute(array(':alias_domain' => $alias_domain, ':alias_domain_in_domain' => $alias_domain)); $stmt->execute(array(':alias_domain' => $alias_domain, ':alias_domain_in_domain' => $alias_domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) { if ($num_results != 0) {
$_SESSION['return'] = array( $_SESSION['return'] = array(
'type' => 'danger', 'type' => 'danger',
'msg' => sprintf($lang['danger']['aliasd_exists']) 'msg' => sprintf($lang['danger']['alias_domain_invalid'])
); );
return false; return false;
} }
@ -1280,11 +1390,12 @@ function mailbox($_action, $_type, $_data = null) {
return false; return false;
} }
foreach ($ids as $id) { foreach ($ids as $id) {
$is_now = mailbox('get', 'syncjob_details', $id); $is_now = mailbox('get', 'syncjob_details', $id, array('with_password'));
if (!empty($is_now)) { if (!empty($is_now)) {
$username = $is_now['user2']; $username = $is_now['user2'];
$user1 = (!empty($_data['user1'])) ? $_data['user1'] : $is_now['user1']; $user1 = (!empty($_data['user1'])) ? $_data['user1'] : $is_now['user1'];
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
$last_run = (isset($_data['last_run'])) ? NULL : $is_now['last_run'];
$delete2duplicates = (isset($_data['delete2duplicates'])) ? intval($_data['delete2duplicates']) : $is_now['delete2duplicates']; $delete2duplicates = (isset($_data['delete2duplicates'])) ? intval($_data['delete2duplicates']) : $is_now['delete2duplicates'];
$delete1 = (isset($_data['delete1'])) ? intval($_data['delete1']) : $is_now['delete1']; $delete1 = (isset($_data['delete1'])) ? intval($_data['delete1']) : $is_now['delete1'];
$delete2 = (isset($_data['delete2'])) ? intval($_data['delete2']) : $is_now['delete2']; $delete2 = (isset($_data['delete2'])) ? intval($_data['delete2']) : $is_now['delete2'];
@ -1346,7 +1457,7 @@ function mailbox($_action, $_type, $_data = null) {
return false; return false;
} }
try { try {
$stmt = $pdo->prepare("UPDATE `imapsync` SET `delete1` = :delete1, `delete2` = :delete2, `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, `delete2` = :delete2, `maxage` = :maxage, `subfolder2` = :subfolder2, `exclude` = :exclude, `host1` = :host1, `last_run` = :last_run, `user1` = :user1, `password1` = :password1, `mins_interval` = :mins_interval, `port1` = :port1, `enc1` = :enc1, `delete2duplicates` = :delete2duplicates, `active` = :active
WHERE `id` = :id"); WHERE `id` = :id");
$stmt->execute(array( $stmt->execute(array(
':delete1' => $delete1, ':delete1' => $delete1,
@ -1358,6 +1469,7 @@ function mailbox($_action, $_type, $_data = null) {
':host1' => $host1, ':host1' => $host1,
':user1' => $user1, ':user1' => $user1,
':password1' => $password1, ':password1' => $password1,
':last_run' => $last_run,
':mins_interval' => $mins_interval, ':mins_interval' => $mins_interval,
':port1' => $port1, ':port1' => $port1,
':enc1' => $enc1, ':enc1' => $enc1,
@ -1379,6 +1491,100 @@ function mailbox($_action, $_type, $_data = null) {
); );
return true; return true;
break; break;
case 'filter':
$sieve = new Sieve\SieveParser();
if (!is_array($_data['id'])) {
$ids = array();
$ids[] = $_data['id'];
}
else {
$ids = $_data['id'];
}
if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
foreach ($ids as $id) {
$is_now = mailbox('get', 'filter_details', $id);
if (!empty($is_now)) {
$username = $is_now['username'];
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
$script_desc = (!empty($_data['script_desc'])) ? $_data['script_desc'] : $is_now['script_desc'];
$script_data = (!empty($_data['script_data'])) ? $_data['script_data'] : $is_now['script_data'];
$filter_type = (!empty($_data['filter_type'])) ? $_data['filter_type'] : $is_now['filter_type'];
}
else {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
try {
$sieve->parse($script_data);
}
catch (Exception $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Sieve parser error: ' . $e->getMessage()
);
return false;
}
if ($filter_type != 'postfilter' && $filter_type != 'prefilter') {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Wrong filter type'
);
return false;
}
if ($active == '1') {
$script_name = 'active';
try {
$stmt = $pdo->prepare("UPDATE `sieve_filters` SET `script_name` = 'inactive' WHERE `username` = :username AND `filter_type` = :filter_type");
$stmt->execute(array(
':username' => $username,
':filter_type' => $filter_type
));
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
else {
$script_name = 'inactive';
}
try {
$stmt = $pdo->prepare("UPDATE `sieve_filters` SET `script_desc` = :script_desc, `script_data` = :script_data, `script_name` = :script_name, `filter_type` = :filter_type
WHERE `id` = :id");
$stmt->execute(array(
':script_desc' => $script_desc,
':script_data' => $script_data,
':script_name' => $script_name,
':filter_type' => $filter_type,
':id' => $id
));
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['mailbox_modified'], $username)
);
return true;
break;
case 'alias': case 'alias':
if (!is_array($_data['address'])) { if (!is_array($_data['address'])) {
$addresses = array(); $addresses = array();
@ -2140,19 +2346,134 @@ function mailbox($_action, $_type, $_data = null) {
} }
return $policydata; return $policydata;
break; break;
case 'filters':
$filters = array();
if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
}
else {
$_data = $_SESSION['mailcow_cc_username'];
}
try {
$stmt = $pdo->prepare("SELECT `id` FROM `sieve_filters` WHERE `username` = :username");
$stmt->execute(array(':username' => $_data));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$filters[] = $row['id'];
}
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
}
return $filters;
break;
case 'filter_details':
$filter_details = array();
if (!is_numeric($_data)) {
return false;
}
try {
$stmt = $pdo->prepare("SELECT CASE `script_name` WHEN 'active' THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`,
CASE `script_name` WHEN 'active' THEN 1 ELSE 0 END AS `active_int`,
id,
username,
filter_type,
script_data,
script_desc
FROM `sieve_filters`
WHERE `id` = :id");
$stmt->execute(array(':id' => $_data));
$filter_details = $stmt->fetch(PDO::FETCH_ASSOC);
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $filter_details['username'])) {
return false;
}
return $filter_details;
break;
case 'active_user_sieve':
$filter_details = array();
if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
}
else {
$_data = $_SESSION['mailcow_cc_username'];
}
$exec_fields = array(
'cmd' => 'sieve_list',
'username' => $_data
);
$filters = json_decode(docker('dovecot-mailcow', 'post', 'exec', $exec_fields), true);
$filters = array_filter(explode(PHP_EOL, $filters));
foreach ($filters as $filter) {
if (preg_match('/.+ ACTIVE/i', $filter)) {
$exec_fields = array(
'cmd' => 'sieve_print',
'script_name' => substr($filter, 0, -7),
'username' => $_data
);
$filters = json_decode(docker('dovecot-mailcow', 'post', 'exec', $exec_fields), true);
return preg_replace('/^.+\n/', '', $filters);
}
}
return false;
break;
case 'syncjob_details': case 'syncjob_details':
$syncjobdetails = array(); $syncjobdetails = array();
if (!is_numeric($_data)) { if (!is_numeric($_data)) {
return false; return false;
} }
try { try {
$stmt = $pdo->prepare("SELECT *, if (isset($attr) && in_array('no_log', $attr)) {
CONCAT(LEFT(`password1`, 3), '...') AS `password1_short`, $field_query = $pdo->query('SHOW FIELDS FROM `imapsync` WHERE FIELD NOT IN ("returned_text", "password1")');
`active` AS `active_int`, $fields = $field_query->fetchAll(PDO::FETCH_ASSOC);
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` while($field = array_shift($fields)) {
FROM `imapsync` WHERE id = :id"); $shown_fields[] = $field['Field'];
}
$stmt = $pdo->prepare("SELECT " . implode(',', $shown_fields) . ",
`active` AS `active_int`,
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
FROM `imapsync` WHERE id = :id");
}
elseif (isset($attr) && in_array('with_password', $attr)) {
$stmt = $pdo->prepare("SELECT *,
`active` AS `active_int`,
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
FROM `imapsync` WHERE id = :id");
}
else {
$field_query = $pdo->query('SHOW FIELDS FROM `imapsync` WHERE FIELD NOT IN ("password1")');
$fields = $field_query->fetchAll(PDO::FETCH_ASSOC);
while($field = array_shift($fields)) {
$shown_fields[] = $field['Field'];
}
$stmt = $pdo->prepare("SELECT " . implode(',', $shown_fields) . ",
`active` AS `active_int`,
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
FROM `imapsync` WHERE id = :id");
}
$stmt->execute(array(':id' => $_data)); $stmt->execute(array(':id' => $_data));
$syncjobdetails = $stmt->fetch(PDO::FETCH_ASSOC); $syncjobdetails = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($syncjobdetails['returned_text'])) {
$syncjobdetails['log'] = $syncjobdetails['returned_text'];
}
else {
$syncjobdetails['log'] = '';
}
unset($syncjobdetails['returned_text']);
} }
catch(PDOException $e) { catch(PDOException $e) {
$_SESSION['return'] = array( $_SESSION['return'] = array(
@ -2816,6 +3137,57 @@ function mailbox($_action, $_type, $_data = null) {
); );
return true; return true;
break; break;
case 'filter':
if (!is_array($_data['id'])) {
$ids = array();
$ids[] = $_data['id'];
}
else {
$ids = $_data['id'];
}
if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
foreach ($ids as $id) {
if (!is_numeric($id)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => $id
);
return false;
}
try {
$stmt = $pdo->prepare("SELECT `username` FROM `sieve_filters` WHERE id = :id");
$stmt->execute(array(':id' => $id));
$usr = $stmt->fetch(PDO::FETCH_ASSOC)['username'];
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $usr)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
$stmt = $pdo->prepare("DELETE FROM `sieve_filters` WHERE `id`= :id");
$stmt->execute(array(':id' => $id));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => 'Deleted filter id/s ' . implode(', ', $ids)
);
return true;
break;
case 'time_limited_alias': case 'time_limited_alias':
if (!is_array($_data['address'])) { if (!is_array($_data['address'])) {
$addresses = array(); $addresses = array();

View File

@ -22,6 +22,7 @@
<link rel="stylesheet" href="/inc/languages.min.css"> <link rel="stylesheet" href="/inc/languages.min.css">
<link rel="stylesheet" href="/css/mailcow.css"> <link rel="stylesheet" href="/css/mailcow.css">
<link rel="stylesheet" href="/css/animate.min.css"> <link rel="stylesheet" href="/css/animate.min.css">
<link rel="stylesheet" href="/css/numberedtextarea.min.css">
<?= (preg_match("/mailbox.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/mailbox.css">' : null; ?> <?= (preg_match("/mailbox.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/mailbox.css">' : null; ?>
<?= (preg_match("/admin.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/admin.css">' : null; ?> <?= (preg_match("/admin.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/admin.css">' : null; ?>
<?= (preg_match("/user.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/user.css">' : null; ?> <?= (preg_match("/user.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/user.css">' : null; ?>

View File

@ -3,7 +3,7 @@ function init_db_schema() {
try { try {
global $pdo; global $pdo;
$db_version = "25102017_0748"; $db_version = "08112017_1049";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@ -27,7 +27,13 @@ function init_db_schema() {
GROUP BY logged_in_as;", GROUP BY logged_in_as;",
"grouped_domain_alias_address" => "CREATE VIEW grouped_domain_alias_address (username, ad_alias) AS "grouped_domain_alias_address" => "CREATE VIEW grouped_domain_alias_address (username, ad_alias) AS
SELECT username, IFNULL(GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ' '), '') AS ad_alias FROM mailbox SELECT username, IFNULL(GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ' '), '') AS ad_alias FROM mailbox
LEFT OUTER JOIN alias_domain on target_domain=domain GROUP BY username;" LEFT OUTER JOIN alias_domain on target_domain=domain GROUP BY username;",
"sieve_before" => "CREATE VIEW sieve_before (id, username, script_name, script_data) AS
SELECT id, username, script_name, script_data FROM sieve_filters
WHERE filter_type = 'prefilter';",
"sieve_after" => "CREATE VIEW sieve_after (id, username, script_name, script_data) AS
SELECT id, username, script_name, script_data FROM sieve_filters
WHERE filter_type = 'postfilter';"
); );
$tables = array( $tables = array(
@ -155,6 +161,36 @@ function init_db_schema() {
), ),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
), ),
"sieve_filters" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"username" => "VARCHAR(255) NOT NULL",
"script_desc" => "VARCHAR(255) NOT NULL",
"script_name" => "ENUM('active','inactive')",
"script_data" => "TEXT NOT NULL",
"filter_type" => "ENUM('postfilter','prefilter')",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
),
"keys" => array(
"primary" => array(
"" => array("id")
),
"key" => array(
"username" => array("username"),
"script_desc" => array("script_desc")
),
"fkey" => array(
"fk_username_sieve_global_before" => array(
"col" => "username",
"ref" => "mailbox.username",
"delete" => "CASCADE",
"update" => "NO ACTION"
)
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"user_acl" => array( "user_acl" => array(
"cols" => array( "cols" => array(
"username" => "VARCHAR(255) NOT NULL", "username" => "VARCHAR(255) NOT NULL",
@ -165,6 +201,7 @@ function init_db_schema() {
"delimiter_action" => "TINYINT(1) NOT NULL DEFAULT '1'", "delimiter_action" => "TINYINT(1) NOT NULL DEFAULT '1'",
"syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'", "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'",
"eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'", "eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'",
"filters" => "TINYINT(1) NOT NULL DEFAULT '1'",
), ),
"keys" => array( "keys" => array(
"fkey" => array( "fkey" => array(
@ -248,8 +285,11 @@ function init_db_schema() {
"active" => "TINYINT(1) NOT NULL DEFAULT '1'" "active" => "TINYINT(1) NOT NULL DEFAULT '1'"
), ),
"keys" => array( "keys" => array(
"primary" => array(
"" => array("username")
),
"key" => array( "key" => array(
"username" => array("username") "domain" => array("domain")
) )
), ),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
@ -274,6 +314,7 @@ function init_db_schema() {
"delete2duplicates" => "TINYINT(1) NOT NULL DEFAULT '1'", "delete2duplicates" => "TINYINT(1) NOT NULL DEFAULT '1'",
"delete1" => "TINYINT(1) NOT NULL DEFAULT '0'", "delete1" => "TINYINT(1) NOT NULL DEFAULT '0'",
"delete2" => "TINYINT(1) NOT NULL DEFAULT '0'", "delete2" => "TINYINT(1) NOT NULL DEFAULT '0'",
"is_running" => "TINYINT(1) NOT NULL DEFAULT '0'",
"returned_text" => "TEXT", "returned_text" => "TEXT",
"last_run" => "TIMESTAMP NULL DEFAULT NULL", "last_run" => "TIMESTAMP NULL DEFAULT NULL",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",

View File

@ -0,0 +1,7 @@
<?php namespace Sieve;
interface SieveDumpable
{
function dump();
function text();
}

View File

@ -0,0 +1,47 @@
<?php namespace Sieve;
require_once('SieveToken.php');
use Exception;
class SieveException extends Exception
{
protected $token_;
public function __construct(SieveToken $token, $arg)
{
$message = 'undefined sieve exception';
$this->token_ = $token;
if (is_string($arg))
{
$message = $arg;
}
else
{
if (is_array($arg))
{
$type = SieveToken::typeString(array_shift($arg));
foreach($arg as $t)
{
$type .= ' or '. SieveToken::typeString($t);
}
}
else
{
$type = SieveToken::typeString($arg);
}
$tokenType = SieveToken::typeString($token->type);
$message = "$tokenType where $type expected near ". $token->text;
}
parent::__construct('line '. $token->line .": $message");
}
public function getLineNo()
{
return $this->token_->line;
}
}

View File

@ -0,0 +1,233 @@
<?php namespace Sieve;
class SieveKeywordRegistry
{
protected $registry_ = array();
protected $matchTypes_ = array();
protected $comparators_ = array();
protected $addressParts_ = array();
protected $commands_ = array();
protected $tests_ = array();
protected $arguments_ = array();
protected static $refcount = 0;
protected static $instance = null;
protected function __construct()
{
$keywords = simplexml_load_file(dirname(__FILE__) .'/keywords.xml');
foreach ($keywords->children() as $keyword)
{
switch ($keyword->getName())
{
case 'matchtype':
$type =& $this->matchTypes_;
break;
case 'comparator':
$type =& $this->comparators_;
break;
case 'addresspart':
$type =& $this->addressParts_;
break;
case 'test':
$type =& $this->tests_;
break;
case 'command':
$type =& $this->commands_;
break;
default:
trigger_error('Unsupported keyword type "'. $keyword->getName()
. '" in file "keywords/'. basename($file) .'"');
return;
}
$name = (string) $keyword['name'];
if (array_key_exists($name, $type))
trigger_error("redefinition of $type $name - skipping");
else
$type[$name] = $keyword->children();
}
foreach (glob(dirname(__FILE__) .'/extensions/*.xml') as $file)
{
$extension = simplexml_load_file($file);
$name = (string) $extension['name'];
if (array_key_exists($name, $this->registry_))
{
trigger_error('overwriting extension "'. $name .'"');
}
$this->registry_[$name] = $extension;
}
}
public static function get()
{
if (self::$instance == null)
{
self::$instance = new SieveKeywordRegistry();
}
self::$refcount++;
return self::$instance;
}
public function put()
{
if (--self::$refcount == 0)
{
self::$instance = null;
}
}
public function activate($extension)
{
if (!isset($this->registry_[$extension]))
{
return;
}
$xml = $this->registry_[$extension];
foreach ($xml->children() as $e)
{
switch ($e->getName())
{
case 'matchtype':
$type =& $this->matchTypes_;
break;
case 'comparator':
$type =& $this->comparators_;
break;
case 'addresspart':
$type =& $this->addressParts_;
break;
case 'test':
$type =& $this->tests_;
break;
case 'command':
$type =& $this->commands_;
break;
case 'tagged-argument':
$xml = $e->parameter[0];
$this->arguments_[(string) $xml['name']] = array(
'extends' => (string) $e['extends'],
'rules' => $xml
);
continue;
default:
trigger_error('Unsupported extension type \''.
$e->getName() ."' in extension '$extension'");
return;
}
$name = (string) $e['name'];
if (!isset($type[$name]) ||
(string) $e['overrides'] == 'true')
{
$type[$name] = $e->children();
}
}
}
public function isTest($name)
{
return (isset($this->tests_[$name]) ? true : false);
}
public function isCommand($name)
{
return (isset($this->commands_[$name]) ? true : false);
}
public function matchtype($name)
{
if (isset($this->matchTypes_[$name]))
{
return $this->matchTypes_[$name];
}
return null;
}
public function addresspart($name)
{
if (isset($this->addressParts_[$name]))
{
return $this->addressParts_[$name];
}
return null;
}
public function comparator($name)
{
if (isset($this->comparators_[$name]))
{
return $this->comparators_[$name];
}
return null;
}
public function test($name)
{
if (isset($this->tests_[$name]))
{
return $this->tests_[$name];
}
return null;
}
public function command($name)
{
if (isset($this->commands_[$name]))
{
return $this->commands_[$name];
}
return null;
}
public function arguments($command)
{
$res = array();
foreach ($this->arguments_ as $arg)
{
if (preg_match('/'.$arg['extends'].'/', $command))
array_push($res, $arg['rules']);
}
return $res;
}
public function argument($name)
{
if (isset($this->arguments_[$name]))
{
return $this->arguments_[$name]['rules'];
}
return null;
}
public function requireStrings()
{
return array_keys($this->registry_);
}
public function matchTypes()
{
return array_keys($this->matchTypes_);
}
public function comparators()
{
return array_keys($this->comparators_);
}
public function addressParts()
{
return array_keys($this->addressParts_);
}
public function tests()
{
return array_keys($this->tests_);
}
public function commands()
{
return array_keys($this->commands_);
}
}

View File

@ -0,0 +1,255 @@
<?php namespace Sieve;
include_once 'SieveTree.php';
include_once 'SieveScanner.php';
include_once 'SieveSemantics.php';
include_once 'SieveException.php';
class SieveParser
{
protected $scanner_;
protected $script_;
protected $tree_;
protected $status_;
public function __construct($script = null)
{
if (isset($script))
$this->parse($script);
}
public function GetParseTree()
{
return $this->tree_;
}
public function dumpParseTree()
{
return $this->tree_->dump();
}
public function getScriptText()
{
return $this->tree_->getText();
}
protected function getPrevToken_($parent_id)
{
$childs = $this->tree_->getChilds($parent_id);
for ($i = count($childs); $i > 0; --$i)
{
$prev = $this->tree_->getNode($childs[$i-1]);
if ($prev->is(SieveToken::Comment|SieveToken::Whitespace))
continue;
// use command owning a block or list instead of previous
if ($prev->is(SieveToken::BlockStart|SieveToken::Comma|SieveToken::LeftParenthesis))
$prev = $this->tree_->getNode($parent_id);
return $prev;
}
return $this->tree_->getNode($parent_id);
}
/*******************************************************************************
* methods for recursive descent start below
*/
public function passthroughWhitespaceComment($token)
{
return 0;
}
public function passthroughFunction($token)
{
$this->tree_->addChild($token);
}
public function parse($script)
{
$this->script_ = $script;
$this->scanner_ = new SieveScanner($this->script_);
// Define what happens with passthrough tokens like whitespacs and comments
$this->scanner_->setPassthroughFunc(
array(
$this, 'passthroughWhitespaceComment'
)
);
$this->tree_ = new SieveTree('tree');
$this->commands_($this->tree_->getRoot());
if (!$this->scanner_->nextTokenIs(SieveToken::ScriptEnd)) {
$token = $this->scanner_->nextToken();
throw new SieveException($token, SieveToken::ScriptEnd);
}
}
protected function commands_($parent_id)
{
while (true)
{
if (!$this->scanner_->nextTokenIs(SieveToken::Identifier))
break;
// Get and check a command token
$token = $this->scanner_->nextToken();
$semantics = new SieveSemantics($token, $this->getPrevToken_($parent_id));
// Process eventual arguments
$this_node = $this->tree_->addChildTo($parent_id, $token);
$this->arguments_($this_node, $semantics);
$token = $this->scanner_->nextToken();
if (!$token->is(SieveToken::Semicolon))
{
// TODO: check if/when semcheck is needed here
$semantics->validateToken($token);
if ($token->is(SieveToken::BlockStart))
{
$this->tree_->addChildTo($this_node, $token);
$this->block_($this_node, $semantics);
continue;
}
throw new SieveException($token, SieveToken::Semicolon);
}
$semantics->done($token);
$this->tree_->addChildTo($this_node, $token);
}
}
protected function arguments_($parent_id, &$semantics)
{
while (true)
{
if ($this->scanner_->nextTokenIs(SieveToken::Number|SieveToken::Tag))
{
// Check if semantics allow a number or tag
$token = $this->scanner_->nextToken();
$semantics->validateToken($token);
$this->tree_->addChildTo($parent_id, $token);
}
else if ($this->scanner_->nextTokenIs(SieveToken::StringList))
{
$this->stringlist_($parent_id, $semantics);
}
else
{
break;
}
}
if ($this->scanner_->nextTokenIs(SieveToken::TestList))
{
$this->testlist_($parent_id, $semantics);
}
}
protected function stringlist_($parent_id, &$semantics)
{
if (!$this->scanner_->nextTokenIs(SieveToken::LeftBracket))
{
$this->string_($parent_id, $semantics);
return;
}
$token = $this->scanner_->nextToken();
$semantics->startStringList($token);
$this->tree_->addChildTo($parent_id, $token);
if($this->scanner_->nextTokenIs(SieveToken::RightBracket)) {
//allow empty lists
$token = $this->scanner_->nextToken();
$this->tree_->addChildTo($parent_id, $token);
$semantics->endStringList();
return;
}
do
{
$this->string_($parent_id, $semantics);
$token = $this->scanner_->nextToken();
if (!$token->is(SieveToken::Comma|SieveToken::RightBracket))
throw new SieveException($token, array(SieveToken::Comma, SieveToken::RightBracket));
if ($token->is(SieveToken::Comma))
$semantics->continueStringList();
$this->tree_->addChildTo($parent_id, $token);
}
while (!$token->is(SieveToken::RightBracket));
$semantics->endStringList();
}
protected function string_($parent_id, &$semantics)
{
$token = $this->scanner_->nextToken();
$semantics->validateToken($token);
$this->tree_->addChildTo($parent_id, $token);
}
protected function testlist_($parent_id, &$semantics)
{
if (!$this->scanner_->nextTokenIs(SieveToken::LeftParenthesis))
{
$this->test_($parent_id, $semantics);
return;
}
$token = $this->scanner_->nextToken();
$semantics->validateToken($token);
$this->tree_->addChildTo($parent_id, $token);
do
{
$this->test_($parent_id, $semantics);
$token = $this->scanner_->nextToken();
if (!$token->is(SieveToken::Comma|SieveToken::RightParenthesis))
{
throw new SieveException($token, array(SieveToken::Comma, SieveToken::RightParenthesis));
}
$this->tree_->addChildTo($parent_id, $token);
}
while (!$token->is(SieveToken::RightParenthesis));
}
protected function test_($parent_id, &$semantics)
{
// Check if semantics allow an identifier
$token = $this->scanner_->nextToken();
$semantics->validateToken($token);
// Get semantics for this test command
$this_semantics = new SieveSemantics($token, $this->getPrevToken_($parent_id));
$this_node = $this->tree_->addChildTo($parent_id, $token);
// Consume eventual argument tokens
$this->arguments_($this_node, $this_semantics);
// Check that all required arguments were there
$token = $this->scanner_->peekNextToken();
$this_semantics->done($token);
}
protected function block_($parent_id, &$semantics)
{
$this->commands_($parent_id, $semantics);
$token = $this->scanner_->nextToken();
if (!$token->is(SieveToken::BlockEnd))
{
throw new SieveException($token, SieveToken::BlockEnd);
}
$this->tree_->addChildTo($parent_id, $token);
}
}

View File

@ -0,0 +1,145 @@
<?php namespace Sieve;
include_once('SieveToken.php');
class SieveScanner
{
public function __construct(&$script)
{
if ($script === null)
return;
$this->tokenize($script);
}
public function setPassthroughFunc($callback)
{
if ($callback == null || is_callable($callback))
$this->ptFn_ = $callback;
}
public function tokenize(&$script)
{
$pos = 0;
$line = 1;
$scriptLength = mb_strlen($script);
$unprocessedScript = $script;
//create one regex to find the right match
//avoids looping over all possible tokens: increases performance
$nameToType = [];
$regex = [];
// chr(65) == 'A'
$i = 65;
foreach ($this->tokenMatch_ as $type => $subregex) {
$nameToType[chr($i)] = $type;
$regex[] = "(?P<". chr($i) . ">^$subregex)";
$i++;
}
$regex = '/' . join('|', $regex) . '/';
while ($pos < $scriptLength)
{
if (preg_match($regex, $unprocessedScript, $match)) {
// only keep the group that match and we only want matches with group names
// we can use the group name to find the token type using nameToType
$filterMatch = array_filter(array_filter($match), 'is_string', ARRAY_FILTER_USE_KEY);
// the first element in filterMatch will contain the matched group and the key will be the name
$type = $nameToType[key($filterMatch)];
$currentMatch = current($filterMatch);
//create the token
$token = new SieveToken($type, $currentMatch, $line);
$this->tokens_[] = $token;
if ($type == SieveToken::Unknown)
return;
// just remove the part that we parsed: don't extract the new substring using script length
// as mb_strlen is \theta(pos) (it's linear in the position)
$matchLength = mb_strlen($currentMatch);
$unprocessedScript = mb_substr($unprocessedScript, $matchLength);
$pos += $matchLength;
$line += mb_substr_count($currentMatch, "\n");
} else {
$this->tokens_[] = new SieveToken(SieveToken::Unknown, '', $line);
return;
}
}
$this->tokens_[] = new SieveToken(SieveToken::ScriptEnd, '', $line);
}
public function nextTokenIs($type)
{
return $this->peekNextToken()->is($type);
}
public function peekNextToken()
{
$offset = 0;
do {
$next = $this->tokens_[$this->tokenPos_ + $offset++];
} while ($next->is(SieveToken::Comment|SieveToken::Whitespace));
return $next;
}
public function nextToken()
{
$token = $this->tokens_[$this->tokenPos_++];
while ($token->is(SieveToken::Comment|SieveToken::Whitespace))
{
if ($this->ptFn_ != null)
call_user_func($this->ptFn_, $token);
$token = $this->tokens_[$this->tokenPos_++];
}
return $token;
}
protected $ptFn_ = null;
protected $tokenPos_ = 0;
protected $tokens_ = array();
protected $tokenMatch_ = array (
SieveToken::LeftBracket => '\[',
SieveToken::RightBracket => '\]',
SieveToken::BlockStart => '\{',
SieveToken::BlockEnd => '\}',
SieveToken::LeftParenthesis => '\(',
SieveToken::RightParenthesis => '\)',
SieveToken::Comma => ',',
SieveToken::Semicolon => ';',
SieveToken::Whitespace => '[ \r\n\t]+',
SieveToken::Tag => ':[[:alpha:]_][[:alnum:]_]*(?=\b)',
/*
" # match a quotation mark
( # start matching parts that include an escaped quotation mark
([^"]*[^"\\\\]) # match a string without quotation marks and not ending with a backlash
? # this also includes the empty string
(\\\\\\\\)* # match any groups of even number of backslashes
# (thus the character after these groups are not escaped)
\\\\" # match an escaped quotation mark
)* # accept any number of strings that end with an escaped quotation mark
[^"]* # accept any trailing part that does not contain any quotation marks
" # end of the quoted string
*/
SieveToken::QuotedString => '"(([^"]*[^"\\\\])?(\\\\\\\\)*\\\\")*[^"]*"',
SieveToken::Number => '[[:digit:]]+(?:[KMG])?(?=\b)',
SieveToken::Comment => '(?:\/\*(?:[^\*]|\*(?=[^\/]))*\*\/|#[^\r\n]*\r?(\n|$))',
SieveToken::MultilineString => 'text:[ \t]*(?:#[^\r\n]*)?\r?\n(\.[^\r\n]+\r?\n|[^\.][^\r\n]*\r?\n)*\.\r?(\n|$)',
SieveToken::Identifier => '[[:alpha:]_][[:alnum:]_]*(?=\b)',
SieveToken::Unknown => '[^ \r\n\t]+'
);
}

View File

@ -0,0 +1,6 @@
<?php namespace Sieve;
class SieveScript
{
// TODO: implement
}

View File

@ -0,0 +1,611 @@
<?php namespace Sieve;
require_once('SieveKeywordRegistry.php');
require_once('SieveToken.php');
require_once('SieveException.php');
class SieveSemantics
{
protected static $requiredExtensions_ = array();
protected $comparator_;
protected $matchType_;
protected $addressPart_;
protected $tags_ = array();
protected $arguments_;
protected $deps_ = array();
protected $followupToken_;
public function __construct($token, $prevToken)
{
$this->registry_ = SieveKeywordRegistry::get();
$command = strtolower($token->text);
// Check the registry for $command
if ($this->registry_->isCommand($command))
{
$xml = $this->registry_->command($command);
$this->arguments_ = $this->makeArguments_($xml);
$this->followupToken_ = SieveToken::Semicolon;
}
else if ($this->registry_->isTest($command))
{
$xml = $this->registry_->test($command);
$this->arguments_ = $this->makeArguments_($xml);
$this->followupToken_ = SieveToken::BlockStart;
}
else
{
throw new SieveException($token, 'unknown command '. $command);
}
// Check if command may appear at this position within the script
if ($this->registry_->isTest($command))
{
if (is_null($prevToken))
throw new SieveException($token, $command .' may not appear as first command');
if (!preg_match('/^(if|elsif|anyof|allof|not)$/i', $prevToken->text))
throw new SieveException($token, $command .' may not appear after '. $prevToken->text);
}
else if (isset($prevToken))
{
switch ($command)
{
case 'require':
$valid_after = 'require';
break;
case 'elsif':
case 'else':
$valid_after = '(if|elsif)';
break;
default:
$valid_after = $this->commandsRegex_();
}
if (!preg_match('/^'. $valid_after .'$/i', $prevToken->text))
throw new SieveException($token, $command .' may not appear after '. $prevToken->text);
}
// Check for extension arguments to add to the command
foreach ($this->registry_->arguments($command) as $arg)
{
switch ((string) $arg['type'])
{
case 'tag':
array_unshift($this->arguments_, array(
'type' => SieveToken::Tag,
'occurrence' => $this->occurrence_($arg),
'regex' => $this->regex_($arg),
'call' => 'tagHook_',
'name' => $this->name_($arg),
'subArgs' => $this->makeArguments_($arg->children())
));
break;
}
}
}
public function __destruct()
{
$this->registry_->put();
}
// TODO: the *Regex functions could possibly also be static properties
protected function requireStringsRegex_()
{
return '('. implode('|', $this->registry_->requireStrings()) .')';
}
protected function matchTypeRegex_()
{
return '('. implode('|', $this->registry_->matchTypes()) .')';
}
protected function addressPartRegex_()
{
return '('. implode('|', $this->registry_->addressParts()) .')';
}
protected function commandsRegex_()
{
return '('. implode('|', $this->registry_->commands()) .')';
}
protected function testsRegex_()
{
return '('. implode('|', $this->registry_->tests()) .')';
}
protected function comparatorRegex_()
{
return '('. implode('|', $this->registry_->comparators()) .')';
}
protected function occurrence_($arg)
{
if (isset($arg['occurrence']))
{
switch ((string) $arg['occurrence'])
{
case 'optional':
return '?';
case 'any':
return '*';
case 'some':
return '+';
}
}
return '1';
}
protected function name_($arg)
{
if (isset($arg['name']))
{
return (string) $arg['name'];
}
return (string) $arg['type'];
}
protected function regex_($arg)
{
if (isset($arg['regex']))
{
return (string) $arg['regex'];
}
return '.*';
}
protected function case_($arg)
{
if (isset($arg['case']))
{
return (string) $arg['case'];
}
return 'adhere';
}
protected function follows_($arg)
{
if (isset($arg['follows']))
{
return (string) $arg['follows'];
}
return '.*';
}
protected function makeValue_($arg)
{
if (isset($arg->value))
{
$res = $this->makeArguments_($arg->value);
return array_shift($res);
}
return null;
}
/**
* Convert an extension (test) commands parameters from XML to
* a PHP array the {@see Semantics} class understands.
* @param array(SimpleXMLElement) $parameters
* @return array
*/
protected function makeArguments_($parameters)
{
$arguments = array();
foreach ($parameters as $arg)
{
// Ignore anything not a <parameter>
if ($arg->getName() != 'parameter')
continue;
switch ((string) $arg['type'])
{
case 'addresspart':
array_push($arguments, array(
'type' => SieveToken::Tag,
'occurrence' => $this->occurrence_($arg),
'regex' => $this->addressPartRegex_(),
'call' => 'addressPartHook_',
'name' => 'address part',
'subArgs' => $this->makeArguments_($arg)
));
break;
case 'block':
array_push($arguments, array(
'type' => SieveToken::BlockStart,
'occurrence' => '1',
'regex' => '{',
'name' => 'block',
'subArgs' => $this->makeArguments_($arg)
));
break;
case 'comparator':
array_push($arguments, array(
'type' => SieveToken::Tag,
'occurrence' => $this->occurrence_($arg),
'regex' => 'comparator',
'name' => 'comparator',
'subArgs' => array( array(
'type' => SieveToken::String,
'occurrence' => '1',
'call' => 'comparatorHook_',
'case' => 'adhere',
'regex' => $this->comparatorRegex_(),
'name' => 'comparator string',
'follows' => 'comparator'
))
));
break;
case 'matchtype':
array_push($arguments, array(
'type' => SieveToken::Tag,
'occurrence' => $this->occurrence_($arg),
'regex' => $this->matchTypeRegex_(),
'call' => 'matchTypeHook_',
'name' => 'match type',
'subArgs' => $this->makeArguments_($arg)
));
break;
case 'number':
array_push($arguments, array(
'type' => SieveToken::Number,
'occurrence' => $this->occurrence_($arg),
'regex' => $this->regex_($arg),
'name' => $this->name_($arg),
'follows' => $this->follows_($arg)
));
break;
case 'requirestrings':
array_push($arguments, array(
'type' => SieveToken::StringList,
'occurrence' => $this->occurrence_($arg),
'call' => 'setRequire_',
'case' => 'adhere',
'regex' => $this->requireStringsRegex_(),
'name' => $this->name_($arg)
));
break;
case 'string':
array_push($arguments, array(
'type' => SieveToken::String,
'occurrence' => $this->occurrence_($arg),
'regex' => $this->regex_($arg),
'case' => $this->case_($arg),
'name' => $this->name_($arg),
'follows' => $this->follows_($arg)
));
break;
case 'stringlist':
array_push($arguments, array(
'type' => SieveToken::StringList,
'occurrence' => $this->occurrence_($arg),
'regex' => $this->regex_($arg),
'case' => $this->case_($arg),
'name' => $this->name_($arg),
'follows' => $this->follows_($arg)
));
break;
case 'tag':
array_push($arguments, array(
'type' => SieveToken::Tag,
'occurrence' => $this->occurrence_($arg),
'regex' => $this->regex_($arg),
'call' => 'tagHook_',
'name' => $this->name_($arg),
'subArgs' => $this->makeArguments_($arg->children()),
'follows' => $this->follows_($arg)
));
break;
case 'test':
array_push($arguments, array(
'type' => SieveToken::Identifier,
'occurrence' => $this->occurrence_($arg),
'regex' => $this->testsRegex_(),
'name' => $this->name_($arg),
'subArgs' => $this->makeArguments_($arg->children())
));
break;
case 'testlist':
array_push($arguments, array(
'type' => SieveToken::LeftParenthesis,
'occurrence' => '1',
'regex' => '\(',
'name' => $this->name_($arg),
'subArgs' => null
));
array_push($arguments, array(
'type' => SieveToken::Identifier,
'occurrence' => '+',
'regex' => $this->testsRegex_(),
'name' => $this->name_($arg),
'subArgs' => $this->makeArguments_($arg->children())
));
break;
}
}
return $arguments;
}
/**
* Add argument(s) expected / allowed to appear next.
* @param array $value
*/
protected function addArguments_($identifier, $subArgs)
{
for ($i = count($subArgs); $i > 0; $i--)
{
$arg = $subArgs[$i-1];
if (preg_match('/^'. $arg['follows'] .'$/si', $identifier))
array_unshift($this->arguments_, $arg);
}
}
/**
* Add dependency that is expected to be fullfilled when parsing
* of the current command is {@see done}.
* @param array $dependency
*/
protected function addDependency_($type, $name, $dependencies)
{
foreach ($dependencies as $d)
{
array_push($this->deps_, array(
'o_type' => $type,
'o_name' => $name,
'type' => $d['type'],
'name' => $d['name'],
'regex' => $d['regex']
));
}
}
protected function invoke_($token, $func, $arg = array())
{
if (!is_array($arg))
$arg = array($arg);
$err = call_user_func_array(array(&$this, $func), $arg);
if ($err)
throw new SieveException($token, $err);
}
protected function setRequire_($extension)
{
array_push(self::$requiredExtensions_, $extension);
$this->registry_->activate($extension);
}
/**
* Hook function that is called after a address part match was found
* in a command. The kind of address part is remembered in case it's
* needed later {@see done}. For address parts from a extension
* dependency information and valid values are looked up as well.
* @param string $addresspart
*/
protected function addressPartHook_($addresspart)
{
$this->addressPart_ = $addresspart;
$xml = $this->registry_->addresspart($this->addressPart_);
if (isset($xml))
{
// Add possible value and dependancy
$this->addArguments_($this->addressPart_, $this->makeArguments_($xml));
$this->addDependency_('address part', $this->addressPart_, $xml->requires);
}
}
/**
* Hook function that is called after a match type was found in a
* command. The kind of match type is remembered in case it's
* needed later {@see done}. For a match type from extensions
* dependency information and valid values are looked up as well.
* @param string $matchtype
*/
protected function matchTypeHook_($matchtype)
{
$this->matchType_ = $matchtype;
$xml = $this->registry_->matchtype($this->matchType_);
if (isset($xml))
{
// Add possible value and dependancy
$this->addArguments_($this->matchType_, $this->makeArguments_($xml));
$this->addDependency_('match type', $this->matchType_, $xml->requires);
}
}
/**
* Hook function that is called after a comparator was found in
* a command. The comparator is remembered in case it's needed for
* comparsion later {@see done}. For a comparator from extensions
* dependency information is looked up as well.
* @param string $comparator
*/
protected function comparatorHook_($comparator)
{
$this->comparator_ = $comparator;
$xml = $this->registry_->comparator($this->comparator_);
if (isset($xml))
{
// Add possible dependancy
$this->addDependency_('comparator', $this->comparator_, $xml->requires);
}
}
/**
* Hook function that is called after a tag was found in
* a command. The tag is remembered in case it's needed for
* comparsion later {@see done}. For a tags from extensions
* dependency information is looked up as well.
* @param string $tag
*/
protected function tagHook_($tag)
{
array_push($this->tags_, $tag);
$xml = $this->registry_->argument($tag);
// Add possible dependancies
if (isset($xml))
$this->addDependency_('tag', $tag, $xml->requires);
}
protected function validType_($token)
{
foreach ($this->arguments_ as $arg)
{
if ($arg['occurrence'] == '0')
{
array_shift($this->arguments_);
continue;
}
if ($token->is($arg['type']))
return;
// Is the argument required
if ($arg['occurrence'] != '?' && $arg['occurrence'] != '*')
throw new SieveException($token, $arg['type']);
array_shift($this->arguments_);
}
// Check if command expects any (more) arguments
if (empty($this->arguments_))
throw new SieveException($token, $this->followupToken_);
throw new SieveException($token, 'unexpected '. SieveToken::typeString($token->type) .' '. $token->text);
}
public function startStringList($token)
{
$this->validType_($token);
$this->arguments_[0]['type'] = SieveToken::String;
$this->arguments_[0]['occurrence'] = '+';
}
public function continueStringList()
{
$this->arguments_[0]['occurrence'] = '+';
}
public function endStringList()
{
array_shift($this->arguments_);
}
public function validateToken($token)
{
// Make sure the argument has a valid type
$this->validType_($token);
foreach ($this->arguments_ as &$arg)
{
// Build regular expression according to argument type
switch ($arg['type'])
{
case SieveToken::String:
case SieveToken::StringList:
$regex = '/^(?:text:[^\n]*\n(?P<one>'. $arg['regex'] .')\.\r?\n?|"(?P<two>'. $arg['regex'] .')")$/'
. ($arg['case'] == 'ignore' ? 'si' : 's');
break;
case SieveToken::Tag:
$regex = '/^:(?P<one>'. $arg['regex'] .')$/si';
break;
default:
$regex = '/^(?P<one>'. $arg['regex'] .')$/si';
}
if (preg_match($regex, $token->text, $match))
{
$text = ($match['one'] ? $match['one'] : $match['two']);
// Add argument(s) that may now appear after this one
if (isset($arg['subArgs']))
$this->addArguments_($text, $arg['subArgs']);
// Call extra processing function if defined
if (isset($arg['call']))
$this->invoke_($token, $arg['call'], $text);
// Check if a possible value of this argument may occur
if ($arg['occurrence'] == '?' || $arg['occurrence'] == '1')
{
$arg['occurrence'] = '0';
}
else if ($arg['occurrence'] == '+')
{
$arg['occurrence'] = '*';
}
return;
}
if ($token->is($arg['type']) && $arg['occurrence'] == 1)
{
throw new SieveException($token,
SieveToken::typeString($token->type) ." $token->text where ". $arg['name'] .' expected');
}
}
throw new SieveException($token, 'unexpected '. SieveToken::typeString($token->type) .' '. $token->text);
}
public function done($token)
{
// Check if there are required arguments left
foreach ($this->arguments_ as $arg)
{
if ($arg['occurrence'] == '+' || $arg['occurrence'] == '1')
throw new SieveException($token, $arg['type']);
}
// Check if the command depends on use of a certain tag
foreach ($this->deps_ as $d)
{
switch ($d['type'])
{
case 'addresspart':
$values = array($this->addressPart_);
break;
case 'matchtype':
$values = array($this->matchType_);
break;
case 'comparator':
$values = array($this->comparator_);
break;
case 'tag':
$values = $this->tags_;
break;
}
foreach ($values as $value)
{
if (preg_match('/^'. $d['regex'] .'$/mi', $value))
break 2;
}
throw new SieveException($token,
$d['o_type'] .' '. $d['o_name'] .' requires use of '. $d['type'] .' '. $d['name']);
}
}
}

View File

@ -0,0 +1,88 @@
<?php namespace Sieve;
include_once('SieveDumpable.php');
class SieveToken implements SieveDumpable
{
const Unknown = 0x0000;
const ScriptEnd = 0x0001;
const LeftBracket = 0x0002;
const RightBracket = 0x0004;
const BlockStart = 0x0008;
const BlockEnd = 0x0010;
const LeftParenthesis = 0x0020;
const RightParenthesis = 0x0040;
const Comma = 0x0080;
const Semicolon = 0x0100;
const Whitespace = 0x0200;
const Tag = 0x0400;
const QuotedString = 0x0800;
const Number = 0x1000;
const Comment = 0x2000;
const MultilineString = 0x4000;
const Identifier = 0x8000;
const String = 0x4800; // Quoted | Multiline
const StringList = 0x4802; // Quoted | Multiline | LeftBracket
const StringListSep = 0x0084; // Comma | RightBracket
const Unparsed = 0x2200; // Comment | Whitespace
const TestList = 0x8020; // Identifier | LeftParenthesis
public $type;
public $text;
public $line;
public function __construct($type, $text, $line)
{
$this->text = $text;
$this->type = $type;
$this->line = intval($line);
}
public function dump()
{
return '<'. SieveToken::escape($this->text) .'> type:'. SieveToken::typeString($this->type) .' line:'. $this->line;
}
public function text()
{
return $this->text;
}
public function is($type)
{
return (bool)($this->type & $type);
}
public static function typeString($type)
{
switch ($type)
{
case SieveToken::Identifier: return 'identifier';
case SieveToken::Whitespace: return 'whitespace';
case SieveToken::QuotedString: return 'quoted string';
case SieveToken::Tag: return 'tag';
case SieveToken::Semicolon: return 'semicolon';
case SieveToken::LeftBracket: return 'left bracket';
case SieveToken::RightBracket: return 'right bracket';
case SieveToken::BlockStart: return 'block start';
case SieveToken::BlockEnd: return 'block end';
case SieveToken::LeftParenthesis: return 'left parenthesis';
case SieveToken::RightParenthesis: return 'right parenthesis';
case SieveToken::Comma: return 'comma';
case SieveToken::Number: return 'number';
case SieveToken::Comment: return 'comment';
case SieveToken::MultilineString: return 'multiline string';
case SieveToken::ScriptEnd: return 'script end';
case SieveToken::String: return 'string';
case SieveToken::StringList: return 'string list';
default: return 'unknown token';
}
}
protected static $tr_ = array("\r" => '\r', "\n" => '\n', "\t" => '\t');
public static function escape($val)
{
return strtr($val, self::$tr_);
}
}

View File

@ -0,0 +1,117 @@
<?php namespace Sieve;
class SieveTree
{
protected $childs_;
protected $parents_;
protected $nodes_;
protected $max_id_;
protected $dump_;
public function __construct($name = 'tree')
{
$this->childs_ = array();
$this->parents_ = array();
$this->nodes_ = array();
$this->max_id_ = 0;
$this->parents_[0] = null;
$this->nodes_[0] = $name;
}
public function addChild(SieveDumpable $child)
{
return $this->addChildTo($this->max_id_, $child);
}
public function addChildTo($parent_id, SieveDumpable $child)
{
if (!is_int($parent_id)
|| !isset($this->nodes_[$parent_id]))
return null;
if (!isset($this->childs_[$parent_id]))
$this->childs_[$parent_id] = array();
$child_id = ++$this->max_id_;
$this->nodes_[$child_id] = $child;
$this->parents_[$child_id] = $parent_id;
array_push($this->childs_[$parent_id], $child_id);
return $child_id;
}
public function getRoot()
{
return 0;
}
public function getChilds($node_id)
{
if (!is_int($node_id)
|| !isset($this->nodes_[$node_id]))
return null;
if (!isset($this->childs_[$node_id]))
return array();
return $this->childs_[$node_id];
}
public function getNode($node_id)
{
if ($node_id == 0 || !is_int($node_id)
|| !isset($this->nodes_[$node_id]))
return null;
return $this->nodes_[$node_id];
}
public function dump()
{
$this->dump_ = $this->nodes_[$this->getRoot()] ."\n";
$this->dumpChilds_($this->getRoot(), ' ');
return $this->dump_;
}
protected function dumpChilds_($parent_id, $prefix)
{
if (!isset($this->childs_[$parent_id]))
return;
$childs = $this->childs_[$parent_id];
$last_child = count($childs);
for ($i=1; $i <= $last_child; ++$i)
{
$child_node = $this->nodes_[$childs[$i-1]];
$infix = ($i == $last_child ? '`--- ' : '|--- ');
$this->dump_ .= $prefix . $infix . $child_node->dump() . " (id:" . $childs[$i-1] . ")\n";
$next_prefix = $prefix . ($i == $last_child ? ' ' : '| ');
$this->dumpChilds_($childs[$i-1], $next_prefix);
}
}
public function getText()
{
$this->dump_ = '';
$this->childText_($this->getRoot());
return $this->dump_;
}
protected function childText_($parent_id)
{
if (!isset($this->childs_[$parent_id]))
return;
$childs = $this->childs_[$parent_id];
for ($i = 0; $i < count($childs); ++$i)
{
$child_node = $this->nodes_[$childs[$i]];
$this->dump_ .= $child_node->text();
$this->childText_($childs[$i]);
}
}
}

View File

@ -0,0 +1,14 @@
<?xml version='1.0' standalone='yes'?>
<extension name="body">
<test name="body">
<parameter type="matchtype" occurrence="optional" />
<parameter type="comparator" occurrence="optional" />
<parameter type="tag" name="body transform" regex="(raw|content|text)" occurrence="optional">
<parameter type="stringlist" name="content types" follows="content" />
</parameter>
<parameter type="stringlist" name="key list" />
</test>
</extension>

View File

@ -0,0 +1,7 @@
<?xml version='1.0' standalone='yes'?>
<extension name="comparator-i;ascii-numeric">
<comparator name="i;ascii-numeric" />
</extension>

View File

@ -0,0 +1,9 @@
<?xml version='1.0' standalone='yes'?>
<extension name="copy">
<tagged-argument extends="(fileinto|redirect)">
<parameter type="tag" name="copy" regex="copy" occurrence="optional" />
</tagged-argument>
</extension>

View File

@ -0,0 +1,28 @@
<?xml version='1.0' standalone='yes'?>
<extension name="date">
<test name="date">
<parameter type="matchtype" occurrence="optional" />
<parameter type="comparator" occurrence="optional" />
<parameter type="tag" name="zone" regex="(zone|originalzone)" occurrence="optional">
<parameter type="string" name="time-zone" follows="zone" />
</parameter>
<parameter type="string" name="header-name" />
<parameter type="string" case="ignore" name="date-part"
regex="(year|month|day|date|julian|hour|minute|second|time|iso8601|std11|zone|weekday)" />
<parameter type="stringlist" name="key-list" />
</test>
<test name="currentdate">
<parameter type="matchtype" occurrence="optional" />
<parameter type="comparator" occurrence="optional" />
<parameter type="tag" name="zone" regex="zone" occurrence="optional">
<parameter type="string" name="time-zone" />
</parameter>
<parameter type="string" case="ignore" name="date-part"
regex="(year|month|day|date|julian|hour|minute|second|time|iso8601|std11|zone|weekday)" />
<parameter type="stringlist" name="key-list" />
</test>
</extension>

View File

@ -0,0 +1,22 @@
<?xml version='1.0' standalone='yes'?>
<extension name="editheader">
<command name="addheader">
<parameter type="tag" name="last" regex="last" occurrence="optional" />
<parameter type="string" name="field name" />
<parameter type="string" name="value" />
</command>
<command name="deleteheader">
<parameter type="tag" name="index" regex="index" occurrence="optional">
<parameter type="number" name="field number" />
<parameter type="tag" name="last" regex="last" occurrence="optional" />
</parameter>
<parameter type="matchtype" occurrence="optional" />
<parameter type="comparator" occurrence="optional" />
<parameter type="string" name="field name" />
<parameter type="stringlist" name="value patterns" occurrence="optional" />
</command>
</extension>

View File

@ -0,0 +1,13 @@
<?xml version='1.0' standalone='yes'?>
<extension name="envelope">
<test name="envelope">
<parameter type="matchtype" occurrence="optional" />
<parameter type="comparator" occurrence="optional" />
<parameter type="addresspart" occurrence="optional" />
<parameter type="stringlist" name="envelope-part" />
<parameter type="stringlist" name="key" />
</test>
</extension>

View File

@ -0,0 +1,13 @@
<?xml version='1.0' standalone='yes'?>
<extension name="environment">
<test name="environment">
<parameter type="matchtype" occurrence="optional" />
<parameter type="comparator" occurrence="optional" />
<parameter type="string" name="name"
regex="(domain|host|location|name|phase|remote-host|remote-ip|version|vnd\..+)" />
<parameter type="stringlist" name="key-list" />
</test>
</extension>

View File

@ -0,0 +1,11 @@
<?xml version='1.0' standalone='yes'?>
<extension name="ereject">
<command name="ereject">
<parameter type="string" name="reason" />
</command>
</extension>

View File

@ -0,0 +1,9 @@
<?xml version='1.0' standalone='yes'?>
<extension name="fileinto">
<command name="fileinto">
<parameter type="string" name="folder" />
</command>
</extension>

View File

@ -0,0 +1,29 @@
<?xml version='1.0' standalone='yes'?>
<extension name="imap4flags">
<command name="setflag">
<parameter type="stringlist" name="flag list" />
</command>
<command name="addflag">
<parameter type="stringlist" name="flag list" />
</command>
<command name="removeflag">
<parameter type="stringlist" name="flag list" />
</command>
<test name="hasflag">
<parameter type="matchtype" occurrence="optional" />
<parameter type="comparator" occurrence="optional" />
<parameter type="stringlist" name="flag list" />
</test>
<tagged-argument extends="(fileinto|keep)">
<parameter type="tag" name="flags" regex="flags" occurrence="optional">
<parameter type="stringlist" name="flag list" />
</parameter>
</tagged-argument>
</extension>

View File

@ -0,0 +1,21 @@
<?xml version='1.0' standalone='yes'?>
<extension name="imapflags">
<command name="mark" />
<command name="unmark" />
<command name="setflag">
<parameter type="stringlist" name="flag list" />
</command>
<command name="addflag">
<parameter type="stringlist" name="flag list" />
</command>
<command name="removeflag">
<parameter type="stringlist" name="flag list" />
</command>
</extension>

View File

@ -0,0 +1,17 @@
<?xml version='1.0' standalone='yes'?>
<extension name="index">
<tagged-argument extends="(header|address|date)">
<parameter type="tag" name="index" regex="index" occurrence="optional">
<parameter type="number" name="field number" />
</parameter>
</tagged-argument>
<tagged-argument extends="(header|address|date)">
<parameter type="tag" name="last" regex="last" occurrence="optional">
<requires type="tag" name="index" regex="index" />
</parameter>
</tagged-argument>
</extension>

View File

@ -0,0 +1,29 @@
<?xml version='1.0' standalone='yes'?>
<extension name="notify">
<command name="notify">
<parameter type="tag" name="method" regex="method" occurrence="optional">
<parameter type="string" name="method-name" />
</parameter>
<parameter type="tag" name="id" regex="id" occurrence="optional">
<parameter type="string" name="message-id" />
</parameter>
<parameter type="tag" name="priority" regex="(low|normal|high)" occurrence="optional" />
<parameter type="tag" name="message" regex="message" occurrence="optional">
<parameter type="string" name="message-text" />
</parameter>
</command>
<command name="denotify">
<parameter type="matchtype" occurrence="optional">
<parameter type="string" name="message-id" />
</parameter>
<parameter type="tag" name="priority" regex="(low|normal|high)" occurrence="optional" />
</command>
</extension>

View File

@ -0,0 +1,11 @@
<?xml version='1.0' standalone='yes'?>
<extension name="regex">
<matchtype name="regex" />
<tagged-argument extends="set">
<parameter type="tag" name="modifier" regex="quoteregex" occurrence="optional" />
</tagged-argument>
</extension>

View File

@ -0,0 +1,11 @@
<?xml version='1.0' standalone='yes'?>
<extension name="reject">
<command name="reject">
<parameter type="string" name="reason" />
</command>
</extension>

View File

@ -0,0 +1,14 @@
<?xml version='1.0' standalone='yes'?>
<extension name="relational">
<matchtype name="count">
<requires type="comparator" name="i;ascii-numeric" regex="i;ascii-numeric" />
<parameter type="string" name="relation string" regex="(lt|le|eq|ge|gt|ne)" />
</matchtype>
<matchtype name="value">
<parameter type="string" name="relation string" regex="(lt|le|eq|ge|gt|ne)" />
</matchtype>
</extension>

View File

@ -0,0 +1,11 @@
<?xml version='1.0' standalone='yes'?>
<extension name="spamtest">
<test name="spamtest">
<parameter type="comparator" occurrence="optional" />
<parameter type="matchtype" occurrence="optional" />
<parameter type="string" name="value" />
</test>
</extension>

View File

@ -0,0 +1,12 @@
<?xml version='1.0' standalone='yes'?>
<extension name="spamtestplus">
<test name="spamtest" overrides="true">
<parameter type="comparator" occurrence="optional" />
<parameter type="matchtype" occurrence="optional" />
<parameter type="tag" name="percent" regex="percent" occurrence="optional" />
<parameter type="string" name="value" />
</test>
</extension>

View File

@ -0,0 +1,8 @@
<?xml version='1.0' standalone='yes'?>
<extension name="subaddress">
<addresspart name="user" />
<addresspart name="detail" />
</extension>

View File

@ -0,0 +1,31 @@
<?xml version='1.0' standalone='yes'?>
<extension name="vacation">
<command name="vacation">
<parameter type="tag" name="days" occurrence="optional" regex="days">
<parameter type="number" name="period" />
</parameter>
<parameter type="tag" name="addresses" occurrence="optional" regex="addresses">
<parameter type="stringlist" name="address strings" />
</parameter>
<parameter type="tag" name="subject" occurrence="optional" regex="subject">
<parameter type="string" name="subject string" />
</parameter>
<parameter type="tag" name="from" occurrence="optional" regex="from">
<parameter type="string" name="from string" />
</parameter>
<parameter type="tag" name="handle" occurrence="optional" regex="handle">
<parameter type="string" name="handle string" />
</parameter>
<parameter type="tag" name="mime" occurrence="optional" regex="mime" />
<parameter type="string" name="reason" />
</command>
</extension>

View File

@ -0,0 +1,21 @@
<?xml version='1.0' standalone='yes'?>
<extension name="variables">
<command name="set">
<parameter type="tag" name="modifier" regex="(lower|upper)" occurrence="optional" />
<parameter type="tag" name="modifier" regex="(lower|upper)first" occurrence="optional" />
<parameter type="tag" name="modifier" regex="quotewildcard" occurrence="optional" />
<parameter type="tag" name="modifier" regex="length" occurrence="optional" />
<parameter type="string" name="name" regex="[[:alpha:]_][[:alnum:]_]*" />
<parameter type="string" name="value" />
</command>
<test name="string">
<parameter type="matchtype" occurrence="optional" />
<parameter type="comparator" occurrence="optional" />
<parameter type="stringlist" name="source" />
<parameter type="stringlist" name="key list" />
</test>
</extension>

View File

@ -0,0 +1,11 @@
<?xml version='1.0' standalone='yes'?>
<extension name="virustest">
<test name="virustest">
<parameter type="comparator" occurrence="optional" />
<parameter type="matchtype" occurrence="optional" />
<parameter type="string" name="value" />
</test>
</extension>

View File

@ -0,0 +1,91 @@
<?xml version='1.0' standalone='yes'?>
<keywords>
<matchtype name="is" />
<matchtype name="contains" />
<matchtype name="matches" />
<matchtype name="value">
<parameter type="string" name="operator" regex="(gt|ge|eq|le|lt)" />
</matchtype>
<comparator name="i;octet" />
<comparator name="i;ascii-casemap" />
<comparator name="i;unicode-casemap" />
<addresspart name="all" />
<addresspart name="localpart" />
<addresspart name="domain" />
<command name="discard" />
<command name="elsif">
<parameter type="test" name="test command" />
<parameter type="block" />
</command>
<command name="else">
<parameter type="block" />
</command>
<command name="if">
<parameter type="test" name="test command" />
<parameter type="block" />
</command>
<command name="keep" />
<command name="redirect">
<parameter type="string" name="address string" />
</command>
<command name="require">
<parameter type="requirestrings" name="require string" />
</command>
<command name="stop" />
<test name="address">
<parameter type="matchtype" occurrence="optional" />
<parameter type="comparator" occurrence="optional" />
<parameter type="addresspart" occurrence="optional" />
<parameter type="stringlist" name="header list" />
<parameter type="stringlist" name="key list" />
</test>
<test name="allof">
<parameter type="testlist" name="test" />
</test>
<test name="anyof">
<parameter type="testlist" name="test" />
</test>
<test name="exists">
<parameter type="stringlist" name="header names" />
</test>
<test name="false" />
<test name="header">
<parameter type="matchtype" occurrence="optional" />
<parameter type="comparator" occurrence="optional" />
<parameter type="stringlist" name="header names" />
<parameter type="stringlist" name="key list" />
</test>
<test name="not">
<parameter type="test" />
</test>
<test name="size">
<parameter type="tag" regex="(over|under)" />
<parameter type="number" name="limit" />
</test>
<test name="true" />
</keywords>

View File

@ -16,6 +16,9 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/Yubico.php';
// Autoload composer // Autoload composer
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/vendor/autoload.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/vendor/autoload.php';
// Load Sieve
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/sieve/SieveParser.php';
// U2F API + T/HOTP API // U2F API + T/HOTP API
$u2f = new u2flib_server\U2F('https://' . $_SERVER['HTTP_HOST']); $u2f = new u2flib_server\U2F('https://' . $_SERVER['HTTP_HOST']);
$tfa = new RobThree\Auth\TwoFactorAuth($OTP_LABEL); $tfa = new RobThree\Auth\TwoFactorAuth($OTP_LABEL);
@ -70,6 +73,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.dkim.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fwdhost.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fwdhost.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.relayhost.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.relayhost.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fail2ban.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fail2ban.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.docker.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/init_db.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/init_db.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.inc.php';
init_db_schema(); init_db_schema();

View File

@ -62,6 +62,8 @@ if (isset($_POST["logout"])) {
$_SESSION["mailcow_cc_username"] = $_SESSION["dual-login"]["username"]; $_SESSION["mailcow_cc_username"] = $_SESSION["dual-login"]["username"];
$_SESSION["mailcow_cc_role"] = $_SESSION["dual-login"]["role"]; $_SESSION["mailcow_cc_role"] = $_SESSION["dual-login"]["role"];
unset($_SESSION["dual-login"]); unset($_SESSION["dual-login"]);
header("Location: /mailbox.php");
exit();
} }
else { else {
session_regenerate_id(true); session_regenerate_id(true);

View File

@ -104,6 +104,9 @@ $MAILCOW_APPS = array(
// Rows until pagination begins // Rows until pagination begins
$PAGINATION_SIZE = 10; $PAGINATION_SIZE = 10;
// Default number of rows/lines to display (log table)
$LOG_LINES = 100;
// Rows until pagination begins (log table) // Rows until pagination begins (log table)
$LOG_PAGINATION_SIZE = 30; $LOG_PAGINATION_SIZE = 30;

View File

@ -1,125 +1,10 @@
var Base64 = { // Base64 functions
_keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(r){var t,e,o,a,h,n,c,d="",C=0;for(r=Base64._utf8_encode(r);C<r.length;)a=(t=r.charCodeAt(C++))>>2,h=(3&t)<<4|(e=r.charCodeAt(C++))>>4,n=(15&e)<<2|(o=r.charCodeAt(C++))>>6,c=63&o,isNaN(e)?n=c=64:isNaN(o)&&(c=64),d=d+this._keyStr.charAt(a)+this._keyStr.charAt(h)+this._keyStr.charAt(n)+this._keyStr.charAt(c);return d},decode:function(r){var t,e,o,a,h,n,c="",d=0;for(r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");d<r.length;)t=this._keyStr.indexOf(r.charAt(d++))<<2|(a=this._keyStr.indexOf(r.charAt(d++)))>>4,e=(15&a)<<4|(h=this._keyStr.indexOf(r.charAt(d++)))>>2,o=(3&h)<<6|(n=this._keyStr.indexOf(r.charAt(d++))),c+=String.fromCharCode(t),64!=h&&(c+=String.fromCharCode(e)),64!=n&&(c+=String.fromCharCode(o));return c=Base64._utf8_decode(c)},_utf8_encode:function(r){r=r.replace(/\r\n/g,"\n");for(var t="",e=0;e<r.length;e++){var o=r.charCodeAt(e);o<128?t+=String.fromCharCode(o):o>127&&o<2048?(t+=String.fromCharCode(o>>6|192),t+=String.fromCharCode(63&o|128)):(t+=String.fromCharCode(o>>12|224),t+=String.fromCharCode(o>>6&63|128),t+=String.fromCharCode(63&o|128))}return t},_utf8_decode:function(r){for(var t="",e=0,o=c1=c2=0;e<r.length;)(o=r.charCodeAt(e))<128?(t+=String.fromCharCode(o),e++):o>191&&o<224?(c2=r.charCodeAt(e+1),t+=String.fromCharCode((31&o)<<6|63&c2),e+=2):(c2=r.charCodeAt(e+1),c3=r.charCodeAt(e+2),t+=String.fromCharCode((15&o)<<12|(63&c2)<<6|63&c3),e+=3);return t}};
encode: function(e) {
var t = "";
var n, r, i, s, o, u, a;
var f = 0;
e = Base64._utf8_encode(e);
while (f < e.length) {
n = e.charCodeAt(f++);
r = e.charCodeAt(f++);
i = e.charCodeAt(f++);
s = n >> 2;
o = (n & 3) << 4 | r >> 4;
u = (r & 15) << 2 | i >> 6;
a = i & 63;
if (isNaN(r)) {
u = a = 64
} else if (isNaN(i)) {
a = 64
}
t = t + this._keyStr.charAt(s) + this._keyStr.charAt(o) +
this._keyStr.charAt(u) + this._keyStr.charAt(a)
}
return t
},
decode: function(e) {
var t = "";
var n, r, i;
var s, o, u, a;
var f = 0;
e = e.replace(/[^A-Za-z0-9\+\/\=]/g, "");
while (f < e.length) {
s = this._keyStr.indexOf(e.charAt(f++));
o = this._keyStr.indexOf(e.charAt(f++));
u = this._keyStr.indexOf(e.charAt(f++));
a = this._keyStr.indexOf(e.charAt(f++));
n = s << 2 | o >> 4;
r = (o & 15) << 4 | u >> 2;
i = (u & 3) << 6 | a;
t = t + String.fromCharCode(n);
if (u != 64) {
t = t + String.fromCharCode(r)
}
if (a != 64) {
t = t + String.fromCharCode(i)
}
}
t = Base64._utf8_decode(t);
return t
},
_utf8_encode: function(e) {
e = e.replace(/\r\n/g, "\n");
var t = "";
for (var n = 0; n < e.length; n++) {
var r = e.charCodeAt(n);
if (r < 128) {
t += String.fromCharCode(r)
} else if (r > 127 && r < 2048) {
t += String.fromCharCode(r >> 6 | 192);
t += String.fromCharCode(r & 63 | 128)
} else {
t += String.fromCharCode(r >> 12 | 224);
t += String.fromCharCode(r >> 6 & 63 | 128);
t += String.fromCharCode(r & 63 | 128)
}
}
return t
},
_utf8_decode: function(e) {
var t = "";
var n = 0;
var r = c1 = c2 = 0;
while (n < e.length) {
r = e.charCodeAt(n);
if (r < 128) {
t += String.fromCharCode(r);
n++
} else if (r > 191 && r < 224) {
c2 = e.charCodeAt(n + 1);
t += String.fromCharCode((r & 31) << 6 | c2 & 63);
n += 2
} else {
c2 = e.charCodeAt(n + 1);
c3 = e.charCodeAt(n + 2);
t += String.fromCharCode((r & 15) << 12 | (c2 & 63) <<
6 | c3 & 63);
n += 3
}
}
return t
}
}
jQuery(function($){ jQuery(function($){
// http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery
var entityMap = { var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};
'&': '&amp;', function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
'<': '&lt;', function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
'/': '&#x2F;',
'`': '&#x60;',
'=': '&#x3D;'
};
function escapeHtml(string) {
return String(string).replace(/[&<>"'`=\/]/g, function (s) {
return entityMap[s];
});
}
function humanFileSize(bytes) {
if(Math.abs(bytes) < 1024) {
return bytes + ' B';
}
var units = ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
var u = -1;
do {
bytes /= 1024;
++u;
} while(Math.abs(bytes) >= 1024 && u < units.length - 1);
return bytes.toFixed(1)+' '+units[u];
}
$("#refresh_postfix_log").on('click', function(e) { $("#refresh_postfix_log").on('click', function(e) {
e.preventDefault(); e.preventDefault();
draw_postfix_logs(); draw_postfix_logs();
@ -148,54 +33,6 @@ jQuery(function($){
e.preventDefault(); e.preventDefault();
$('#import_dkim_arrow').toggleClass("animation"); $('#import_dkim_arrow').toggleClass("animation");
}); });
function draw_postfix_logs() {
ft_postfix_logs = FooTable.init('#postfix_log', {
"columns": [
{"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
{"name":"priority","title":lang.priority,"style":{"width":"80px"}},
{"name":"message","title":lang.message},
],
"rows": $.ajax({
dataType: 'json',
url: '/api/v1/get/logs/postfix/1000',
jsonp: false,
error: function () {
console.log('Cannot draw postfix log table');
},
success: function (data) {
$.each(data, function (i, item) {
item.message = escapeHtml(item.message);
var danger_class = ["emerg", "alert", "crit", "err"];
var warning_class = ["warning", "warn"];
var info_class = ["notice", "info", "debug"];
if (jQuery.inArray(item.priority, danger_class) !== -1) {
item.priority = '<span class="label label-danger">' + item.priority + '</span>';
}
else if (jQuery.inArray(item.priority, warning_class) !== -1) {
item.priority = '<span class="label label-warning">' + item.priority + '</span>';
}
else if (jQuery.inArray(item.priority, info_class) !== -1) {
item.priority = '<span class="label label-info">' + item.priority + '</span>';
}
});
}
}),
"empty": lang.empty,
"paging": {
"enabled": true,
"limit": 5,
"size": log_pagination_size
},
"filtering": {
"enabled": true,
"position": "left",
"placeholder": lang.filter_table
},
"sorting": {
"enabled": true
}
});
}
function draw_autodiscover_logs() { function draw_autodiscover_logs() {
ft_autodiscover_logs = FooTable.init('#autodiscover_log', { ft_autodiscover_logs = FooTable.init('#autodiscover_log', {
"columns": [ "columns": [
@ -212,33 +49,53 @@ jQuery(function($){
console.log('Cannot draw autodiscover log table'); console.log('Cannot draw autodiscover log table');
}, },
success: function (data) { success: function (data) {
$.each(data, function (i, item) { return process_table_data(data, 'autodiscover_log');
item.ua = '<span style="font-size:small">' + item.ua + '</span>';
if (item.service == "activesync") {
item.service = '<span class="label label-info">ActiveSync</span>';
}
else if (item.service == "imap") {
item.service = '<span class="label label-success">IMAP, SMTP, Cal-/CardDAV</span>';
}
else {
item.service = '<span class="label label-danger">' + item.service + '</span>';
}
});
} }
}), }),
"empty": lang.empty, "empty": lang.empty,
"paging": { "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
"enabled": true, "filtering": {"enabled": true,"position": "left","placeholder": lang.filter_table},
"limit": 5, "sorting": {"enabled": true},
"size": log_pagination_size "on": {"ready.ft.table": function(e, ft){
}, heading = ft.$el.parents('.tab-pane').find('.panel-heading')
"filtering": { $(heading).children('.log-lines').text(function(){
"enabled": true, var ft_paging = ft.use(FooTable.Paging)
"position": "left", return ft_paging.totalRows;
"placeholder": lang.filter_table })
}, }
"sorting": { }
"enabled": true });
}
function draw_postfix_logs() {
ft_postfix_logs = FooTable.init('#postfix_log', {
"columns": [
{"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
{"name":"priority","title":lang.priority,"style":{"width":"80px"}},
{"name":"message","title":lang.message},
],
"rows": $.ajax({
dataType: 'json',
url: '/api/v1/get/logs/postfix',
jsonp: false,
error: function () {
console.log('Cannot draw postfix log table');
},
success: function (data) {
return process_table_data(data, 'general_syslog');
}
}),
"empty": lang.empty,
"paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
"filtering": {"enabled": true,"position": "left","placeholder": lang.filter_table},
"sorting": {"enabled": true},
"on": {
"ready.ft.table": function(e, ft){
heading = ft.$el.parents('.tab-pane').find('.panel-heading')
$(heading).children('.log-lines').text(function(){
var ft_paging = ft.use(FooTable.Paging)
return ft_paging.totalRows;
})
}
} }
}); });
} }
@ -251,43 +108,27 @@ jQuery(function($){
], ],
"rows": $.ajax({ "rows": $.ajax({
dataType: 'json', dataType: 'json',
url: '/api/v1/get/logs/fail2ban/1000', url: '/api/v1/get/logs/fail2ban',
jsonp: false, jsonp: false,
error: function () { error: function () {
console.log('Cannot draw fail2ban log table'); console.log('Cannot draw fail2ban log table');
}, },
success: function (data) { success: function (data) {
$.each(data, function (i, item) { return process_table_data(data, 'general_syslog');
var danger_class = ["emerg", "alert", "crit", "err"];
var warning_class = ["warning", "warn"];
var info_class = ["notice", "info", "debug"];
item.message = escapeHtml(item.message);
if (jQuery.inArray(item.priority, danger_class) !== -1) {
item.priority = '<span class="label label-danger">' + item.priority + '</span>';
}
else if (jQuery.inArray(item.priority, warning_class) !== -1) {
item.priority = '<span class="label label-warning">' + item.priority + '</span>';
}
else if (jQuery.inArray(item.priority, info_class) !== -1) {
item.priority = '<span class="label label-info">' + item.priority + '</span>';
}
});
} }
}), }),
"empty": lang.empty, "empty": lang.empty,
"paging": { "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
"enabled": true, "filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table},
"limit": 5, "sorting": {"enabled": true},
"size": log_pagination_size "on": {
}, "ready.ft.table": function(e, ft){
"filtering": { heading = ft.$el.parents('.tab-pane').find('.panel-heading')
"enabled": true, $(heading).children('.log-lines').text(function(){
"position": "left", var ft_paging = ft.use(FooTable.Paging)
"connectors": false, return ft_paging.totalRows;
"placeholder": lang.filter_table })
}, }
"sorting": {
"enabled": true
} }
}); });
} }
@ -300,48 +141,32 @@ jQuery(function($){
], ],
"rows": $.ajax({ "rows": $.ajax({
dataType: 'json', dataType: 'json',
url: '/api/v1/get/logs/sogo/1000', url: '/api/v1/get/logs/sogo',
jsonp: false, jsonp: false,
error: function () { error: function () {
console.log('Cannot draw sogo log table'); console.log('Cannot draw sogo log table');
}, },
success: function (data) { success: function (data) {
$.each(data, function (i, item) { return process_table_data(data, 'general_syslog');
var danger_class = ["emerg", "alert", "crit", "err"];
var warning_class = ["warning", "warn"];
var info_class = ["notice", "info", "debug"];
item.message = escapeHtml(item.message);
if (jQuery.inArray(item.priority, danger_class) !== -1) {
item.priority = '<span class="label label-danger">' + item.priority + '</span>';
}
else if (jQuery.inArray(item.priority, warning_class) !== -1) {
item.priority = '<span class="label label-warning">' + item.priority + '</span>';
}
else if (jQuery.inArray(item.priority, info_class) !== -1) {
item.priority = '<span class="label label-info">' + item.priority + '</span>';
}
});
} }
}), }),
"empty": lang.empty, "empty": lang.empty,
"paging": { "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
"enabled": true, "filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table},
"limit": 5, "sorting": {"enabled": true},
"size": log_pagination_size "on": {
}, "ready.ft.table": function(e, ft){
"filtering": { heading = ft.$el.parents('.tab-pane').find('.panel-heading')
"enabled": true, $(heading).children('.log-lines').text(function(){
"position": "left", var ft_paging = ft.use(FooTable.Paging)
"connectors": false, return ft_paging.totalRows;
"placeholder": lang.filter_table })
}, }
"sorting": {
"enabled": true
} }
}); });
} }
function draw_dovecot_logs() { function draw_dovecot_logs() {
ft_postfix_logs = FooTable.init('#dovecot_log', { ft_dovecot_logs = FooTable.init('#dovecot_log', {
"columns": [ "columns": [
{"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}}, {"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
{"name":"priority","title":lang.priority,"style":{"width":"80px"}}, {"name":"priority","title":lang.priority,"style":{"width":"80px"}},
@ -349,43 +174,27 @@ jQuery(function($){
], ],
"rows": $.ajax({ "rows": $.ajax({
dataType: 'json', dataType: 'json',
url: '/api/v1/get/logs/dovecot/1000', url: '/api/v1/get/logs/dovecot',
jsonp: false, jsonp: false,
error: function () { error: function () {
console.log('Cannot draw dovecot log table'); console.log('Cannot draw dovecot log table');
}, },
success: function (data) { success: function (data) {
$.each(data, function (i, item) { return process_table_data(data, 'general_syslog');
var danger_class = ["emerg", "alert", "crit", "err"];
var warning_class = ["warning", "warn"];
var info_class = ["notice", "info", "debug"];
item.message = escapeHtml(item.message);
if (jQuery.inArray(item.priority, danger_class) !== -1) {
item.priority = '<span class="label label-danger">' + item.priority + '</span>';
}
else if (jQuery.inArray(item.priority, warning_class) !== -1) {
item.priority = '<span class="label label-warning">' + item.priority + '</span>';
}
else if (jQuery.inArray(item.priority, info_class) !== -1) {
item.priority = '<span class="label label-info">' + item.priority + '</span>';
}
});
} }
}), }),
"empty": lang.empty, "empty": lang.empty,
"paging": { "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
"enabled": true, "filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table},
"limit": 5, "sorting": {"enabled": true},
"size": log_pagination_size "on": {
}, "ready.ft.table": function(e, ft){
"filtering": { heading = ft.$el.parents('.tab-pane').find('.panel-heading')
"enabled": true, $(heading).children('.log-lines').text(function(){
"position": "left", var ft_paging = ft.use(FooTable.Paging)
"connectors": false, return ft_paging.totalRows;
"placeholder": lang.filter_table })
}, }
"sorting": {
"enabled": true
} }
}); });
} }
@ -407,30 +216,14 @@ jQuery(function($){
console.log('Cannot draw domain admin table'); console.log('Cannot draw domain admin table');
}, },
success: function (data) { success: function (data) {
$.each(data, function (i, item) { return process_table_data(data, 'domainadminstable');
item.chkbox = '<input type="checkbox" data-id="domain_admins" name="multi_select" value="' + item.username + '" />';
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="#" id="delete_selected" data-id="single-domain-admin" data-api-url="delete/domain-admin" data-item="' + encodeURI(item.username) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'</div>';
});
} }
}), }),
"empty": lang.empty, "empty": lang.empty,
"paging": { "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
"enabled": true, "filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table
"limit": 5,
"size": log_pagination_size
}, },
"filtering": { "sorting": {"enabled": true}
"enabled": true,
"position": "left",
"connectors": false,
"placeholder": lang.filter_table
},
"sorting": {
"enabled": true
}
}); });
} }
function draw_fwd_hosts() { function draw_fwd_hosts() {
@ -450,33 +243,16 @@ jQuery(function($){
console.log('Cannot draw forwarding hosts table'); console.log('Cannot draw forwarding hosts table');
}, },
success: function (data) { success: function (data) {
$.each(data, function (i, item) { return process_table_data(data, 'forwardinghoststable');
item.action = '<div class="btn-group">' +
'<a href="#" id="delete_selected" data-id="single-fwdhost" data-api-url="delete/fwdhost" data-item="' + encodeURI(item.host) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'</div>';
if (item.keep_spam == "yes") {
item.keep_spam = lang.no;
}
else {
item.keep_spam = lang.yes;
}
item.chkbox = '<input type="checkbox" data-id="fwdhosts" name="multi_select" value="' + item.host + '" />';
});
} }
}), }),
"empty": lang.empty, "empty": lang.empty,
"paging": { "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
"enabled": true, "sorting": {"enabled": true}
"limit": 5,
"size": log_pagination_size
},
"sorting": {
"enabled": true
}
}); });
} }
function draw_relayhosts() { function draw_relayhosts() {
ft_forwardinghoststable = FooTable.init('#relayhoststable', { ft_relayhoststable = FooTable.init('#relayhoststable', {
"columns": [ "columns": [
{"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"},
{"name":"id","type":"text","title":"ID","style":{"width":"50px"}}, {"name":"id","type":"text","title":"ID","style":{"width":"50px"}},
@ -494,115 +270,30 @@ jQuery(function($){
console.log('Cannot draw forwarding hosts table'); console.log('Cannot draw forwarding hosts table');
}, },
success: function (data) { success: function (data) {
$.each(data, function (i, item) { return process_table_data(data, 'relayhoststable');
item.action = '<div class="btn-group">' +
'<a href="#" data-toggle="modal" id="miau" data-target="#testRelayhostModal" data-relayhost-id="' + encodeURI(item.id) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-stats"></span> Test</a>' +
'<a href="/edit.php?relayhost=' + encodeURI(item.id) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
'<a href="#" id="delete_selected" data-id="single-rlshost" data-api-url="delete/relayhost" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'</div>';
item.chkbox = '<input type="checkbox" data-id="rlyhosts" name="multi_select" value="' + item.id + '" />';
});
} }
}), }),
"empty": lang.empty, "empty": lang.empty,
"paging": { "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
"enabled": true, "sorting": {"enabled": true}
"limit": 5,
"size": log_pagination_size
},
"sorting": {
"enabled": true
}
}); });
} }
function draw_rspamd_history() { function draw_rspamd_history() {
ft_postfix_logs = FooTable.init('#rspamd_history', { ft_rspamd_history = FooTable.init('#rspamd_history', {
"columns": [{ "columns": [
"name":"unix_time", {"name":"unix_time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
"formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();}, {"name": "ip","title": "IP address","breakpoints": "all","style": {"minWidth": 88}},
"title":lang.time, {"name": "sender_mime","title": "From","breakpoints": "xs sm md","style": {"minWidth": 100}},
"style":{ {"name": "rcpt_mime","title": "To","breakpoints": "xs sm md","style": {"minWidth": 100}},
"width":"170px" {"name": "subject","title": "Subject","breakpoints": "all","style": {"word-break": "break-all","minWidth": 150}},
} {"name": "action","title": "Action","style": {"minwidth": 82}},
}, { {"name": "score","title": "Score","style": {"maxWidth": 110},},
"name": "ip", {"name": "symbols","title": "Symbols","breakpoints": "all",},
"title": "IP address", {"name": "size","title": "Msg size","breakpoints": "all","style": {"minwidth": 50},"formatter": function(value){return humanFileSize(value);}},
"breakpoints": "all", {"name": "scan_time","title": "Scan time","breakpoints": "all","style": {"maxWidth": 72},},
"style": { {"name": "message-id","title": "ID","breakpoints": "all","style": {"minWidth": 130,"overflow": "hidden","textOverflow": "ellipsis","wordBreak": "break-all","whiteSpace": "normal"}},
"minWidth": 88 {"name": "user","title": "Authenticated user","breakpoints": "xs sm md","style": {"minWidth": 100}}
} ],
}, {
"name": "sender_mime",
"title": "From",
"breakpoints": "xs sm md",
"style": {
"minWidth": 100
}
}, {
"name": "rcpt_mime",
"title": "To",
"breakpoints": "xs sm md",
"style": {
"minWidth": 100
}
}, {
"name": "subject",
"title": "Subject",
"breakpoints": "all",
"style": {
"word-break": "break-all",
"minWidth": 150
}
}, {
"name": "action",
"title": "Action",
"style": {
"minwidth": 82
}
}, {
"name": "score",
"title": "Score",
"style": {
"maxWidth": 110
},
}, {
"name": "symbols",
"title": "Symbols",
"breakpoints": "all",
}, {
"name": "size",
"title": "Msg size",
"breakpoints": "all",
"style": {
"minwidth": 50,
},
"formatter": function(value) { return humanFileSize(value); }
}, {
"name": "scan_time",
"title": "Scan time",
"breakpoints": "all",
"style": {
"maxWidth": 72
},
}, {
"name": "message-id",
"title": "ID",
"breakpoints": "all",
"style": {
"minWidth": 130,
"overflow": "hidden",
"textOverflow": "ellipsis",
"wordBreak": "break-all",
"whiteSpace": "normal"
}
}, {
"name": "user",
"title": "Authenticated user",
"breakpoints": "xs sm md",
"style": {
"minWidth": 100
}
}],
"rows": $.ajax({ "rows": $.ajax({
dataType: 'json', dataType: 'json',
url: '/api/v1/get/logs/rspamd-history', url: '/api/v1/get/logs/rspamd-history',
@ -611,81 +302,146 @@ jQuery(function($){
console.log('Cannot draw rspamd history table'); console.log('Cannot draw rspamd history table');
}, },
success: function (data) { success: function (data) {
$.each(data, function (i, item) { return process_table_data(data, 'rspamd_history');
item.rcpt_mime = item.rcpt_mime.join(",&#8203;");
Object.keys(item.symbols).map(function(key) {
var sym = item.symbols[key];
if (sym.score <= 0) {
sym.score_formatted = '(<span class="text-success"><b>' + sym.score + '</b></span>)'
}
else {
sym.score_formatted = '(<span class="text-danger"><b>' + sym.score + '</b></span>)'
}
var str = '<strong>' + key + '</strong> ' + sym.score_formatted;
if (sym.options) {
str += ' [' + sym.options.join(",") + "]";
}
item.symbols[key].str = str;
});
item.symbols = Object.keys(item.symbols).
map(function(key) {
return item.symbols[key];
}).sort(function(e1, e2) {
return Math.abs(e1.score) < Math.abs(e2.score);
}).map(function(e) {
return e.str;
}).join("<br>\n");
var scan_time = item.time_real.toFixed(3) + ' / ' + item.time_virtual.toFixed(3);
item.scan_time = {
"options": {
"sortValue": item.time_real
},
"value": scan_time
};
if (item.action === 'clean' || item.action === 'no action') {
item.action = "<div class='label label-success'>" + item.action + "</div>";
} else if (item.action === 'rewrite subject' || item.action === 'add header' || item.action === 'probable spam') {
item.action = "<div class='label label-warning'>" + item.action + "</div>";
} else if (item.action === 'spam' || item.action === 'reject') {
item.action = "<div class='label label-danger'>" + item.action + "</div>";
} else {
item.action = "<div class='label label-info'>" + item.action + "</div>";
}
var score_content;
if (item.score < item.required_score) {
score_content = "[ <span class='text-success'>" + item.score.toFixed(2) + " / " + item.required_score + "</span> ]";
} else {
score_content = "[ <span class='text-danger'>" + item.score.toFixed(2) + " / " + item.required_score + "</span> ]";
}
item.score = {
"options": {
"sortValue": item.score
},
"value": score_content
};
if (item.user == null) {
item.user = "none";
}
});
} }
}), }),
"empty": lang.empty, "empty": lang.empty,
"paging": { "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
"enabled": true, "filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table},
"limit": 5, "sorting": {"enabled": true},
"size": log_pagination_size "on": {
}, "ready.ft.table": function(e, ft){
"filtering": { heading = ft.$el.parents('.tab-pane').find('.panel-heading')
"enabled": true, $(heading).children('.log-lines').text(function(){
"position": "left", var ft_paging = ft.use(FooTable.Paging)
"connectors": false, return ft_paging.totalRows;
"placeholder": lang.filter_table })
}, }
"sorting": {
"enabled": true
} }
}); });
} }
function process_table_data(data, table) {
if (table == 'rspamd_history') {
$.each(data, function (i, item) {
item.rcpt_mime = item.rcpt_mime.join(",&#8203;");
Object.keys(item.symbols).map(function(key) {
var sym = item.symbols[key];
if (sym.score <= 0) {
sym.score_formatted = '(<span class="text-success"><b>' + sym.score + '</b></span>)'
}
else {
sym.score_formatted = '(<span class="text-danger"><b>' + sym.score + '</b></span>)'
}
var str = '<strong>' + key + '</strong> ' + sym.score_formatted;
if (sym.options) {
str += ' [' + sym.options.join(",") + "]";
}
item.symbols[key].str = str;
});
item.symbols = Object.keys(item.symbols).
map(function(key) {
return item.symbols[key];
}).sort(function(e1, e2) {
return Math.abs(e1.score) < Math.abs(e2.score);
}).map(function(e) {
return e.str;
}).join("<br>\n");
var scan_time = item.time_real.toFixed(3) + ' / ' + item.time_virtual.toFixed(3);
item.scan_time = {
"options": {
"sortValue": item.time_real
},
"value": scan_time
};
if (item.action === 'clean' || item.action === 'no action') {
item.action = "<div class='label label-success'>" + item.action + "</div>";
} else if (item.action === 'rewrite subject' || item.action === 'add header' || item.action === 'probable spam') {
item.action = "<div class='label label-warning'>" + item.action + "</div>";
} else if (item.action === 'spam' || item.action === 'reject') {
item.action = "<div class='label label-danger'>" + item.action + "</div>";
} else {
item.action = "<div class='label label-info'>" + item.action + "</div>";
}
var score_content;
if (item.score < item.required_score) {
score_content = "[ <span class='text-success'>" + item.score.toFixed(2) + " / " + item.required_score + "</span> ]";
} else {
score_content = "[ <span class='text-danger'>" + item.score.toFixed(2) + " / " + item.required_score + "</span> ]";
}
item.score = {
"options": {
"sortValue": item.score
},
"value": score_content
};
if (item.user == null) {
item.user = "none";
}
});
} else if (table == 'relayhoststable') {
$.each(data, function (i, item) {
item.action = '<div class="btn-group">' +
'<a href="#" data-toggle="modal" id="miau" data-target="#testRelayhostModal" data-relayhost-id="' + encodeURI(item.id) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-stats"></span> Test</a>' +
'<a href="/edit.php?relayhost=' + encodeURI(item.id) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
'<a href="#" id="delete_selected" data-id="single-rlshost" data-api-url="delete/relayhost" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'</div>';
item.chkbox = '<input type="checkbox" data-id="rlyhosts" name="multi_select" value="' + item.id + '" />';
});
} else if (table == 'forwardinghoststable') {
$.each(data, function (i, item) {
item.action = '<div class="btn-group">' +
'<a href="#" id="delete_selected" data-id="single-fwdhost" data-api-url="delete/fwdhost" data-item="' + encodeURI(item.host) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'</div>';
if (item.keep_spam == "yes") {
item.keep_spam = lang.no;
}
else {
item.keep_spam = lang.yes;
}
item.chkbox = '<input type="checkbox" data-id="fwdhosts" name="multi_select" value="' + item.host + '" />';
});
} else if (table == 'domainadminstable') {
$.each(data, function (i, item) {
item.chkbox = '<input type="checkbox" data-id="domain_admins" name="multi_select" value="' + item.username + '" />';
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="#" id="delete_selected" data-id="single-domain-admin" data-api-url="delete/domain-admin" data-item="' + encodeURI(item.username) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'</div>';
});
} else if (table == 'autodiscover_log') {
$.each(data, function (i, item) {
item.ua = '<span style="font-size:small">' + item.ua + '</span>';
if (item.service == "activesync") {
item.service = '<span class="label label-info">ActiveSync</span>';
}
else if (item.service == "imap") {
item.service = '<span class="label label-success">IMAP, SMTP, Cal-/CardDAV</span>';
}
else {
item.service = '<span class="label label-danger">' + item.service + '</span>';
}
});
} else if (table == 'general_syslog') {
$.each(data, function (i, item) {
item.message = escapeHtml(item.message);
var danger_class = ["emerg", "alert", "crit", "err"];
var warning_class = ["warning", "warn"];
var info_class = ["notice", "info", "debug"];
if (jQuery.inArray(item.priority, danger_class) !== -1) {
item.priority = '<span class="label label-danger">' + item.priority + '</span>';
}
else if (jQuery.inArray(item.priority, warning_class) !== -1) {
item.priority = '<span class="label label-warning">' + item.priority + '</span>';
}
else if (jQuery.inArray(item.priority, info_class) !== -1) {
item.priority = '<span class="label label-info">' + item.priority + '</span>';
}
});
}
return data
};
// Initial table drawings
draw_postfix_logs(); draw_postfix_logs();
draw_autodiscover_logs(); draw_autodiscover_logs();
draw_dovecot_logs(); draw_dovecot_logs();
@ -695,7 +451,7 @@ jQuery(function($){
draw_fwd_hosts(); draw_fwd_hosts();
draw_relayhosts(); draw_relayhosts();
draw_rspamd_history(); draw_rspamd_history();
// Relayhost
$('#testRelayhostModal').on('show.bs.modal', function (e) { $('#testRelayhostModal').on('show.bs.modal', function (e) {
$('#test_relayhost_result').text("-"); $('#test_relayhost_result').text("-");
button = $(e.relatedTarget) button = $(e.relatedTarget)
@ -703,16 +459,6 @@ jQuery(function($){
$('#relayhost_id').val(button.data('relayhost-id')); $('#relayhost_id').val(button.data('relayhost-id'));
} }
}) })
$('#showDKIMprivKey').on('show.bs.modal', function (e) {
$('#priv_key_pre').text("-");
p_related = $(e.relatedTarget)
if (p_related != null) {
var decoded_key = Base64.decode((p_related.data('priv-key')));
$('#priv_key_pre').text(decoded_key);
}
})
$('#test_relayhost').on('click', function (e) { $('#test_relayhost').on('click', function (e) {
e.preventDefault(); e.preventDefault();
prev = $('#test_relayhost').text(); prev = $('#test_relayhost').text();
@ -720,7 +466,7 @@ jQuery(function($){
$(this).html('<span class="glyphicon glyphicon-refresh glyphicon-spin"></span> '); $(this).html('<span class="glyphicon glyphicon-refresh glyphicon-spin"></span> ');
$.ajax({ $.ajax({
type: 'GET', type: 'GET',
url: 'inc/relay_check.php', url: 'inc/ajax/relay_check.php',
dataType: 'text', dataType: 'text',
data: $('#test_relayhost_form').serialize(), data: $('#test_relayhost_form').serialize(),
complete: function (data) { complete: function (data) {
@ -730,7 +476,41 @@ jQuery(function($){
} }
}); });
}) })
// DKIM private key modal
$('#showDKIMprivKey').on('show.bs.modal', function (e) {
$('#priv_key_pre').text("-");
p_related = $(e.relatedTarget)
if (p_related != null) {
var decoded_key = Base64.decode((p_related.data('priv-key')));
$('#priv_key_pre').text(decoded_key);
}
})
$('.add_log_lines').on('click', function (e) {
e.preventDefault();
var log_table= $(this).data("table")
var new_nrows = ($(this).data("nrows") - 1)
var post_process = $(this).data("post-process")
var log_url = $(this).data("log-url")
if (log_table === undefined || new_nrows === undefined || post_process === undefined || log_url === undefined) {
console.log("no data-table or data-nrows or log_url or data-post-process attr found");
return;
}
if (ft = FooTable.get($('#' + log_table))) {
var heading = ft.$el.parents('.tab-pane').find('.panel-heading')
var ft_paging = ft.use(FooTable.Paging)
var load_rows = ft_paging.totalRows + '-' + (ft_paging.totalRows + new_nrows)
$.get('/api/v1/get/logs/' + log_url + '/' + load_rows).then(function(data){
if (data.length === undefined) { mailcow_alert_box(lang.no_new_rows, "info"); return; }
var rows = process_table_data(data, post_process);
var rows_now = (ft_paging.totalRows + data.length);
$(heading).children('.log-lines').text(rows_now)
mailcow_alert_box(data.length + lang.additional_rows, "success");
ft.rows.load(rows, true);
});
}
})
// App links
function add_table_row(table_id) { function add_table_row(table_id) {
var row = $('<tr />'); var row = $('<tr />');
cols = '<td><input class="input-sm form-control" data-id="app_links" type="text" name="app" required></td>'; cols = '<td><input class="input-sm form-control" data-id="app_links" type="text" name="app" required></td>';
@ -739,17 +519,14 @@ jQuery(function($){
row.append(cols); row.append(cols);
table_id.append(row); table_id.append(row);
} }
$('#app_link_table').on('click', 'tr a', function (e) { $('#app_link_table').on('click', 'tr a', function (e) {
e.preventDefault(); e.preventDefault();
$(this).parents('tr').remove(); $(this).parents('tr').remove();
}); });
$('#add_app_link_row').click(function() { $('#add_app_link_row').click(function() {
add_table_row($('#app_link_table')); add_table_row($('#app_link_table'));
}); });
}); });
$(window).load(function(){ $(window).load(function(){
initial_width = $("#sidebar-admin").width(); initial_width = $("#sidebar-admin").width();
$("#scrollbox").css("width", initial_width); $("#scrollbox").css("width", initial_width);
@ -761,13 +538,10 @@ $(window).load(function(){
} }
}); });
}); });
function resizeScrollbox() { function resizeScrollbox() {
on_resize_width = $("#sidebar-admin").width(); on_resize_width = $("#sidebar-admin").width();
$("#scrollbox").removeAttr("style"); $("#scrollbox").removeAttr("style");
$("#scrollbox").css("width", on_resize_width); $("#scrollbox").css("width", on_resize_width);
} }
$(window).on('resize', resizeScrollbox); $(window).on('resize', resizeScrollbox);
$('a[data-toggle="tab"]').on('shown.bs.tab', resizeScrollbox); $('a[data-toggle="tab"]').on('shown.bs.tab', resizeScrollbox);

View File

@ -61,6 +61,11 @@ $(document).ready(function() {
var id = $(this).data('id'); var id = $(this).data('id');
var api_url = $(this).data('api-url'); var api_url = $(this).data('api-url');
var api_attr = $(this).data('api-attr'); var api_attr = $(this).data('api-attr');
if (typeof $(this).data('api-reload-window') !== 'undefined') {
api_reload_window = $(this).data('api-reload-window');
} else {
api_reload_window = true;
}
// If clicked element #edit_selected is in a form with the same data-id as the button, // If clicked element #edit_selected is in a form with the same data-id as the button,
// we merge all input fields by {"name":"value"} into api-attr // we merge all input fields by {"name":"value"} into api-attr
if ($(this).closest("form").data('id') == id) { if ($(this).closest("form").data('id') == id) {
@ -74,6 +79,21 @@ $(document).ready(function() {
$(this).removeClass('inputMissingAttr'); $(this).removeClass('inputMissingAttr');
} }
} }
if ($(this).attr("max")) {
if ($(this).val() > $(this).attr("max")) {
invalid = true;
$(this).addClass('inputMissingAttr');
} else {
if ($(this).attr("min")) {
if ($(this).val() < $(this).attr("min")) {
invalid = true;
$(this).addClass('inputMissingAttr');
} else {
$(this).removeClass('inputMissingAttr');
}
}
}
}
}); });
if (!req_empty) { if (!req_empty) {
var attr_to_merge = $(this).closest("form").serializeObject(); var attr_to_merge = $(this).closest("form").serializeObject();
@ -106,10 +126,10 @@ $(document).ready(function() {
jsonp: false, jsonp: false,
complete: function(data) { complete: function(data) {
var response = (data.responseText); var response = (data.responseText);
// alert(response); response_obj = JSON.parse(response);
// console.log(reponse.type); if (api_reload_window === true) {
// console.log(reponse.msg); window.location = window.location.href.split("#")[0];
window.location = window.location.href.split("#")[0]; }
} }
}); });
} }
@ -124,18 +144,33 @@ $(document).ready(function() {
// If clicked button is in a form with the same data-id as the button, // If clicked button is in a form with the same data-id as the button,
// we merge all input fields by {"name":"value"} into api-attr // we merge all input fields by {"name":"value"} into api-attr
if ($(this).closest("form").data('id') == id) { if ($(this).closest("form").data('id') == id) {
var req_empty = false; var invalid = false;
$(this).closest("form").find('select, textarea, input').each(function() { $(this).closest("form").find('select, textarea, input').each(function() {
if ($(this).prop('required')) { if ($(this).prop('required')) {
if (!$(this).val() && $(this).prop('disabled') === false) { if (!$(this).val() && $(this).prop('disabled') === false) {
req_empty = true; invalid = true;
$(this).addClass('inputMissingAttr'); $(this).addClass('inputMissingAttr');
} else { } else {
$(this).removeClass('inputMissingAttr'); $(this).removeClass('inputMissingAttr');
} }
} }
if ($(this).attr("max")) {
if ($(this).val() > $(this).attr("max")) {
invalid = true;
$(this).addClass('inputMissingAttr');
} else {
if ($(this).attr("min")) {
if ($(this).val() < $(this).attr("min")) {
invalid = true;
$(this).addClass('inputMissingAttr');
} else {
$(this).removeClass('inputMissingAttr');
}
}
}
}
}); });
if (!req_empty) { if (!invalid) {
var attr_to_merge = $(this).closest("form").serializeObject(); var attr_to_merge = $(this).closest("form").serializeObject();
var api_attr = $.extend(api_attr, attr_to_merge) var api_attr = $.extend(api_attr, attr_to_merge)
} else { } else {

View File

@ -10,6 +10,7 @@ $(document).ready(function() {
$("#textarea_alias_goto").removeAttr('disabled'); $("#textarea_alias_goto").removeAttr('disabled');
} }
}); });
$("#script_data").numberedtextarea({allowTabChar: true});
}); });
jQuery(function($){ jQuery(function($){

View File

@ -40,10 +40,61 @@ $(document).ready(function() {
}); });
// Log modal // Log modal
$('#logModal').on('show.bs.modal', function(e) { $('#syncjobLogModal').on('show.bs.modal', function(e) {
var logText = $(e.relatedTarget).data('log-text'); var syncjob_id = $(e.relatedTarget).data('syncjob-id');
$(e.currentTarget).find('#logText').html('<pre style="background:none;font-size:11px;line-height:1.1;border:0px">' + logText + '</pre>'); $.ajax({
url: '/inc/ajax/syncjob_logs.php',
data: { id: syncjob_id },
dataType: 'text',
success: function(data){
$(e.currentTarget).find('#logText').text(data);
},
error: function(xhr, status, error) {
$(e.currentTarget).find('#logText').text(xhr.responseText);
}
});
}); });
// Sieve data modal
$('#sieveDataModal').on('show.bs.modal', function(e) {
var sieveScript = $(e.relatedTarget).data('sieve-script');
$(e.currentTarget).find('#sieveDataText').html('<pre style="font-size:14px;line-height:1.1">' + sieveScript + '</pre>');
});
// Set line numbers for textarea
$("#script_data").numberedtextarea({allowTabChar: true});
// Disable submit button on script change
$('#script_data').on('keyup', function() {
$('#add_filter_btns > #add_item').attr({"disabled": true});
$('#validation_msg').html('-');
});
// Validate script data
$("#validate_sieve").click(function( event ) {
event.preventDefault();
var script = $('#script_data').val();
$.ajax({
dataType: 'jsonp',
url: "/inc/ajax/sieve_validation.php",
type: "get",
data: { script: script },
complete: function(data) {
var response = (data.responseText);
response_obj = JSON.parse(response);
if (response_obj.type == "success") {
$('#add_filter_btns > #add_item').attr({"disabled": false});
}
mailcow_alert_box(response_obj.msg, response_obj.type);
},
});
});
// $(document).on('DOMNodeInserted', '#prefilter_table', function () {
// $("#active-script").closest('td').css('background-color','#b0f0a0');
// $("#inactive-script").closest('td').css('background-color','#b0f0a0');
// });
}); });
jQuery(function($){ jQuery(function($){
// http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery
@ -87,7 +138,7 @@ jQuery(function($){
function draw_domain_table() { function draw_domain_table() {
ft_domain_table = FooTable.init('#domain_table', { ft_domain_table = FooTable.init('#domain_table', {
"columns": [ "columns": [
{"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"},
{"sorted": true,"name":"domain_name","title":lang.domain,"style":{"width":"250px"}}, {"sorted": true,"name":"domain_name","title":lang.domain,"style":{"width":"250px"}},
{"name":"aliases","title":lang.aliases,"breakpoints":"xs sm"}, {"name":"aliases","title":lang.aliases,"breakpoints":"xs sm"},
{"name":"mailboxes","title":lang.mailboxes}, {"name":"mailboxes","title":lang.mailboxes},
@ -152,7 +203,7 @@ jQuery(function($){
function draw_mailbox_table() { function draw_mailbox_table() {
ft_mailbox_table = FooTable.init('#mailbox_table', { ft_mailbox_table = FooTable.init('#mailbox_table', {
"columns": [ "columns": [
{"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"},
{"sorted": true,"name":"username","style":{"word-break":"break-all","min-width":"120px"},"title":lang.username}, {"sorted": true,"name":"username","style":{"word-break":"break-all","min-width":"120px"},"title":lang.username},
{"name":"name","title":lang.fname,"style":{"word-break":"break-all","min-width":"120px"},"breakpoints":"xs sm"}, {"name":"name","title":lang.fname,"style":{"word-break":"break-all","min-width":"120px"},"breakpoints":"xs sm"},
{"name":"domain","title":lang.domain,"breakpoints":"xs sm"}, {"name":"domain","title":lang.domain,"breakpoints":"xs sm"},
@ -222,7 +273,7 @@ jQuery(function($){
function draw_resource_table() { function draw_resource_table() {
ft_resource_table = FooTable.init('#resource_table', { ft_resource_table = FooTable.init('#resource_table', {
"columns": [ "columns": [
{"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"},
{"sorted": true,"name":"description","title":lang.description,"style":{"width":"250px"}}, {"sorted": true,"name":"description","title":lang.description,"style":{"width":"250px"}},
{"name":"kind","title":lang.kind}, {"name":"kind","title":lang.kind},
{"name":"domain","title":lang.domain,"breakpoints":"xs sm"}, {"name":"domain","title":lang.domain,"breakpoints":"xs sm"},
@ -267,7 +318,7 @@ jQuery(function($){
function draw_alias_table() { function draw_alias_table() {
ft_alias_table = FooTable.init('#alias_table', { ft_alias_table = FooTable.init('#alias_table', {
"columns": [ "columns": [
{"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"},
{"sorted": true,"name":"address","title":lang.alias,"style":{"width":"250px"}}, {"sorted": true,"name":"address","title":lang.alias,"style":{"width":"250px"}},
{"name":"goto","title":lang.target_address}, {"name":"goto","title":lang.target_address},
{"name":"domain","title":lang.domain,"breakpoints":"xs sm"}, {"name":"domain","title":lang.domain,"breakpoints":"xs sm"},
@ -320,7 +371,7 @@ jQuery(function($){
function draw_aliasdomain_table() { function draw_aliasdomain_table() {
ft_aliasdomain_table = FooTable.init('#aliasdomain_table', { ft_aliasdomain_table = FooTable.init('#aliasdomain_table', {
"columns": [ "columns": [
{"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"},
{"sorted": true,"name":"alias_domain","title":lang.alias,"style":{"width":"250px"}}, {"sorted": true,"name":"alias_domain","title":lang.alias,"style":{"width":"250px"}},
{"name":"target_domain","title":lang.target_domain}, {"name":"target_domain","title":lang.target_domain},
{"name":"active","filterable": false,"style":{"maxWidth":"50px","width":"70px"},"title":lang.active}, {"name":"active","filterable": false,"style":{"maxWidth":"50px","width":"70px"},"title":lang.active},
@ -363,27 +414,28 @@ jQuery(function($){
function draw_sync_job_table() { function draw_sync_job_table() {
ft_syncjob_table = FooTable.init('#sync_job_table', { ft_syncjob_table = FooTable.init('#sync_job_table', {
"columns": [ "columns": [
{"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"}, {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"},
{"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}}, {"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}},
{"name":"user2","title":lang.owner}, {"name":"user2","title":lang.owner},
{"name":"server_w_port","title":"Server","breakpoints":"xs"}, {"name":"server_w_port","title":"Server","breakpoints":"xs"},
{"name":"mins_interval","title":lang.mins_interval,"breakpoints":"all"}, {"name":"mins_interval","title":lang.mins_interval,"breakpoints":"all"},
{"name":"last_run","title":lang.last_run,"breakpoints":"all"}, {"name":"last_run","title":lang.last_run,"breakpoints":"all"},
{"name":"log","title":"Log"}, {"name":"log","title":"Log"},
{"name":"active","filterable": false,"style":{"maxWidth":"50px","width":"70px"},"title":lang.active}, {"name":"active","filterable": false,"style":{"maxWidth":"70px","width":"70px"},"title":lang.active},
{"name":"is_running","filterable": false,"style":{"maxWidth":"120px","width":"100px"},"title":lang.status},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
], ],
"empty": lang.empty, "empty": lang.empty,
"rows": $.ajax({ "rows": $.ajax({
dataType: 'json', dataType: 'json',
url: '/api/v1/get/syncjobs/all', url: '/api/v1/get/syncjobs/all/no_log',
jsonp: false, jsonp: false,
error: function () { error: function () {
console.log('Cannot draw sync job table'); console.log('Cannot draw sync job table');
}, },
success: function (data) { success: function (data) {
$.each(data, function (i, item) { $.each(data, function (i, item) {
item.log = '<a href="#logModal" data-toggle="modal" data-log-text="' + escapeHtml(item.returned_text) + '">Open logs</a>' item.log = '<a href="#syncjobLogModal" data-toggle="modal" data-syncjob-id="' + encodeURI(item.id) + '">Open logs</a>'
item.exclude = '<code>' + item.exclude + '</code>' item.exclude = '<code>' + item.exclude + '</code>'
item.server_w_port = item.user1 + '@' + item.host1 + ':' + item.port1; item.server_w_port = item.user1 + '@' + item.host1 + ':' + item.port1;
item.action = '<div class="btn-group">' + item.action = '<div class="btn-group">' +
@ -391,6 +443,14 @@ jQuery(function($){
'<a href="#" id="delete_selected" data-id="single-syncjob" data-api-url="delete/syncjob" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + '<a href="#" id="delete_selected" data-id="single-syncjob" data-api-url="delete/syncjob" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'</div>'; '</div>';
item.chkbox = '<input type="checkbox" data-id="syncjob" name="multi_select" value="' + item.id + '" />'; item.chkbox = '<input type="checkbox" data-id="syncjob" name="multi_select" value="' + item.id + '" />';
if (item.is_running == 1) {
item.is_running = '<span id="active-script" class="label label-success">' + lang.running + '</span>';
} else {
item.is_running = '<span id="inactive-script" class="label label-warning">' + lang.waiting + '</span>';
}
if (!item.last_run > 0) {
item.last_run = lang.waiting;
}
}); });
} }
}), }),
@ -399,16 +459,76 @@ jQuery(function($){
"limit": 5, "limit": 5,
"size": pagination_size "size": pagination_size
}, },
"filtering": {
"enabled": true,
"position": "left",
"placeholder": lang.filter_table
},
"sorting": { "sorting": {
"enabled": true "enabled": true
} }
}); });
} }
function draw_filter_table() {
ft_filter_table = FooTable.init('#filter_table', {
"columns": [
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"},
{"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}},
{"name":"active","style":{"maxWidth":"80px","width":"80px"},"title":lang.active},
{"name":"filter_type","style":{"maxWidth":"80px","width":"80px"},"title":"Type"},
{"sorted": true,"name":"username","title":lang.owner,"style":{"maxWidth":"550px","width":"350px"}},
{"name":"script_desc","title":lang.description,"breakpoints":"xs"},
{"name":"script_data","title":"Script","breakpoints":"all"},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
],
"empty": lang.empty,
"rows": $.ajax({
dataType: 'json',
url: '/api/v1/get/filters/all',
jsonp: false,
error: function () {
console.log('Cannot draw filter table');
},
success: function (data) {
$.each(data, function (i, item) {
if (item.active_int == 1) {
item.active = '<span id="active-script" class="label label-success">' + lang.active + '</span>';
} else {
item.active = '<span id="inactive-script" class="label label-warning">' + lang.inactive + '</span>';
}
item.script_data = '<pre style="margin:0px">' + escapeHtml(item.script_data) + '</pre>'
item.filter_type = '<div class="label label-default">' + item.filter_type.charAt(0).toUpperCase() + item.filter_type.slice(1).toLowerCase() + '</div>'
item.action = '<div class="btn-group">' +
'<a href="/edit.php?filter=' + item.id + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
'<a href="#" id="delete_selected" data-id="single-filter" data-api-url="delete/filter" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'</div>';
item.chkbox = '<input type="checkbox" data-id="filter_item" name="multi_select" value="' + item.id + '" />'
});
}
}),
"paging": {
"enabled": true,
"limit": 5,
"size": pagination_size
},
"filtering": {
"enabled": true,
"position": "left",
"placeholder": lang.filter_table
},
"sorting": {
"enabled": true
}
});
};
draw_domain_table(); draw_domain_table();
draw_mailbox_table(); draw_mailbox_table();
draw_resource_table(); draw_resource_table();
draw_alias_table(); draw_alias_table();
draw_aliasdomain_table(); draw_aliasdomain_table();
draw_sync_job_table(); draw_sync_job_table();
}); draw_filter_table();
});

View File

@ -0,0 +1 @@
!function(e){function t(t,n){var a=e('<div class="numberedtextarea-wrapper"></div>').insertAfter(t);e(t).detach().appendTo(a)}function n(t,n){(t=e(t)).parents(".numberedtextarea-wrapper");var i=parseFloat(t.css("padding-left")),o=parseFloat(t.css("padding-top")),s=(parseFloat(t.css("padding-bottom")),e('<div class="numberedtextarea-line-numbers"></div>').insertAfter(t));t.css({paddingLeft:i+s.width()+"px"}).on("input propertychange change keyup paste",function(){a(t,n)}).on("scroll",function(){r(t,n)}),s.css({paddingLeft:i+"px",paddingTop:o+"px",lineHeight:t.css("line-height"),fontFamily:t.css("font-family"),width:s.width()-i+"px"}),t.trigger("change")}function a(t,n){var a=(t=e(t)).parent().find(".numberedtextarea-line-numbers"),r=t.val().split("\n").length,o=parseFloat(t.css("padding-bottom"));for(a.find(".numberedtextarea-number").remove(),i=1;i<=r;i++){var s=e('<div class="numberedtextarea-number numberedtextarea-number-'+i+'">'+i+"</div>").appendTo(a);i===r&&s.css("margin-bottom",o+"px")}}function r(t,n){(t=e(t)).parent().find(".numberedtextarea-line-numbers").scrollTop(t.scrollTop())}function o(e,t){if(e.focus(),"number"==typeof e.selectionStart){var n=e.value,a=e.selectionStart;e.value=n.slice(0,a)+t+n.slice(e.selectionEnd),e.selectionEnd=e.selectionStart=a+t.length}else if(void 0!==document.selection){var i=document.selection.createRange();i.text=t,i.collapse(!1),i.select()}}function s(t){e(t).keydown(function(e){if(9==e.which)return o(this,"\t"),!1}),e(t).keypress(function(e){if(9==e.which)return!1})}e.fn.numberedtextarea=function(a){var i=e.extend({color:null,borderColor:null,class:null,allowTabChar:!1},a);return this.each(function(){if("textarea"!==this.nodeName.toLowerCase())return console.log("This is not a <textarea>, so no way Jose..."),!1;t(this,i),n(this,i),i.allowTabChar&&e(this).allowTabChar()}),this},e.fn.allowTabChar=function(){return this.jquery&&this.each(function(){if(1==this.nodeType){var e=this.nodeName.toLowerCase();("textarea"==e||"input"==e&&"text"==this.type)&&s(this)}}),this}}(jQuery);

View File

@ -1,13 +1,19 @@
$(document).ready(function() { $(document).ready(function() {
$('#syncjobLogModal').on('show.bs.modal', function(e) {
// Log modal var syncjob_id = $(e.relatedTarget).data('syncjob-id');
$('#logModal').on('show.bs.modal', function(e) { $.ajax({
var logText = $(e.relatedTarget).data('log-text'); url: '/inc/ajax/syncjob_logs.php',
$(e.currentTarget).find('#logText').html('<pre style="background:none;font-size:11px;line-height:1.1;border:0px">' + logText + '</pre>'); data: { id: syncjob_id },
dataType: 'text',
success: function(data){
$(e.currentTarget).find('#logText').text(data);
},
error: function(xhr, status, error) {
$(e.currentTarget).find('#logText').text(xhr.responseText);
}
});
}); });
}); });
jQuery(function($){ jQuery(function($){
// http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery
var entityMap = { var entityMap = {
@ -83,27 +89,32 @@ jQuery(function($){
{"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"}, {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"},
{"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}}, {"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}},
{"name":"server_w_port","title":"Server"}, {"name":"server_w_port","title":"Server"},
{"name":"enc1","title":lang.encryption}, {"name":"enc1","title":lang.encryption,"breakpoints":"xs sm"},
{"name":"user1","title":lang.username}, {"name":"user1","title":lang.username},
{"name":"exclude","title":lang.excludes}, {"name":"exclude","title":lang.excludes,"breakpoints":"xs sm"},
{"name":"mins_interval","title":lang.interval + " (min)"}, {"name":"mins_interval","title":lang.interval + " (min)"},
{"name":"last_run","title":lang.last_run}, {"name":"last_run","title":lang.last_run,"breakpoints":"xs sm"},
{"name":"log","title":"Log"}, {"name":"log","title":"Log"},
{"name":"active","filterable": false,"style":{"maxWidth":"50px","width":"70px"},"title":lang.active}, {"name":"active","filterable": false,"style":{"maxWidth":"70px","width":"70px"},"title":lang.active},
{"name":"is_running","filterable": false,"style":{"maxWidth":"120px","width":"100px"},"title":lang.status},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
], ],
"empty": lang.empty, "empty": lang.empty,
"rows": $.ajax({ "rows": $.ajax({
dataType: 'json', dataType: 'json',
url: '/api/v1/get/syncjobs/' + mailcow_cc_username, url: '/api/v1/get/syncjobs/' + mailcow_cc_username + '/no_log',
jsonp: false, jsonp: false,
error: function () { error: function () {
console.log('Cannot draw sync job table'); console.log('Cannot draw sync job table');
}, },
success: function (data) { success: function (data) {
$.each(data, function (i, item) { $.each(data, function (i, item) {
item.log = '<a href="#logModal" data-toggle="modal" data-log-text="' + escapeHtml(item.returned_text) + '">Open logs</a>' item.log = '<a href="#syncjobLogModal" data-toggle="modal" data-syncjob-id="' + encodeURI(item.id) + '">Open logs</a>'
item.exclude = '<code>' + item.exclude + '</code>' if (!item.exclude > 0) {
item.exclude = '-';
} else {
item.exclude = '<code>' + item.exclude + '</code>';
}
item.server_w_port = item.user1 + '@' + item.host1 + ':' + item.port1; item.server_w_port = item.user1 + '@' + item.host1 + ':' + item.port1;
if (acl_data.syncjobs === 1) { if (acl_data.syncjobs === 1) {
item.action = '<div class="btn-group">' + item.action = '<div class="btn-group">' +
@ -116,6 +127,14 @@ jQuery(function($){
item.action = '<span>-</span>'; item.action = '<span>-</span>';
item.chkbox = '<input type="checkbox" disabled />'; item.chkbox = '<input type="checkbox" disabled />';
} }
if (item.is_running == 1) {
item.is_running = '<span id="active-script" class="label label-success">' + lang.running + '</span>';
} else {
item.is_running = '<span id="inactive-script" class="label label-warning">' + lang.waiting + '</span>';
}
if (!item.last_run > 0) {
item.last_run = lang.waiting;
}
}); });
} }
}), }),
@ -213,4 +232,27 @@ jQuery(function($){
draw_tla_table(); draw_tla_table();
draw_wl_policy_mailbox_table(); draw_wl_policy_mailbox_table();
draw_bl_policy_mailbox_table(); draw_bl_policy_mailbox_table();
// Sieve data modal
$('#userFilterModal').on('show.bs.modal', function(e) {
$('#user_sieve_filter').text(lang.loading);
$.ajax({
dataType: 'json',
url: '/api/v1/get/active-user-sieve/' + mailcow_cc_username,
jsonp: false,
error: function () {
console.log('Cannot get active sieve script');
},
complete: function (data) {
if (data.responseText == '{}') {
$('#user_sieve_filter').text(lang.no_active_filter);
} else {
$('#user_sieve_filter').text(JSON.parse(data.responseText));
}
}
})
});
$('#userFilterModal').on('hidden.bs.modal', function () {
$('#user_sieve_filter').text(lang.loading);
});
}); });

View File

@ -13,7 +13,7 @@ delete/alias => POST data:
*/ */
header('Content-Type: application/json'); header('Content-Type: application/json');
require_once 'inc/prerequisites.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
error_reporting(0); error_reporting(0);
if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_username'])) { if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_username'])) {
if (isset($_GET['query'])) { if (isset($_GET['query'])) {
@ -225,10 +225,10 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
)); ));
} }
break; break;
case "syncjob": case "filter":
if (isset($_POST['attr'])) { if (isset($_POST['attr'])) {
$attr = (array)json_decode($_POST['attr'], true); $attr = (array)json_decode($_POST['attr'], true);
if (mailbox('add', 'syncjob', $attr) === false) { if (mailbox('add', 'filter', $attr) === false) {
if (isset($_SESSION['return'])) { if (isset($_SESSION['return'])) {
echo json_encode($_SESSION['return']); echo json_encode($_SESSION['return']);
} }
@ -489,6 +489,39 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
)); ));
} }
break; break;
case "syncjob":
if (isset($_POST['attr'])) {
$attr = (array)json_decode($_POST['attr'], true);
if (mailbox('add', 'syncjob', $attr) === false) {
if (isset($_SESSION['return'])) {
echo json_encode($_SESSION['return']);
}
else {
echo json_encode(array(
'type' => 'error',
'msg' => 'Cannot add item'
));
}
}
else {
if (isset($_SESSION['return'])) {
echo json_encode($_SESSION['return']);
}
else {
echo json_encode(array(
'type' => 'success',
'msg' => 'Task completed'
));
}
}
}
else {
echo json_encode(array(
'type' => 'error',
'msg' => 'Cannot find attributes in post data'
));
}
break;
} }
break; break;
case "get": case "get":
@ -725,12 +758,13 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
case "logs": case "logs":
switch ($object) { switch ($object) {
case "dovecot": case "dovecot":
if (isset($extra) && !empty($extra)) { // 0 is first record, so empty is fine
$extra = intval($extra); if (isset($extra)) {
$extra = preg_replace('/[^\d\-]/i', '', $extra);
$logs = get_logs('dovecot-mailcow', $extra); $logs = get_logs('dovecot-mailcow', $extra);
} }
else { else {
$logs = get_logs('dovecot-mailcow', -1); $logs = get_logs('dovecot-mailcow');
} }
if (isset($logs) && !empty($logs)) { if (isset($logs) && !empty($logs)) {
echo json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); echo json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
@ -740,12 +774,13 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
} }
break; break;
case "fail2ban": case "fail2ban":
if (isset($extra) && !empty($extra)) { // 0 is first record, so empty is fine
$extra = intval($extra); if (isset($extra)) {
$extra = preg_replace('/[^\d\-]/i', '', $extra);
$logs = get_logs('fail2ban-mailcow', $extra); $logs = get_logs('fail2ban-mailcow', $extra);
} }
else { else {
$logs = get_logs('fail2ban-mailcow', -1); $logs = get_logs('fail2ban-mailcow');
} }
if (isset($logs) && !empty($logs)) { if (isset($logs) && !empty($logs)) {
echo json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); echo json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
@ -755,12 +790,13 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
} }
break; break;
case "postfix": case "postfix":
if (isset($extra) && !empty($extra)) { // 0 is first record, so empty is fine
$extra = intval($extra); if (isset($extra)) {
$extra = preg_replace('/[^\d\-]/i', '', $extra);
$logs = get_logs('postfix-mailcow', $extra); $logs = get_logs('postfix-mailcow', $extra);
} }
else { else {
$logs = get_logs('postfix-mailcow', -1); $logs = get_logs('postfix-mailcow');
} }
if (isset($logs) && !empty($logs)) { if (isset($logs) && !empty($logs)) {
echo json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); echo json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
@ -770,12 +806,13 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
} }
break; break;
case "autodiscover": case "autodiscover":
if (isset($extra) && !empty($extra)) { // 0 is first record, so empty is fine
$extra = intval($extra); if (isset($extra)) {
$extra = preg_replace('/[^\d\-]/i', '', $extra);
$logs = get_logs('autodiscover-mailcow', $extra); $logs = get_logs('autodiscover-mailcow', $extra);
} }
else { else {
$logs = get_logs('autodiscover-mailcow', -1); $logs = get_logs('autodiscover-mailcow');
} }
if (isset($logs) && !empty($logs)) { if (isset($logs) && !empty($logs)) {
echo json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); echo json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
@ -785,12 +822,13 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
} }
break; break;
case "sogo": case "sogo":
if (isset($extra) && !empty($extra)) { // 0 is first record, so empty is fine
$extra = intval($extra); if (isset($extra)) {
$extra = preg_replace('/[^\d\-]/i', '', $extra);
$logs = get_logs('sogo-mailcow', $extra); $logs = get_logs('sogo-mailcow', $extra);
} }
else { else {
$logs = get_logs('sogo-mailcow', -1); $logs = get_logs('sogo-mailcow');
} }
if (isset($logs) && !empty($logs)) { if (isset($logs) && !empty($logs)) {
echo json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); echo json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
@ -800,7 +838,14 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
} }
break; break;
case "rspamd-history": case "rspamd-history":
$logs = get_logs('rspamd-history'); // 0 is first record, so empty is fine
if (isset($extra)) {
$extra = preg_replace('/[^\d\-]/i', '', $extra);
$logs = get_logs('rspamd-history', $extra);
}
else {
$logs = get_logs('rspamd-history');
}
if (isset($logs) && !empty($logs)) { if (isset($logs) && !empty($logs)) {
echo json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); echo json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
} }
@ -863,7 +908,13 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
$syncjobs = mailbox('get', 'syncjobs', $mailbox); $syncjobs = mailbox('get', 'syncjobs', $mailbox);
if (!empty($syncjobs)) { if (!empty($syncjobs)) {
foreach ($syncjobs as $syncjob) { foreach ($syncjobs as $syncjob) {
if ($details = mailbox('get', 'syncjob_details', $syncjob)) { if (isset($extra)) {
$details = mailbox('get', 'syncjob_details', $syncjob, explode(',', $extra));
}
else {
$details = mailbox('get', 'syncjob_details', $syncjob);
}
if ($details) {
$data[] = $details; $data[] = $details;
} }
else { else {
@ -890,7 +941,83 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
$syncjobs = mailbox('get', 'syncjobs', $object); $syncjobs = mailbox('get', 'syncjobs', $object);
if (!empty($syncjobs)) { if (!empty($syncjobs)) {
foreach ($syncjobs as $syncjob) { foreach ($syncjobs as $syncjob) {
if ($details = mailbox('get', 'syncjob_details', $syncjob)) { if (isset($extra)) {
$details = mailbox('get', 'syncjob_details', $syncjob, explode(',', $extra));
}
else {
$details = mailbox('get', 'syncjob_details', $syncjob);
}
if ($details) {
$data[] = $details;
}
else {
continue;
}
}
}
if (!isset($data) || empty($data)) {
echo '{}';
}
else {
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
break;
}
break;
case "active-user-sieve":
if (isset($object)) {
$sieve_filter = mailbox('get', 'active_user_sieve', $object);
if (!empty($sieve_filter)) {
$data[] = $sieve_filter;
}
}
if (!isset($data) || empty($data)) {
echo '{}';
}
else {
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
break;
case "filters":
switch ($object) {
case "all":
$domains = mailbox('get', 'domains');
if (!empty($domains)) {
foreach ($domains as $domain) {
$mailboxes = mailbox('get', 'mailboxes', $domain);
if (!empty($mailboxes)) {
foreach ($mailboxes as $mailbox) {
$filters = mailbox('get', 'filters', $mailbox);
if (!empty($filters)) {
foreach ($filters as $filter) {
if ($details = mailbox('get', 'filter_details', $filter)) {
$data[] = $details;
}
else {
continue;
}
}
}
}
}
}
if (!isset($data) || empty($data)) {
echo '{}';
}
else {
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
}
else {
echo '{}';
}
break;
default:
$filters = mailbox('get', 'filters', $object);
if (!empty($filters)) {
foreach ($filters as $filter) {
if ($details = mailbox('get', 'filter_details', $filter)) {
$data[] = $details; $data[] = $details;
} }
else { else {
@ -1296,6 +1423,47 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
)); ));
} }
break; break;
case "filter":
if (isset($_POST['items'])) {
$items = (array)json_decode($_POST['items'], true);
if (is_array($items)) {
if (mailbox('delete', 'filter', array('id' => $items)) === false) {
if (isset($_SESSION['return'])) {
echo json_encode($_SESSION['return']);
}
else {
echo json_encode(array(
'type' => 'error',
'msg' => 'Deletion of items/s failed'
));
}
}
else {
if (isset($_SESSION['return'])) {
echo json_encode($_SESSION['return']);
}
else {
echo json_encode(array(
'type' => 'success',
'msg' => 'Task completed'
));
}
}
}
else {
echo json_encode(array(
'type' => 'error',
'msg' => 'Cannot find id array in post data'
));
}
}
else {
echo json_encode(array(
'type' => 'error',
'msg' => 'Cannot find items in post data'
));
}
break;
case "fwdhost": case "fwdhost":
if (isset($_POST['items'])) { if (isset($_POST['items'])) {
$items = (array)json_decode($_POST['items'], true); $items = (array)json_decode($_POST['items'], true);
@ -2102,6 +2270,50 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
)); ));
} }
break; break;
case "filter":
if (isset($_POST['items']) && isset($_POST['attr'])) {
$items = (array)json_decode($_POST['items'], true);
$attr = (array)json_decode($_POST['attr'], true);
$postarray = array_merge(array('id' => $items), $attr);
if (is_array($postarray['id'])) {
if (mailbox('edit', 'filter', $postarray) === false) {
if (isset($_SESSION['return'])) {
echo json_encode($_SESSION['return']);
}
else {
echo json_encode(array(
'type' => 'error',
'msg' => 'Edit failed'
));
}
exit();
}
else {
if (isset($_SESSION['return'])) {
echo json_encode($_SESSION['return']);
}
else {
echo json_encode(array(
'type' => 'success',
'msg' => 'Task completed'
));
}
}
}
else {
echo json_encode(array(
'type' => 'error',
'msg' => 'Incomplete post data'
));
}
}
else {
echo json_encode(array(
'type' => 'error',
'msg' => 'Incomplete post data'
));
}
break;
case "resource": case "resource":
if (isset($_POST['items']) && isset($_POST['attr'])) { if (isset($_POST['items']) && isset($_POST['attr'])) {
$items = (array)json_decode($_POST['items'], true); $items = (array)json_decode($_POST['items'], true);

View File

@ -99,6 +99,9 @@ $lang['danger']['domain_not_empty'] = 'Kann nur leere Domains entfernen';
$lang['warning']['spam_alias_temp_error'] = 'Kann zur Zeit keinen Spam-Alias erstellen, bitte versuchen Sie es später noch einmal.'; $lang['warning']['spam_alias_temp_error'] = 'Kann zur Zeit keinen Spam-Alias erstellen, bitte versuchen Sie es später noch einmal.';
$lang['danger']['spam_alias_max_exceeded'] = 'Maximale Anzahl an Spam-Alias-Adressen erreicht'; $lang['danger']['spam_alias_max_exceeded'] = 'Maximale Anzahl an Spam-Alias-Adressen erreicht';
$lang['danger']['validity_missing'] = 'Bitte geben Sie eine Gültigkeitsdauer an'; $lang['danger']['validity_missing'] = 'Bitte geben Sie eine Gültigkeitsdauer an';
$lang['user']['loading'] = "Lade...";
$lang['user']['active_sieve'] = "Aktiver Filter";
$lang['user']['no_active_filter'] = "Kein aktiver Filter vorhanden";
$lang['user']['on'] = 'Ein'; $lang['user']['on'] = 'Ein';
$lang['user']['off'] = 'Aus'; $lang['user']['off'] = 'Aus';
$lang['user']['messages'] = "Nachrichten"; $lang['user']['messages'] = "Nachrichten";
@ -155,6 +158,9 @@ $lang['user']['spamfilter_red'] = 'Rot: Die Nachricht ist eindeutig Spam und wir
$lang['user']['spamfilter_default_score'] = 'Standardwert:'; $lang['user']['spamfilter_default_score'] = 'Standardwert:';
$lang['user']['spamfilter_hint'] = 'Der erste Wert beschreibt den "low spam score", der zweite Wert den "high spam score".'; $lang['user']['spamfilter_hint'] = 'Der erste Wert beschreibt den "low spam score", der zweite Wert den "high spam score".';
$lang['user']['spamfilter_table_domain_policy'] = "n.v. (Domainrichtlinie)"; $lang['user']['spamfilter_table_domain_policy'] = "n.v. (Domainrichtlinie)";
$lang['user']['waiting'] = "Warte auf Ausführung";
$lang['user']['status'] = "Status";
$lang['user']['running'] = "Wird ausgeführt";
$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_warning'] = '<strong>Vorsicht:</strong> Entscheiden Sie sich unverschlüsselte Verbindungen abzulehnen, kann dies dazu führen, dass Kontakte Sie nicht mehr erreichen.<br>Nachrichten, die die Richtlinie nicht erfüllen, werden durch einen Hard-Fail im Mailsystem abgewiesen.<br>Diese Einstellung ist aktiv für die primäre Mailbox, für alle Alias-Adressen, die dieser Mailbox <b>direkt zugeordnet</b> sind (lediglich eine einzige Ziel-Adresse) und der Adressen, die sich aus Alias-Domains ergeben. Ausgeschlossen sind temporäre Aliasse ("Spam-Alias-Adressen"), Catch-All Alias-Adressen sowie Alias-Adressen mit mehreren Zielen.';
$lang['user']['tls_policy'] = 'Verschlüsselungsrichtlinie'; $lang['user']['tls_policy'] = 'Verschlüsselungsrichtlinie';
@ -215,6 +221,7 @@ $lang['header']['mailcow_settings'] = 'Konfiguration';
$lang['header']['administration'] = 'Administration'; $lang['header']['administration'] = 'Administration';
$lang['header']['mailboxes'] = 'Mailboxen'; $lang['header']['mailboxes'] = 'Mailboxen';
$lang['header']['user_settings'] = 'Benutzereinstellungen'; $lang['header']['user_settings'] = 'Benutzereinstellungen';
$lang['header']['diagnostics'] = 'Diagnose';
$lang['header']['login'] = 'Anmeldung'; $lang['header']['login'] = 'Anmeldung';
$lang['header']['logged_in_as_logout'] = 'Eingeloggt als <b>%s</b> (abmelden)'; $lang['header']['logged_in_as_logout'] = 'Eingeloggt als <b>%s</b> (abmelden)';
$lang['header']['logged_in_as_logout_dual'] = 'Eingeloggt als <b>%s <span class="text-info">[%s]</span></b>'; $lang['header']['logged_in_as_logout_dual'] = 'Eingeloggt als <b>%s <span class="text-info">[%s]</span></b>';
@ -265,6 +272,10 @@ $lang['mailbox']['deactivate'] = 'Deaktivieren';
$lang['mailbox']['owner'] = 'Besitzer'; $lang['mailbox']['owner'] = 'Besitzer';
$lang['mailbox']['mins_interval'] = 'Intervall (min)'; $lang['mailbox']['mins_interval'] = 'Intervall (min)';
$lang['mailbox']['last_run'] = 'Letzte Ausführung'; $lang['mailbox']['last_run'] = 'Letzte Ausführung';
$lang['mailbox']['last_run_reset'] = 'Als nächstes ausführen';
$lang['mailbox']['sieve_info'] = 'Es können mehrere Filter pro Benutzer existieren, aber nur ein Filter eines Typs (Pre-/Postfilter) kann gleichzeitig aktiv sein.<br>
Die Ausführung erfolgt in nachstehender Reihenfolge. Ein fehlgeschlagenes Script sowie der Befehl "keep;" stoppen die weitere Verarbeitung <b>nicht</b>.<br>
Prefilter User scripts Postfilter <a href="https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/sieve_after" target="_blank">global sieve postfilter</a>';
$lang['info']['no_action'] = 'Keine Aktion anwendbar'; $lang['info']['no_action'] = 'Keine Aktion anwendbar';
$lang['delete']['title'] = 'Objekt entfernen'; $lang['delete']['title'] = 'Objekt entfernen';
@ -287,7 +298,7 @@ $lang['edit']['syncjob'] = 'Sync-Job bearbeiten';
$lang['edit']['save'] = 'Änderungen speichern'; $lang['edit']['save'] = 'Änderungen speichern';
$lang['edit']['username'] = 'Benutzername'; $lang['edit']['username'] = 'Benutzername';
$lang['edit']['hostname'] = 'Servername'; $lang['edit']['hostname'] = 'Servername';
$lang['edit']['encryption'] = 'Verschlüsselungsmethode'; $lang['edit']['encryption'] = 'Verschlüsselung';
$lang['edit']['maxage'] = 'Maximales Alter in Tagen einer Nachricht, die kopiert werden soll</br ><small>(0 = alle Nachrichten kopieren)</small>'; $lang['edit']['maxage'] = 'Maximales Alter in Tagen einer Nachricht, die kopiert werden soll</br ><small>(0 = alle Nachrichten kopieren)</small>';
$lang['edit']['subfolder2'] = 'Ziel-Ordner<br><small>(leer = kein Unterordner)</small>'; $lang['edit']['subfolder2'] = 'Ziel-Ordner<br><small>(leer = kein Unterordner)</small>';
$lang['edit']['mins_interval'] = 'Intervall (min)'; $lang['edit']['mins_interval'] = 'Intervall (min)';
@ -339,7 +350,7 @@ $lang['add']['syncjob_hint'] = 'Passwörter werden unverschlüsselt abgelegt!';
$lang['add']['hostname'] = 'Servername'; $lang['add']['hostname'] = 'Servername';
$lang['add']['port'] = 'Port'; $lang['add']['port'] = 'Port';
$lang['add']['username'] = 'Benutzername'; $lang['add']['username'] = 'Benutzername';
$lang['add']['enc_method'] = 'Verschlüsselungsmethode'; $lang['add']['enc_method'] = 'Verschlüsselung';
$lang['add']['maxage'] = 'Maximales Alter von Nachrichten, welche vom Remote abgefragt werden (0 = Alter ignorieren)'; $lang['add']['maxage'] = 'Maximales Alter von Nachrichten, welche vom Remote abgefragt werden (0 = Alter ignorieren)';
$lang['add']['subfolder2'] = 'Synchronisation in Unterordner am Ziel'; $lang['add']['subfolder2'] = 'Synchronisation in Unterordner am Ziel';
$lang['add']['mins_interval'] = 'Abrufintervall (Minuten)'; $lang['add']['mins_interval'] = 'Abrufintervall (Minuten)';
@ -388,6 +399,21 @@ $lang['add']['password_repeat'] = 'Passwort (Wiederholung)';
$lang['add']['previous'] = 'Vorherige Seite'; $lang['add']['previous'] = 'Vorherige Seite';
$lang['add']['restart_sogo_hint'] = 'Der SOGo Container muss nach dem Hinzufügen einer neuen Domain neugestartet werden!'; $lang['add']['restart_sogo_hint'] = 'Der SOGo Container muss nach dem Hinzufügen einer neuen Domain neugestartet werden!';
$lang['add']['goto_null'] = 'Nachrichten sofort verwerfen'; $lang['add']['goto_null'] = 'Nachrichten sofort verwerfen';
$lang['add']['validation_success'] = 'Erfolgreich validiert';
$lang['add']['activate_filter_warn'] = 'Alle anderen Filter diesen Typs werden deaktiviert, falls dieses Script aktiv markiert wird.';
$lang['add']['validate'] = 'Validieren';
$lang['mailbox']['add_filter'] = 'Filter erstellen';
$lang['add']['sieve_desc'] = 'Kurze Beschreibung';
$lang['edit']['sieve_desc'] = 'Kurze Beschreibung';
$lang['add']['sieve_type'] = 'Filtertyp';
$lang['edit']['sieve_type'] = 'Filtertyp';
$lang['mailbox']['set_prefilter'] = 'Als Prefilter markieren';
$lang['mailbox']['set_postfilter'] = 'Als Postfilter markieren';
$lang['mailbox']['filters'] = 'Filter';
$lang['mailbox']['sync_jobs'] = 'Synchronisationen';
$lang['mailbox']['inactive'] = 'Inaktiv';
$lang['edit']['validate_save'] = 'Validieren und speichern';
$lang['login']['title'] = 'Anmeldung'; $lang['login']['title'] = 'Anmeldung';
$lang['login']['administration'] = 'Administration'; $lang['login']['administration'] = 'Administration';
@ -425,6 +451,8 @@ $lang['tfa']['scan_qr_code'] = "Bitte scannen Sie jetzt den angezeigten QR-Code:
$lang['tfa']['enter_qr_code'] = "Falls Sie den angezeigten QR-Code nicht scannen können, verwenden Sie bitte nachstehenden Sicherheitsschlüssel"; $lang['tfa']['enter_qr_code'] = "Falls Sie den angezeigten QR-Code nicht scannen können, verwenden Sie bitte nachstehenden Sicherheitsschlüssel";
$lang['tfa']['confirm_totp_token'] = "Bitte bestätigen Sie die Änderung durch Eingabe eines generierten Tokens"; $lang['tfa']['confirm_totp_token'] = "Bitte bestätigen Sie die Änderung durch Eingabe eines generierten Tokens";
$lang['admin']['no_new_rows'] = 'Keine weiteren Zeilen vorhanden';
$lang['admin']['additional_rows'] = ' zusätzliche Zeilen geladen'; // parses to 'n additional rows were added'
$lang['admin']['private_key'] = 'Private Key'; $lang['admin']['private_key'] = 'Private Key';
$lang['admin']['import'] = 'Importieren'; $lang['admin']['import'] = 'Importieren';
$lang['admin']['import_private_key'] = 'Private Key importieren'; $lang['admin']['import_private_key'] = 'Private Key importieren';
@ -510,6 +538,12 @@ $lang['success']['forwarding_host_removed'] = "Weiterleitungs-Host %s wurde entf
$lang['success']['forwarding_host_added'] = "Weiterleitungs-Host %s wurde hinzugefügt"; $lang['success']['forwarding_host_added'] = "Weiterleitungs-Host %s wurde hinzugefügt";
$lang['success']['relayhost_removed'] = "Relayhost %s wurde entfernt"; $lang['success']['relayhost_removed'] = "Relayhost %s wurde entfernt";
$lang['success']['relayhost_added'] = "Relayhost %s wurde hinzugefügt"; $lang['success']['relayhost_added'] = "Relayhost %s wurde hinzugefügt";
$lang['diagnostics']['dns_records'] = 'DNS-Einträge';
$lang['diagnostics']['dns_records_24hours'] = 'Bitte beachten Sie, dass es bis zu 24 Stunden dauern kann, bis Änderungen an Ihren DNS-Einträgen als aktueller Status auf dieser Seite dargestellt werden. Diese Seite ist nur als Hilfsmittel gedacht, um die korrekten Werte für DNS-Einträge zu anzuzeigen und zu überprüfen, ob die Daten im DNS hinterlegt sind.';
$lang['diagnostics']['dns_records_name'] = 'Name';
$lang['diagnostics']['dns_records_type'] = 'Typ';
$lang['diagnostics']['dns_records_data'] = 'Korrekte Daten';
$lang['diagnostics']['dns_records_status'] = 'Aktueller Status';
$lang['admin']['relay_from'] = "Absenderadresse"; $lang['admin']['relay_from'] = "Absenderadresse";
$lang['admin']['relay_run'] = "Test durchführen"; $lang['admin']['relay_run'] = "Test durchführen";
$lang['admin']['customize'] = "Anpassung"; $lang['admin']['customize'] = "Anpassung";
@ -523,3 +557,6 @@ $lang['admin']['remove_row'] = "Zeile entfernen";
$lang['admin']['add_row'] = "Zeile hinzufügen"; $lang['admin']['add_row'] = "Zeile hinzufügen";
$lang['admin']['reset_default'] = "Auf Standard zurücksetzen"; $lang['admin']['reset_default'] = "Auf Standard zurücksetzen";
$lang['admin']['merged_vars_hint'] = 'Ausgegraute Zeilen wurden aus der Datei <code>vars.inc.(local.)php</code> gelesen und können nicht mittels UI verändert werden.'; $lang['admin']['merged_vars_hint'] = 'Ausgegraute Zeilen wurden aus der Datei <code>vars.inc.(local.)php</code> gelesen und können nicht mittels UI verändert werden.';
$lang['mailbox']['waiting'] = "Wartend";
$lang['mailbox']['status'] = "Status";
$lang['mailbox']['running'] = "In Ausführung";

View File

@ -99,6 +99,9 @@ $lang['danger']['domain_not_empty'] = "Cannot remove non-empty domain";
$lang['warning']['spam_alias_temp_error'] = "Temporary error: Cannot add spam alias, please try again later."; $lang['warning']['spam_alias_temp_error'] = "Temporary error: Cannot add spam alias, please try again later.";
$lang['danger']['spam_alias_max_exceeded'] = "Max. allowed spam alias addresses exceeded"; $lang['danger']['spam_alias_max_exceeded'] = "Max. allowed spam alias addresses exceeded";
$lang['danger']['validity_missing'] = 'Please assign a period of validity'; $lang['danger']['validity_missing'] = 'Please assign a period of validity';
$lang['user']['loading'] = "Loading...";
$lang['user']['active_sieve'] = "Active filter";
$lang['user']['no_active_filter'] = "No active filter available";
$lang['user']['on'] = "On"; $lang['user']['on'] = "On";
$lang['user']['off'] = "Off"; $lang['user']['off'] = "Off";
$lang['user']['messages'] = "messages"; // "123 messages" $lang['user']['messages'] = "messages"; // "123 messages"
@ -154,6 +157,9 @@ $lang['user']['spamfilter_red'] = 'Red: This message is spam and will be rejecte
$lang['user']['spamfilter_default_score'] = 'Default values:'; $lang['user']['spamfilter_default_score'] = 'Default values:';
$lang['user']['spamfilter_hint'] = 'The first value describes the "low spam score", the second represents the "high spam score".'; $lang['user']['spamfilter_hint'] = 'The first value describes the "low spam score", the second represents the "high spam score".';
$lang['user']['spamfilter_table_domain_policy'] = "n/a (domain policy)"; $lang['user']['spamfilter_table_domain_policy'] = "n/a (domain policy)";
$lang['user']['waiting'] = "Waiting";
$lang['user']['status'] = "Status";
$lang['user']['running'] = "Running";
$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_warning'] = '<strong>Warning:</strong> If you decide to enforce encrypted mail transfer, you may lose emails.<br>Messages to not satisfy the policy will be bounced with a hard fail by the mail system.<br>This option applies to your primary email address (login name), all addresses derived from alias domains as well as alias addresses <b>with only this single mailbox</b> as target.';
$lang['user']['tls_policy'] = 'Encryption policy'; $lang['user']['tls_policy'] = 'Encryption policy';
@ -214,6 +220,7 @@ $lang['header']['mailcow_settings'] = 'Configuration';
$lang['header']['administration'] = 'Administration'; $lang['header']['administration'] = 'Administration';
$lang['header']['mailboxes'] = 'Mailboxes'; $lang['header']['mailboxes'] = 'Mailboxes';
$lang['header']['user_settings'] = 'User settings'; $lang['header']['user_settings'] = 'User settings';
$lang['header']['diagnostics'] = 'Diagnostics';
$lang['header']['login'] = 'Login'; $lang['header']['login'] = 'Login';
$lang['header']['logged_in_as_logout'] = 'Logged in as <b>%s</b> (logout)'; $lang['header']['logged_in_as_logout'] = 'Logged in as <b>%s</b> (logout)';
$lang['header']['logged_in_as_logout_dual'] = 'Logged in as <b>%s <span class="text-info">[%s]</span></b>'; $lang['header']['logged_in_as_logout_dual'] = 'Logged in as <b>%s <span class="text-info">[%s]</span></b>';
@ -265,7 +272,10 @@ $lang['mailbox']['deactivate'] = 'Deactivate';
$lang['mailbox']['owner'] = 'Owner'; $lang['mailbox']['owner'] = 'Owner';
$lang['mailbox']['mins_interval'] = 'Interval (min)'; $lang['mailbox']['mins_interval'] = 'Interval (min)';
$lang['mailbox']['last_run'] = 'Last run'; $lang['mailbox']['last_run'] = 'Last run';
$lang['mailbox']['last_run_reset'] = 'Schedule next';
$lang['mailbox']['sieve_info'] = 'You can store multiple filters per user, but only one prefilter and one postfilter can be active at the same time.<br>
Each filter will be processed in the described order. Neither a failed script nor an issued "keep;" will stop processing of further scripts.<br>
Prefilter User scripts Postfilter <a href="https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/sieve_after" target="_blank">global sieve postfilter</a>';
$lang['info']['no_action'] = 'No action applicable'; $lang['info']['no_action'] = 'No action applicable';
$lang['delete']['title'] = 'Remove object'; $lang['delete']['title'] = 'Remove object';
@ -389,6 +399,21 @@ $lang['add']['password_repeat'] = 'Confirmation password (repeat)';
$lang['add']['previous'] = 'Previous page'; $lang['add']['previous'] = 'Previous page';
$lang['add']['restart_sogo_hint'] = 'You will need to restart the SOGo service container after adding a new domain!'; $lang['add']['restart_sogo_hint'] = 'You will need to restart the SOGo service container after adding a new domain!';
$lang['add']['goto_null'] = 'Silently discard mail'; $lang['add']['goto_null'] = 'Silently discard mail';
$lang['add']['validation_success'] = 'Validated successfully';
$lang['add']['activate_filter_warn'] = 'All other filters will be deactivated, when active is checked.';
$lang['add']['validate'] = 'Validate';
$lang['mailbox']['add_filter'] = 'Add filter';
$lang['add']['sieve_desc'] = 'Short description';
$lang['edit']['sieve_desc'] = 'Short description';
$lang['add']['sieve_type'] = 'Filter type';
$lang['edit']['sieve_type'] = 'Filter type';
$lang['mailbox']['set_prefilter'] = 'Mark as prefilter';
$lang['mailbox']['set_postfilter'] = 'Mark as postfilter';
$lang['mailbox']['filters'] = 'Filters';
$lang['mailbox']['sync_jobs'] = 'Sync jobs';
$lang['mailbox']['inactive'] = 'Inactive';
$lang['edit']['validate_save'] = 'Validate and save';
$lang['login']['title'] = 'Login'; $lang['login']['title'] = 'Login';
$lang['login']['administration'] = 'Administration'; $lang['login']['administration'] = 'Administration';
@ -425,6 +450,8 @@ $lang['tfa']['scan_qr_code'] = "Please scan the following code with your authent
$lang['tfa']['enter_qr_code'] = "Your TOTP code if your device cannot scan QR codes"; $lang['tfa']['enter_qr_code'] = "Your TOTP code if your device cannot scan QR codes";
$lang['tfa']['confirm_totp_token'] = "Please confirm your changes by entering the generated token"; $lang['tfa']['confirm_totp_token'] = "Please confirm your changes by entering the generated token";
$lang['admin']['no_new_rows'] = 'No further rows available';
$lang['admin']['additional_rows'] = ' additional rows were added'; // parses to 'n additional rows were added'
$lang['admin']['private_key'] = 'Private key'; $lang['admin']['private_key'] = 'Private key';
$lang['admin']['import'] = 'Import'; $lang['admin']['import'] = 'Import';
$lang['admin']['import_private_key'] = 'Import private key'; $lang['admin']['import_private_key'] = 'Import private key';
@ -516,6 +543,12 @@ $lang['success']['forwarding_host_removed'] = "Forwarding host %s has been remov
$lang['success']['forwarding_host_added'] = "Forwarding host %s has been added"; $lang['success']['forwarding_host_added'] = "Forwarding host %s has been added";
$lang['success']['relayhost_removed'] = "Relayhost %s has been removed"; $lang['success']['relayhost_removed'] = "Relayhost %s has been removed";
$lang['success']['relayhost_added'] = "Relayhost %s has been added"; $lang['success']['relayhost_added'] = "Relayhost %s has been added";
$lang['diagnostics']['dns_records'] = 'DNS Records';
$lang['diagnostics']['dns_records_24hours'] = 'Please note that changes made to DNS may take up to 24 hours to correctly have their current state reflected on this page. It is intended as a way for you to easily see how to configure your DNS records and to check whether all your records are correctly stored in DNS.';
$lang['diagnostics']['dns_records_name'] = 'Name';
$lang['diagnostics']['dns_records_type'] = 'Type';
$lang['diagnostics']['dns_records_data'] = 'Correct Data';
$lang['diagnostics']['dns_records_status'] = 'Current State';
$lang['admin']['relay_from'] = '"From:" address'; $lang['admin']['relay_from'] = '"From:" address';
$lang['admin']['relay_run'] = "Run test"; $lang['admin']['relay_run'] = "Run test";
@ -530,6 +563,9 @@ $lang['admin']['remove_row'] = "Remove row";
$lang['admin']['add_row'] = "Add row"; $lang['admin']['add_row'] = "Add row";
$lang['admin']['reset_default'] = "Reset to default"; $lang['admin']['reset_default'] = "Reset to default";
$lang['admin']['merged_vars_hint'] = 'Greyed out rows were merged from <code>vars.inc.(local.)php</code> and cannot be modified.'; $lang['admin']['merged_vars_hint'] = 'Greyed out rows were merged from <code>vars.inc.(local.)php</code> and cannot be modified.';
$lang['mailbox']['waiting'] = "Waiting";
$lang['mailbox']['status'] = "Status";
$lang['mailbox']['running'] = "Running";
$lang['edit']['tls_policy'] = "Change TLS policy"; $lang['edit']['tls_policy'] = "Change TLS policy";
$lang['edit']['spam_score'] = "Set a custom spam score"; $lang['edit']['spam_score'] = "Set a custom spam score";

View File

@ -19,7 +19,8 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
<li role="presentation"><a href="#tab-domain-aliases" aria-controls="tab-domain-aliases" role="tab" data-toggle="tab"><?=$lang['mailbox']['domain_aliases'];?></a></li> <li role="presentation"><a href="#tab-domain-aliases" aria-controls="tab-domain-aliases" role="tab" data-toggle="tab"><?=$lang['mailbox']['domain_aliases'];?></a></li>
</ul> </ul>
</li> </li>
<li role="presentation"><a href="#tab-syncjobs" aria-controls="tab-resources" role="tab" data-toggle="tab">Sync jobs</a></li> <li role="presentation"><a href="#tab-syncjobs" aria-controls="tab-syncjobs" role="tab" data-toggle="tab"><?=$lang['mailbox']['sync_jobs'];?></a></li>
<li role="presentation"><a href="#tab-filters" aria-controls="tab-filters" role="tab" data-toggle="tab"><?=$lang['mailbox']['filters'];?></a></li>
</ul> </ul>
<div class="row"> <div class="row">
@ -155,7 +156,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
<div role="tabpanel" class="tab-pane" id="tab-syncjobs"> <div role="tabpanel" class="tab-pane" id="tab-syncjobs">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title">Sync jobs</h3> <h3 class="panel-title"><?=$lang['mailbox']['sync_jobs'];?></h3>
</div> </div>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped" id="sync_job_table"></table> <table class="table table-striped" id="sync_job_table"></table>
@ -165,6 +166,8 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
<a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="syncjob" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="syncjob" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
<a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a id="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"last_run":""}' href="#"><?=$lang['mailbox']['last_run_reset'];?></a></li>
<li role="separator" class="divider"></li>
<li><a id="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> <li><a id="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li>
<li><a id="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> <li><a id="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li>
<li role="separator" class="divider"></li> <li role="separator" class="divider"></li>
@ -173,6 +176,35 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
<a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addSyncJobModalAdmin"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['create_syncjob'];?></a> <a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addSyncJobModalAdmin"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['create_syncjob'];?></a>
</div> </div>
</div> </div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="tab-filters">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><?=$lang['mailbox']['filters'];?></h3>
</div>
<p style="margin:10px" class="help-block"><?=$lang['mailbox']['sieve_info'];?></p>
<div class="table-responsive">
<table class="table table-striped" id="filter_table"></table>
</div>
<div class="mass-actions-mailbox">
<div class="btn-group">
<a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="filter_item" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
<a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a id="edit_selected" data-id="filter_item" data-api-url='edit/filter' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li>
<li><a id="edit_selected" data-id="filter_item" data-api-url='edit/filter' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li>
<li role="separator" class="divider"></li>
<li><a id="edit_selected" data-id="filter_item" data-api-url='edit/filter' data-api-attr='{"filter_type":"prefilter"}' href="#"><?=$lang['mailbox']['set_prefilter'];?></a></li>
<li><a id="edit_selected" data-id="filter_item" data-api-url='edit/filter' data-api-attr='{"filter_type":"postfilter"}' href="#"><?=$lang['mailbox']['set_postfilter'];?></a></li>
<li role="separator" class="divider"></li>
<li><a id="delete_selected" data-text="<?=$lang['user']['eas_reset'];?>?" data-id="filter_item" data-api-url='delete/filter' href="#"><?=$lang['mailbox']['remove'];?></a></li>
</ul>
<a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addFilterModalAdmin"><span class="glyphicon glyphicon-plus"></span> <?=$lang['mailbox']['add_filter'];?></a>
</div>
</div>
</div>
</div> </div>
</div> <!-- /tab-content --> </div> <!-- /tab-content -->

View File

@ -44,6 +44,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" name="quota" min="1" max="" id="addInputQuota" disabled value="<?=$lang['add']['select_domain'];?>" required> <input type="text" class="form-control" name="quota" min="1" max="" id="addInputQuota" disabled value="<?=$lang['add']['select_domain'];?>" required>
<small class="help-block">min. 1</small>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -314,7 +315,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
<h3 class="modal-title"><?=$lang['add']['syncjob'];?></h3> <h3 class="modal-title"><?=$lang['add']['syncjob'];?></h3>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p><?=$lang['add']['syncjob_hint'];?></p> <p class="help-block"><?=$lang['add']['syncjob_hint'];?></p>
<form class="form-horizontal" role="form" data-id="add_syncjob"> <form class="form-horizontal" role="form" data-id="add_syncjob">
<div class="form-group"> <div class="form-group">
<label class="control-label col-sm-2" for="username"><?=$lang['add']['username'];?>:</label> <label class="control-label col-sm-2" for="username"><?=$lang['add']['username'];?>:</label>
@ -344,6 +345,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
<label class="control-label col-sm-2" for="port1"><?=$lang['add']['port'];?></label> <label class="control-label col-sm-2" for="port1"><?=$lang['add']['port'];?></label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" name="port1" id="port1" min="1" max="65535" value="143" required> <input type="number" class="form-control" name="port1" id="port1" min="1" max="65535" value="143" required>
<small class="help-block">1-65535</small>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -372,6 +374,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
<label class="control-label col-sm-2" for="mins_interval"><?=$lang['add']['mins_interval'];?></label> <label class="control-label col-sm-2" for="mins_interval"><?=$lang['add']['mins_interval'];?></label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" name="mins_interval" min="10" max="3600" value="20" required> <input type="number" class="form-control" name="mins_interval" min="10" max="3600" value="20" required>
<small class="help-block">10-3600</small>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -384,6 +387,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
<label class="control-label col-sm-2" for="maxage"><?=$lang['edit']['maxage'];?></label> <label class="control-label col-sm-2" for="maxage"><?=$lang['edit']['maxage'];?></label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" name="maxage" id="maxage" min="0" max="32000" value="0"> <input type="number" class="form-control" name="maxage" id="maxage" min="0" max="32000" value="0">
<small class="help-block">0-32000</small>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -437,12 +441,81 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
</div> </div>
</div> </div>
</div><!-- add sync job modal --> </div><!-- add sync job modal -->
<!-- log modal --> <!-- add add_filter modal -->
<div class="modal fade" id="logModal" tabindex="-1" role="dialog" aria-labelledby="logTextLabel"> <div class="modal fade" id="addFilterModalAdmin" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog" style="width:90%" role="document"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span></button>
<h3 class="modal-title">Filter</h3>
</div>
<div class="modal-body"> <div class="modal-body">
<span id="logText"></span> <form class="form-horizontal" role="form" data-id="add_filter">
<div class="form-group">
<label class="control-label col-sm-2" for="username"><?=$lang['add']['username'];?>:</label>
<div class="col-sm-10">
<select id="addSelectUsername" name="username" id="username" required>
<?php
$domains = mailbox('get', 'domains');
if (!empty($domains)) {
foreach ($domains as $domain) {
$mailboxes = mailbox('get', 'mailboxes', $domain);
foreach ($mailboxes as $mailbox) {
echo "<option>".htmlspecialchars($mailbox)."</option>";
}
}
}
?>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="filter_type"><?=$lang['add']['sieve_type'];?>:</label>
<div class="col-sm-10">
<select id="addFilterType" name="filter_type" id="filter_type" required>
<option value="prefilter">Prefilter</option>
<option value="postfilter">Postfilter</option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="script_desc"><?=$lang['add']['sieve_desc'];?>:</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="script_desc" id="script_desc" required maxlength="255">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="script_data">Script:</label>
<div class="col-sm-10">
<textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control" rows="20" id="script_data" name="script_data" required></textarea>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<p class="help-block"><?=$lang['add']['activate_filter_warn'];?></p>
<div class="checkbox">
<label><input type="checkbox" value="1" name="active" checked> <?=$lang['add']['active'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10" id="add_filter_btns">
<button class="btn btn-default" id="validate_sieve" href="#"><?=$lang['add']['validate'];?></button>
<button class="btn btn-success" id="add_item" data-id="add_filter" data-api-url='add/filter' data-api-attr='{}' href="#" disabled><?=$lang['admin']['add'];?></button>
</div>
</div>
</form>
</div>
</div>
</div>
</div><!-- add add_filter modal -->
<!-- log modal -->
<div class="modal fade" id="syncjobLogModal" tabindex="-1" role="dialog" aria-labelledby="syncjobLogModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header"><h4 class="modal-title">Log</h4></div>
<div class="modal-body">
<textarea class="form-control" rows="20" id="logText" spellcheck="false"></textarea>
</div> </div>
</div> </div>
</div> </div>

View File

@ -25,6 +25,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
<label class="control-label col-sm-2" for="port1"><?=$lang['add']['port'];?></label> <label class="control-label col-sm-2" for="port1"><?=$lang['add']['port'];?></label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" name="port1" id="port1" min="1" max="65535" value="143" required> <input type="number" class="form-control" name="port1" id="port1" min="1" max="65535" value="143" required>
<small class="help-block">1-65535</small>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -53,6 +54,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
<label class="control-label col-sm-2" for="mins_interval"><?=$lang['add']['mins_interval'];?></label> <label class="control-label col-sm-2" for="mins_interval"><?=$lang['add']['mins_interval'];?></label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" name="mins_interval" min="10" max="3600" value="20" required> <input type="number" class="form-control" name="mins_interval" min="10" max="3600" value="20" required>
<small class="help-block">10-3600</small>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -65,6 +67,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
<label class="control-label col-sm-2" for="maxage"><?=$lang['edit']['maxage'];?></label> <label class="control-label col-sm-2" for="maxage"><?=$lang['edit']['maxage'];?></label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" name="maxage" id="maxage" min="0" max="32000" value="0"> <input type="number" class="form-control" name="maxage" id="maxage" min="0" max="32000" value="0">
<small class="help-block">0-32000</small>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -112,11 +115,12 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
</div> </div>
</div><!-- add sync job modal --> </div><!-- add sync job modal -->
<!-- log modal --> <!-- log modal -->
<div class="modal fade" id="logModal" tabindex="-1" role="dialog" aria-labelledby="logTextLabel"> <div class="modal fade" id="syncjobLogModal" tabindex="-1" role="dialog" aria-labelledby="syncjobLogModalLabel">
<div class="modal-dialog" style="width:90%" role="document"> <div class="modal-dialog modal-lg" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"><h4 class="modal-title">Log</h4></div>
<div class="modal-body"> <div class="modal-body">
<span id="logText"></span> <textarea class="form-control" rows="20" id="logText" spellcheck="false"></textarea>
</div> </div>
</div> </div>
</div> </div>
@ -156,4 +160,18 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
</div> </div>
</div> </div>
</div> </div>
</div><!-- pw change modal --> </div><!-- pw change modal -->
<!-- sieve filter modal -->
<div class="modal fade" id="userFilterModal" tabindex="-1" role="dialog" aria-labelledby="pwChangeModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span></button>
<h3 class="modal-title"><?=$lang['user']['active_sieve'];?></h3>
</div>
<div class="modal-body">
<pre id="user_sieve_filter"></pre>
</div>
</div>
</div>
</div><!-- sieve filter modal -->

View File

@ -94,6 +94,13 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
</div> </div>
</div> </div>
<hr> <hr>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"> <span class="glyphicon glyphicon-filter"></span></div>
<div class="col-md-9 col-xs-7">
<p><a href="#userFilterModal" data-toggle="modal">[Show active user sieve filter]</a></p>
</div>
</div>
<hr>
<?php // Get user information about aliases <?php // Get user information about aliases
$user_get_alias_details = user_get_alias_details($username); $user_get_alias_details = user_get_alias_details($username);
?> ?>

View File

@ -92,7 +92,7 @@ services:
- rspamd - rspamd
php-fpm-mailcow: php-fpm-mailcow:
image: mailcow/phpfpm:1.4 image: mailcow/phpfpm:1.5
build: ./data/Dockerfiles/phpfpm build: ./data/Dockerfiles/phpfpm
command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
depends_on: depends_on:
@ -143,8 +143,10 @@ services:
- sogo - sogo
dovecot-mailcow: dovecot-mailcow:
image: mailcow/dovecot:1.9 image: mailcow/dovecot:1.12
build: ./data/Dockerfiles/dovecot build: ./data/Dockerfiles/dovecot
cap_add:
- NET_BIND_SERVICE
volumes: volumes:
- ./data/conf/dovecot:/usr/local/etc/dovecot - ./data/conf/dovecot:/usr/local/etc/dovecot
- ./data/assets/ssl:/etc/ssl/mail/:ro - ./data/assets/ssl:/etc/ssl/mail/:ro
@ -222,6 +224,7 @@ services:
envsubst < /etc/nginx/conf.d/templates/server_name.template > /etc/nginx/conf.d/server_name.active && envsubst < /etc/nginx/conf.d/templates/server_name.template > /etc/nginx/conf.d/server_name.active &&
nginx -qt && nginx -qt &&
until ping phpfpm -c1 > /dev/null; do sleep 1; done && until ping phpfpm -c1 > /dev/null; do sleep 1; done &&
until ping sogo -c1 > /dev/null; do sleep 1; done &&
until ping redis -c1 > /dev/null; do sleep 1; done && until ping redis -c1 > /dev/null; do sleep 1; done &&
exec nginx -g 'daemon off;'" exec nginx -g 'daemon off;'"
environment: environment:
@ -249,7 +252,7 @@ services:
depends_on: depends_on:
- nginx-mailcow - nginx-mailcow
- mysql-mailcow - mysql-mailcow
image: mailcow/acme:1.22 image: mailcow/acme:1.23
build: ./data/Dockerfiles/acme build: ./data/Dockerfiles/acme
dns: dns:
- 172.22.1.254 - 172.22.1.254
@ -272,7 +275,7 @@ services:
- acme - acme
fail2ban-mailcow: fail2ban-mailcow:
image: mailcow/fail2ban:1.7 image: mailcow/fail2ban:1.8
build: ./data/Dockerfiles/fail2ban build: ./data/Dockerfiles/fail2ban
stop_grace_period: 30s stop_grace_period: 30s
depends_on: depends_on:
@ -293,7 +296,7 @@ services:
- /lib/modules:/lib/modules:ro - /lib/modules:/lib/modules:ro
watchdog-mailcow: watchdog-mailcow:
image: mailcow/watchdog:1.9 image: mailcow/watchdog:1.10
build: ./data/Dockerfiles/watchdog build: ./data/Dockerfiles/watchdog
volumes: volumes:
- vmail-vol-1:/vmail:ro - vmail-vol-1:/vmail:ro
@ -312,9 +315,10 @@ services:
- watchdog - watchdog
dockerapi-mailcow: dockerapi-mailcow:
image: mailcow/dockerapi:1.1 image: mailcow/dockerapi:1.2
stop_grace_period: 3s restart: always
build: ./data/Dockerfiles/dockerapi build: ./data/Dockerfiles/dockerapi
oom_score_adj: -10
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro - /var/run/docker.sock:/var/run/docker.sock:ro
networks: networks:

View File

@ -13,7 +13,7 @@ if [[ -f mailcow.conf ]]; then
fi fi
if [ -z "$MAILCOW_HOSTNAME" ]; then if [ -z "$MAILCOW_HOSTNAME" ]; then
read -p "Hostname (FQDN): " -ei "mx.example.org" MAILCOW_HOSTNAME read -p "Hostname (FQDN - example.org is not a valid FQDN): " -ei "mx.example.org" MAILCOW_HOSTNAME
fi fi
if [[ -a /etc/timezone ]]; then if [[ -a /etc/timezone ]]; then

View File

@ -0,0 +1,116 @@
#!/bin/bash
[[ -z ${1} ]] && { echo "No parameters given"; exit 1; }
for bin in curl dirmngr; do
if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi
done
while [ "$1" != '' ]; do
case "${1}" in
-p|--purge) NC_PURGE=y && shift;;
-i|--install) NC_INSTALL=y && shift;;
*) echo "Unknown parameter: ${1}" && shift;;
esac
done
[[ ${NC_PURGE} == "y" ]] && [[ ${NC_INSTALL} == "y" ]] && { echo "Cannot use -p and -i at the same time"; }
source ./mailcow.conf
if [[ ${NC_PURGE} == "y" ]]; then
docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e \
"$(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "SELECT GROUP_CONCAT('DROP TABLE ', TABLE_SCHEMA, '.', TABLE_NAME SEPARATOR ';') FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'nc_%' AND TABLE_SCHEMA = '${DBNAME}';" -BN)"
docker exec -it $(docker ps -f name=redis-mailcow -q) /bin/sh -c 'redis-cli KEYS "*nextcloud*" | xargs redis-cli DEL'
if [ -d ./data/web/nextcloud/config ]; then
mv ./data/web/nextcloud/config/ ./data/conf/nextcloud-config-folder-$(date +%s).bak
fi
[[ -d ./data/web/nextcloud ]] && rm -rf ./data/web/nextcloud
[[ -f ./data/conf/nginx/site.nextcloud.custom ]] && mv ./data/conf/nginx/site.nextcloud.custom ./data/conf/nginx/site.nextcloud.custom-$(date +%s).bak
[[ -f ./data/conf/nginx/nextcloud.conf ]] && mv ./data/conf/nginx/nextcloud.conf ./data/conf/nginx/nextcloud.conf-$(date +%s).bak
docker-compose restart nginx-mailcow
elif [[ ${NC_INSTALL} == "y" ]]; then
NC_TYPE=
while [[ ! ${NC_TYPE} =~ ^subfolder$|^subdomain$ ]]; do
read -p "Configure as subdomain or subfolder? [subdomain/subfolder] " NC_TYPE
done
if [[ ${NC_TYPE} == "subdomain" ]]; then
NC_SUBD=
while [[ -z ${NC_SUBD} ]]; do
read -p "Which subdomain? [format: nextcloud.domain.tld] " NC_SUBD
done
if ! ping -q -c2 ${NC_SUBD} > /dev/null 2>&1 ; then
read -p "Cannot ping subdomain, continue anyway? [y|N] " NC_CONT_FAIL
[[ ! ${NC_CONT_FAIL,,} =~ ^(yes|y)$ ]] && { echo "Ok, exiting..."; exit 1; }
fi
fi
ADMIN_NC_PASS=$(</dev/urandom tr -dc A-Za-z0-9 | head -c 28)
NEXTCLOUD_VERSION=$(curl -s https://www.servercow.de/nextcloud/latest.php)
[[ -z ${NEXTCLOUD_VERSION} ]] && { echo "Error, cannot determine nextcloud version, exiting..."; exit 1; }
curl -L# -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/nextcloud-${NEXTCLOUD_VERSION}.tar.bz2" \
&& curl -L# -o nextcloud.tar.bz2.asc "https://download.nextcloud.com/server/releases/nextcloud-${NEXTCLOUD_VERSION}.tar.bz2.asc" \
&& export GNUPGHOME="$(mktemp -d)" \
&& gpg --keyserver ha.pool.sks-keyservers.net --recv-keys 28806A878AE423A28372792ED75899B9A724937A \
&& gpg --batch --verify nextcloud.tar.bz2.asc nextcloud.tar.bz2 \
&& rm -r "$GNUPGHOME" nextcloud.tar.bz2.asc \
&& tar -xjf nextcloud.tar.bz2 -C ./data/web/ \
&& rm nextcloud.tar.bz2 \
&& rm -rf ./data/web/nextcloud/updater \
&& mkdir -p ./data/web/nextcloud/data \
&& mkdir -p ./data/web/nextcloud/custom_apps \
&& chmod +x ./data/web/nextcloud/occ
docker exec -it $(docker ps -f name=php-fpm-mailcow -q) /bin/bash -c "chown -R www-data:www-data /web/nextcloud/data /web/nextcloud/config /web/nextcloud/apps /web/nextcloud/custom_apps"
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ maintenance:install \
--database mysql \
--database-host mysql \
--database-name ${DBNAME} \
--database-user ${DBUSER} \
--database-pass ${DBPASS} \
--database-table-prefix nc_ \
--admin-user admin \
--admin-pass ${ADMIN_NC_PASS} \
--data-dir /web/nextcloud/data
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ config:system:set redis host --value=redis --type=string; \
/web/nextcloud/occ config:system:set redis port --value=6379 --type=integer; \
/web/nextcloud/occ config:system:set memcache.locking --value='\OC\Memcache\Redis' --type=string; \
/web/nextcloud/occ config:system:set memcache.local --value='\OC\Memcache\Redis' --type=string; \
/web/nextcloud/occ config:system:set trusted_proxies 0 --value=fd4d:6169:6c63:6f77::1; \
/web/nextcloud/occ config:system:set trusted_proxies 1 --value=172.22.1.0/24; \
/web/nextcloud/occ config:system:set overwritewebroot --value=/nextcloud; \
/web/nextcloud/occ config:system:set overwritehost --value=${MAILCOW_HOSTNAME}; \
/web/nextcloud/occ config:system:set mail_smtpmode --value=smtp; \
/web/nextcloud/occ config:system:set mail_smtpauthtype --value=LOGIN; \
/web/nextcloud/occ config:system:set mail_from_address --value=nextcloud; \
/web/nextcloud/occ config:system:set mail_domain --value=${MAILCOW_HOSTNAME}; \
/web/nextcloud/occ config:system:set mail_smtphost --value=postfix; \
/web/nextcloud/occ config:system:set mail_smtpport --value=588
/web/nextcloud/occ app:enable user_external
/web/nextcloud/occ config:system:set user_backends 0 arguments 0 --value={dovecot:143/imap/tls/novalidate-cert}
/web/nextcloud/occ config:system:set user_backends 0 class --value=OC_User_IMAP"
if [[ ${NC_TYPE} == "subdomain" ]]; then
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ config:system:set overwritewebroot --value=/
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ config:system:set overwritehost --value=nextcloud.develcow.de
cp ./data/assets/nextcloud/nextcloud.conf ./data/conf/nginx/
sed -i 's/NC_SUBD/${NC_SUBD}/g' ./data/conf/nginx/nextcloud.conf
elif [[ ${NC_TYPE} == "subfolder" ]]; then
cp ./data/assets/nextcloud/site.nextcloud.custom ./data/conf/nginx/
fi
docker-compose restart nginx-mailcow
echo "Login as admin with password: ${ADMIN_NC_PASS}"
fi