commit
						9633a34f9f
					
				@ -1,33 +1,30 @@
 | 
				
			|||||||
FROM ubuntu:xenial
 | 
					FROM debian:stretch-slim
 | 
				
			||||||
 | 
					#ubuntu:xenial
 | 
				
			||||||
MAINTAINER Andre Peters <andre.peters@servercow.de>
 | 
					MAINTAINER Andre Peters <andre.peters@servercow.de>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENV DEBIAN_FRONTEND noninteractive
 | 
					ENV DEBIAN_FRONTEND noninteractive
 | 
				
			||||||
ENV LC_ALL C
 | 
					ENV LC_ALL C
 | 
				
			||||||
 | 
					ENV DOVECOT_VERSION 2.2.28
 | 
				
			||||||
 | 
					ENV PIGEONHOLE_VERSION 0.4.17
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN dpkg-divert --local --rename --add /sbin/initctl \
 | 
					RUN apt-get update \
 | 
				
			||||||
    && ln -sf /bin/true /sbin/initctl \
 | 
						&& apt-get -y install libpam-dev \
 | 
				
			||||||
    && dpkg-divert --local --rename --add /usr/bin/ischroot \
 | 
						default-libmysqlclient-dev \
 | 
				
			||||||
    && ln -sf /bin/true /usr/bin/ischroot
 | 
						lzma-dev \
 | 
				
			||||||
 | 
						liblz-dev \
 | 
				
			||||||
RUN apt-get update
 | 
						libbz2-dev \
 | 
				
			||||||
RUN apt-get -y install dovecot-common \
 | 
						liblz4-dev \
 | 
				
			||||||
	dovecot-core \
 | 
						liblzma-dev \
 | 
				
			||||||
	dovecot-imapd \
 | 
						build-essential \
 | 
				
			||||||
	dovecot-lmtpd \
 | 
						autotools-dev \
 | 
				
			||||||
	dovecot-managesieved \
 | 
						automake \
 | 
				
			||||||
	dovecot-sieve \
 | 
					 | 
				
			||||||
	dovecot-mysql \
 | 
					 | 
				
			||||||
	dovecot-pop3d \
 | 
					 | 
				
			||||||
	dovecot-dev \
 | 
					 | 
				
			||||||
	syslog-ng \
 | 
						syslog-ng \
 | 
				
			||||||
	syslog-ng-core \
 | 
						syslog-ng-core \
 | 
				
			||||||
	ca-certificates \
 | 
						ca-certificates \
 | 
				
			||||||
	supervisor \
 | 
						supervisor \
 | 
				
			||||||
	wget \
 | 
						wget \
 | 
				
			||||||
	curl \
 | 
						curl \
 | 
				
			||||||
	build-essential \
 | 
						libssl-dev \
 | 
				
			||||||
	autotools-dev \
 | 
					 | 
				
			||||||
	automake \
 | 
					 | 
				
			||||||
	libauthen-ntlm-perl \
 | 
						libauthen-ntlm-perl \
 | 
				
			||||||
	libcrypt-ssleay-perl \
 | 
						libcrypt-ssleay-perl \
 | 
				
			||||||
	libdigest-hmac-perl \
 | 
						libdigest-hmac-perl \
 | 
				
			||||||
@ -52,36 +49,57 @@ RUN apt-get -y install dovecot-common \
 | 
				
			|||||||
	make \
 | 
						make \
 | 
				
			||||||
	cpanminus
 | 
						cpanminus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN wget https://www.dovecot.org/releases/2.2/dovecot-$DOVECOT_VERSION.tar.gz -O - | tar xvz  \
 | 
				
			||||||
 | 
						&& cd dovecot-$DOVECOT_VERSION \
 | 
				
			||||||
 | 
						&& ./configure --with-mysql --with-lzma --with-lz4 --with-ssl=openssl --with-notify=inotify --with-storages=mdbox,sdbox,maildir,mbox,imapc,pop3c --with-bzlib --with-zlib \
 | 
				
			||||||
 | 
						&& make -j3 \
 | 
				
			||||||
 | 
						&& make install \
 | 
				
			||||||
 | 
						&& make clean
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN wget https://pigeonhole.dovecot.org/releases/2.2/dovecot-2.2-pigeonhole-$PIGEONHOLE_VERSION.tar.gz -O - | tar xvz  \
 | 
				
			||||||
 | 
						&& cd dovecot-2.2-pigeonhole-$PIGEONHOLE_VERSION \
 | 
				
			||||||
 | 
						&& ./configure \
 | 
				
			||||||
 | 
						&& make -j3 \
 | 
				
			||||||
 | 
						&& make install \
 | 
				
			||||||
 | 
						&& make clean
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN sed -i -E 's/^(\s*)system\(\);/\1unix-stream("\/dev\/log");/' /etc/syslog-ng/syslog-ng.conf
 | 
					RUN sed -i -E 's/^(\s*)system\(\);/\1unix-stream("\/dev\/log");/' /etc/syslog-ng/syslog-ng.conf
 | 
				
			||||||
RUN cpanm Data::Uniqid Mail::IMAPClient String::Util
 | 
					RUN cpanm Data::Uniqid Mail::IMAPClient String::Util
 | 
				
			||||||
RUN echo '* * * * *   root   /usr/local/bin/imapsync_cron.pl' > /etc/cron.d/imapsync
 | 
					RUN echo '* * * * *   root   /usr/local/bin/imapsync_cron.pl' > /etc/cron.d/imapsync
 | 
				
			||||||
RUN echo '30 3 * * *   vmail  /usr/bin/doveadm quota recalc -A' > /etc/cron.d/dovecot-sync
 | 
					RUN echo '30 3 * * *   vmail  /usr/bin/doveadm quota recalc -A' > /etc/cron.d/dovecot-sync
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WORKDIR /tmp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN wget http://hg.dovecot.org/dovecot-antispam-plugin/archive/tip.tar.gz -O - | tar xvz  \
 | 
					 | 
				
			||||||
	&& cd /tmp/dovecot-antispam* \
 | 
					 | 
				
			||||||
	&& ./autogen.sh \
 | 
					 | 
				
			||||||
	&& ./configure --prefix=/usr \
 | 
					 | 
				
			||||||
	&& make \
 | 
					 | 
				
			||||||
	&& make install
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
COPY ./imapsync /usr/local/bin/imapsync
 | 
					COPY ./imapsync /usr/local/bin/imapsync
 | 
				
			||||||
COPY ./postlogin.sh /usr/local/bin/postlogin.sh
 | 
					COPY ./postlogin.sh /usr/local/bin/postlogin.sh
 | 
				
			||||||
COPY ./imapsync_cron.pl /usr/local/bin/imapsync_cron.pl
 | 
					COPY ./imapsync_cron.pl /usr/local/bin/imapsync_cron.pl
 | 
				
			||||||
COPY ./rspamd-pipe /usr/local/bin/rspamd-pipe
 | 
					COPY ./report-spam.sieve /usr/local/lib/dovecot/sieve/report-spam.sieve
 | 
				
			||||||
 | 
					COPY ./report-ham.sieve /usr/local/lib/dovecot/sieve/report-ham.sieve
 | 
				
			||||||
 | 
					COPY ./rspamd-pipe-ham /usr/local/lib/dovecot/sieve/rspamd-pipe-ham
 | 
				
			||||||
 | 
					COPY ./rspamd-pipe-spam /usr/local/lib/dovecot/sieve/rspamd-pipe-spam
 | 
				
			||||||
COPY ./docker-entrypoint.sh /
 | 
					COPY ./docker-entrypoint.sh /
 | 
				
			||||||
COPY ./supervisord.conf /etc/supervisor/supervisord.conf
 | 
					COPY ./supervisord.conf /etc/supervisor/supervisord.conf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN chmod +x /usr/local/bin/rspamd-pipe
 | 
					RUN chmod +x /usr/local/lib/dovecot/sieve/rspamd-pipe-ham \
 | 
				
			||||||
RUN chmod +x /usr/local/bin/imapsync_cron.pl
 | 
						/usr/local/lib/dovecot/sieve/rspamd-pipe-spam \
 | 
				
			||||||
 | 
						/usr/local/bin/imapsync_cron.pl \
 | 
				
			||||||
 | 
						/usr/local/bin/postlogin.sh \
 | 
				
			||||||
 | 
						/usr/local/bin/imapsync
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN groupadd -g 5000 vmail
 | 
					RUN groupadd -g 5000 vmail \
 | 
				
			||||||
RUN useradd -g vmail -u 5000 vmail -d /var/vmail
 | 
						&& groupadd -g 401 dovecot \
 | 
				
			||||||
 | 
					    && groupadd -g 402 dovenull \
 | 
				
			||||||
 | 
						&& useradd -g vmail -u 5000 vmail -d /var/vmail \
 | 
				
			||||||
 | 
						&& useradd -c "Dovecot unprivileged user" -d /dev/null -u 401 -g dovecot -s /bin/false dovecot \
 | 
				
			||||||
 | 
						&& useradd -c "Dovecot login user" -d /dev/null -u 402 -g dovenull -s /bin/false dovenull
 | 
				
			||||||
 | 
					
 | 
				
			||||||
EXPOSE 24 10001
 | 
					EXPOSE 24 10001
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENTRYPOINT ["/docker-entrypoint.sh"]
 | 
					ENTRYPOINT ["/docker-entrypoint.sh"]
 | 
				
			||||||
CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
 | 
					CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
 | 
					RUN apt-get clean \
 | 
				
			||||||
 | 
						&& rm -rf /var/lib/apt/lists/* \
 | 
				
			||||||
 | 
						/tmp/* \
 | 
				
			||||||
 | 
						/var/tmp/* \
 | 
				
			||||||
 | 
						/dovecot-2.2-pigeonhole-$PIGEONHOLE_VERSION \
 | 
				
			||||||
 | 
						/dovecot-$DOVECOT_VERSION
 | 
				
			||||||
 | 
				
			|||||||
@ -6,12 +6,16 @@ sed -i "/^\$DBUSER/c\\\$DBUSER='${DBUSER}';" /usr/local/bin/imapsync_cron.pl
 | 
				
			|||||||
sed -i "/^\$DBPASS/c\\\$DBPASS='${DBPASS}';" /usr/local/bin/imapsync_cron.pl
 | 
					sed -i "/^\$DBPASS/c\\\$DBPASS='${DBPASS}';" /usr/local/bin/imapsync_cron.pl
 | 
				
			||||||
sed -i "/^\$DBNAME/c\\\$DBNAME='${DBNAME}';" /usr/local/bin/imapsync_cron.pl
 | 
					sed -i "/^\$DBNAME/c\\\$DBNAME='${DBNAME}';" /usr/local/bin/imapsync_cron.pl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[ ! -d /etc/dovecot/sql/ ]] && mkdir -p /etc/dovecot/sql/
 | 
					# Create missing directories
 | 
				
			||||||
 | 
					[[ ! -d /usr/local/etc/dovecot/sql/ ]] && mkdir -p /usr/local/etc/dovecot/sql/
 | 
				
			||||||
 | 
					[[ ! -d /var/vmail/sieve ]] && mkdir -p /var/vmail/sieve
 | 
				
			||||||
 | 
					[[ ! -d /etc/sogo ]] && mkdir -p /etc/sogo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Set Dovecot sql config parameters, escape " in db password
 | 
					# Set Dovecot sql config parameters, escape " in db password
 | 
				
			||||||
DBPASS=$(echo ${DBPASS} | sed 's/"/\\"/g')
 | 
					DBPASS=$(echo ${DBPASS} | sed 's/"/\\"/g')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cat <<EOF > /etc/dovecot/sql/dovecot-dict-sql.conf
 | 
					# Create quota dict for Dovecot
 | 
				
			||||||
 | 
					cat <<EOF > /usr/local/etc/dovecot/sql/dovecot-dict-sql.conf
 | 
				
			||||||
connect = "host=mysql dbname=${DBNAME} user=${DBNAME} password=${DBPASS}"
 | 
					connect = "host=mysql dbname=${DBNAME} user=${DBNAME} password=${DBPASS}"
 | 
				
			||||||
map {
 | 
					map {
 | 
				
			||||||
  pattern = priv/quota/storage
 | 
					  pattern = priv/quota/storage
 | 
				
			||||||
@ -27,7 +31,8 @@ map {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
EOF
 | 
					EOF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cat <<EOF > /etc/dovecot/sql/dovecot-mysql.conf
 | 
					# Create user and pass dict for Dovecot
 | 
				
			||||||
 | 
					cat <<EOF > /usr/local/etc/dovecot/sql/dovecot-mysql.conf
 | 
				
			||||||
driver = mysql
 | 
					driver = mysql
 | 
				
			||||||
connect = "host=mysql dbname=${DBNAME} user=${DBNAME} password=${DBPASS}"
 | 
					connect = "host=mysql dbname=${DBNAME} user=${DBNAME} password=${DBPASS}"
 | 
				
			||||||
default_pass_scheme = SSHA256
 | 
					default_pass_scheme = SSHA256
 | 
				
			||||||
@ -36,19 +41,35 @@ user_query = SELECT CONCAT('maildir:/var/vmail/',maildir) AS mail, 5000 AS uid,
 | 
				
			|||||||
iterate_query = SELECT username FROM mailbox WHERE active='1';
 | 
					iterate_query = SELECT username FROM mailbox WHERE active='1';
 | 
				
			||||||
EOF
 | 
					EOF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[ ! -d /var/vmail/sieve ]] && mkdir -p /var/vmail/sieve
 | 
					# Create global sieve_after script
 | 
				
			||||||
[[ ! -d /etc/sogo ]] && mkdir -p /etc/sogo
 | 
					cat /usr/local/etc/dovecot/sieve_after > /var/vmail/sieve/global.sieve
 | 
				
			||||||
cat /etc/dovecot/sieve_after > /var/vmail/sieve/global.sieve
 | 
					 | 
				
			||||||
sievec /var/vmail/sieve/global.sieve
 | 
					 | 
				
			||||||
chown -R vmail:vmail /var/vmail/sieve
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Check permissions of vmail directory.
 | 
				
			||||||
# Do not do this every start-up, it may take a very long time. So we use a stat check here.
 | 
					# Do not do this every start-up, it may take a very long time. So we use a stat check here.
 | 
				
			||||||
if [[ $(stat -c %U /var/vmail/) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail ; fi
 | 
					if [[ $(stat -c %U /var/vmail/) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail ; fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Create random master for SOGo sieve features
 | 
					# Create random master for SOGo sieve features
 | 
				
			||||||
RAND_USER=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 16 | head -n 1)
 | 
					RAND_USER=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 16 | head -n 1)
 | 
				
			||||||
RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 24 | head -n 1)
 | 
					RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 24 | head -n 1)
 | 
				
			||||||
echo ${RAND_USER}:$(doveadm pw -s SHA1 -p ${RAND_PASS}) > /etc/dovecot/dovecot-master.passwd
 | 
					echo ${RAND_USER}:$(doveadm pw -s SHA1 -p ${RAND_PASS}) > /usr/local/etc/dovecot/dovecot-master.passwd
 | 
				
			||||||
echo ${RAND_USER}:${RAND_PASS} > /etc/sogo/sieve.creds
 | 
					echo ${RAND_USER}:${RAND_PASS} > /etc/sogo/sieve.creds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 401 is user dovecot
 | 
				
			||||||
 | 
					if [[ ! -f /mail_crypt/ecprivkey.pem || ! -f /mail_crypt/ecpubkey.pem ]]; then
 | 
				
			||||||
 | 
						openssl ecparam -name prime256v1 -genkey | openssl pkey -out /mail_crypt/ecprivkey.pem
 | 
				
			||||||
 | 
						openssl pkey -in /mail_crypt/ecprivkey.pem -pubout -out /mail_crypt/ecpubkey.pem
 | 
				
			||||||
 | 
						chown 401 /mail_crypt/ecprivkey.pem /mail_crypt/ecpubkey.pem
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
						chown 401 /mail_crypt/ecprivkey.pem /mail_crypt/ecpubkey.pem
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Compile sieve scripts
 | 
				
			||||||
 | 
					sievec /var/vmail/sieve/global.sieve
 | 
				
			||||||
 | 
					sievec /usr/local/lib/dovecot/sieve/report-spam.sieve
 | 
				
			||||||
 | 
					sievec /usr/local/lib/dovecot/sieve/report-ham.sieve
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Fix permissions
 | 
				
			||||||
 | 
					chown -R vmail:vmail /var/vmail/sieve
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
exec "$@"
 | 
					exec "$@"
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,7 @@ open my $file, '<', "/etc/sogo/sieve.creds";
 | 
				
			|||||||
my $creds = <$file>; 
 | 
					my $creds = <$file>; 
 | 
				
			||||||
close $file;
 | 
					close $file;
 | 
				
			||||||
my ($master_user, $master_pass) = split /:/, $creds;
 | 
					my ($master_user, $master_pass) = split /:/, $creds;
 | 
				
			||||||
my $sth = $dbh->prepare("SELECT id, user1, user2, host1, authmech1, password1, exclude, port1, enc1, delete2duplicates, maxage, subfolder2 FROM imapsync WHERE active = 1 AND (UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(last_run) > mins_interval * 60 OR last_run IS NULL)");
 | 
					my $sth = $dbh->prepare("SELECT id, user1, user2, host1, authmech1, password1, exclude, port1, enc1, delete2duplicates, maxage, subfolder2, delete1 FROM imapsync WHERE active = 1 AND (UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(last_run) > mins_interval * 60 OR last_run IS NULL)");
 | 
				
			||||||
$sth->execute();
 | 
					$sth->execute();
 | 
				
			||||||
my $row;
 | 
					my $row;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -39,6 +39,7 @@ while ($row = $sth->fetchrow_arrayref()) {
 | 
				
			|||||||
  $delete2duplicates  = @$row[9];
 | 
					  $delete2duplicates  = @$row[9];
 | 
				
			||||||
  $maxage             = @$row[10];
 | 
					  $maxage             = @$row[10];
 | 
				
			||||||
  $subfolder2         = @$row[11];
 | 
					  $subfolder2         = @$row[11];
 | 
				
			||||||
 | 
					  $delete1            = @$row[12];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if ($enc1 eq "TLS") { $enc1 = "--tls1"; } elsif ($enc1 eq "SSL") { $enc1 = "--ssl1"; } else { undef $enc1; }
 | 
					  if ($enc1 eq "TLS") { $enc1 = "--tls1"; } elsif ($enc1 eq "SSL") { $enc1 = "--ssl1"; } else { undef $enc1; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -50,6 +51,7 @@ while ($row = $sth->fetchrow_arrayref()) {
 | 
				
			|||||||
	($subfolder2 eq ""	? () : ('--subfolder2', $subfolder2)),
 | 
						($subfolder2 eq ""	? () : ('--subfolder2', $subfolder2)),
 | 
				
			||||||
	($maxage eq "0"	? () : ('--maxage', $maxage)),
 | 
						($maxage eq "0"	? () : ('--maxage', $maxage)),
 | 
				
			||||||
	($delete2duplicates	ne "1"	? () : ('--delete2duplicates')),
 | 
						($delete2duplicates	ne "1"	? () : ('--delete2duplicates')),
 | 
				
			||||||
 | 
						($delete1	ne "1"	? () : ('--delete')),
 | 
				
			||||||
	(!defined($enc1) ? () : ($enc1)),
 | 
						(!defined($enc1) ? () : ($enc1)),
 | 
				
			||||||
	"--host1", $host1,
 | 
						"--host1", $host1,
 | 
				
			||||||
	"--user1", $user1,
 | 
						"--user1", $user1,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										11
									
								
								data/Dockerfiles/dovecot/report-ham.sieve
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								data/Dockerfiles/dovecot/report-ham.sieve
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if environment :matches "imap.mailbox" "*" {
 | 
				
			||||||
 | 
					  set "mailbox" "${1}";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if string "${mailbox}" "Trash" {
 | 
				
			||||||
 | 
					  stop;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pipe :copy "rspamd-pipe-ham";
 | 
				
			||||||
							
								
								
									
										3
									
								
								data/Dockerfiles/dovecot/report-spam.sieve
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								data/Dockerfiles/dovecot/report-spam.sieve
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					require ["vnd.dovecot.pipe", "copy"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pipe :copy "rspamd-pipe-spam";
 | 
				
			||||||
@ -1,8 +0,0 @@
 | 
				
			|||||||
#!/bin/bash
 | 
					 | 
				
			||||||
if [[ ${2} == "learn_spam" ]]; then
 | 
					 | 
				
			||||||
/usr/bin/curl --data-binary @- http://rspamd:11334/learnspam < /dev/stdin
 | 
					 | 
				
			||||||
elif [[ ${2} == "learn_ham" ]]; then
 | 
					 | 
				
			||||||
/usr/bin/curl --data-binary @- http://rspamd:11334/learnham < /dev/stdin
 | 
					 | 
				
			||||||
fi
 | 
					 | 
				
			||||||
# Always return 0 to satisfy Dovecot...
 | 
					 | 
				
			||||||
exit 0
 | 
					 | 
				
			||||||
							
								
								
									
										4
									
								
								data/Dockerfiles/dovecot/rspamd-pipe-ham
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										4
									
								
								data/Dockerfiles/dovecot/rspamd-pipe-ham
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					/usr/bin/curl -s --data-binary @- http://rspamd:11334/learnham < /dev/stdin
 | 
				
			||||||
 | 
					# Always return 0 to satisfy Dovecot...
 | 
				
			||||||
 | 
					exit 0
 | 
				
			||||||
							
								
								
									
										4
									
								
								data/Dockerfiles/dovecot/rspamd-pipe-spam
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										4
									
								
								data/Dockerfiles/dovecot/rspamd-pipe-spam
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					/usr/bin/curl -s --data-binary @- http://rspamd:11334/learnspam < /dev/stdin
 | 
				
			||||||
 | 
					# Always return 0 to satisfy Dovecot...
 | 
				
			||||||
 | 
					exit 0
 | 
				
			||||||
@ -8,7 +8,7 @@ autostart=true
 | 
				
			|||||||
stdout_syslog=true
 | 
					stdout_syslog=true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[program:dovecot]
 | 
					[program:dovecot]
 | 
				
			||||||
command=/usr/sbin/dovecot -F
 | 
					command=/usr/local/sbin/dovecot -F
 | 
				
			||||||
autorestart=true
 | 
					autorestart=true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[program:logfiles]
 | 
					[program:logfiles]
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
FROM ubuntu:xenial
 | 
					FROM debian:testing-slim
 | 
				
			||||||
MAINTAINER Andre Peters <andre.peters@servercow.de>
 | 
					MAINTAINER Andre Peters <andre.peters@servercow.de>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENV DEBIAN_FRONTEND noninteractive
 | 
					ENV DEBIAN_FRONTEND noninteractive
 | 
				
			||||||
@ -19,10 +19,19 @@ RUN apt-get install -y --no-install-recommends supervisor \
 | 
				
			|||||||
	postfix-pcre \
 | 
						postfix-pcre \
 | 
				
			||||||
	syslog-ng \
 | 
						syslog-ng \
 | 
				
			||||||
	syslog-ng-core \
 | 
						syslog-ng-core \
 | 
				
			||||||
	ca-certificates
 | 
						ca-certificates \
 | 
				
			||||||
 | 
						gnupg \
 | 
				
			||||||
 | 
						python-gpgme \
 | 
				
			||||||
 | 
						sudo \
 | 
				
			||||||
 | 
						dirmngr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN addgroup --system --gid 600 zeyple
 | 
				
			||||||
 | 
					RUN adduser --system --home /var/lib/zeyple --no-create-home --uid 600 --gid 600 --disabled-login zeyple
 | 
				
			||||||
 | 
					RUN touch /var/log/zeyple.log && chown zeyple: /var/log/zeyple.log
 | 
				
			||||||
RUN sed -i -E 's/^(\s*)system\(\);/\1unix-stream("\/dev\/log");/' /etc/syslog-ng/syslog-ng.conf
 | 
					RUN sed -i -E 's/^(\s*)system\(\);/\1unix-stream("\/dev\/log");/' /etc/syslog-ng/syslog-ng.conf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY zeyple.py /usr/local/bin/zeyple.py
 | 
				
			||||||
 | 
					COPY zeyple.conf /etc/zeyple.conf
 | 
				
			||||||
COPY supervisord.conf /etc/supervisor/supervisord.conf
 | 
					COPY supervisord.conf /etc/supervisor/supervisord.conf
 | 
				
			||||||
COPY postfix.sh /opt/postfix.sh
 | 
					COPY postfix.sh /opt/postfix.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,7 @@ user = ${DBUSER}
 | 
				
			|||||||
password = ${DBPASS}
 | 
					password = ${DBPASS}
 | 
				
			||||||
hosts = mysql
 | 
					hosts = mysql
 | 
				
			||||||
dbname = ${DBNAME}
 | 
					dbname = ${DBNAME}
 | 
				
			||||||
query = SELECT IF( EXISTS( SELECT 'TLS_ACTIVE' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.address WHERE (address='%s' OR address IN (SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain='%d')) AND mailbox.tls_enforce_in = '1' AND mailbox.active = '1'), 'reject_plaintext_session', 'DUNNO') AS 'tls_enforce_in';
 | 
					query = SELECT IF( EXISTS( SELECT 'TLS_ACTIVE' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.goto WHERE (address='%s' OR address IN (SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain='%d')) AND mailbox.tls_enforce_in = '1' AND mailbox.active = '1'), 'reject_plaintext_session', NULL) AS 'tls_enforce_in';
 | 
				
			||||||
EOF
 | 
					EOF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cat <<EOF > /opt/postfix/conf/sql/mysql_tls_enforce_out_policy.cf
 | 
					cat <<EOF > /opt/postfix/conf/sql/mysql_tls_enforce_out_policy.cf
 | 
				
			||||||
@ -25,7 +25,7 @@ user = ${DBUSER}
 | 
				
			|||||||
password = ${DBPASS}
 | 
					password = ${DBPASS}
 | 
				
			||||||
hosts = mysql
 | 
					hosts = mysql
 | 
				
			||||||
dbname = ${DBNAME}
 | 
					dbname = ${DBNAME}
 | 
				
			||||||
query = SELECT IF( EXISTS( SELECT 'TLS_ACTIVE' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.address WHERE (address='%s' OR address IN (SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain='%d')) AND mailbox.tls_enforce_out = '1' AND mailbox.active = '1'), 'smtp_enforced_tls:', 'DUNNO') AS 'tls_enforce_out';
 | 
					query = SELECT IF( EXISTS( SELECT 'TLS_ACTIVE' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.goto WHERE (address='%s' OR address IN (SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain='%d')) AND mailbox.tls_enforce_out = '1' AND mailbox.active = '1'), 'smtp_enforced_tls:', NULL) AS 'tls_enforce_out';
 | 
				
			||||||
EOF
 | 
					EOF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_domain_catchall_maps.cf
 | 
					cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_domain_catchall_maps.cf
 | 
				
			||||||
@ -92,11 +92,24 @@ dbname = ${DBNAME}
 | 
				
			|||||||
query = SELECT goto FROM spamalias WHERE address='%s' AND validity >= UNIX_TIMESTAMP()
 | 
					query = SELECT goto FROM spamalias WHERE address='%s' AND validity >= UNIX_TIMESTAMP()
 | 
				
			||||||
EOF
 | 
					EOF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Reset GPG key permissions
 | 
				
			||||||
 | 
					mkdir -p /var/lib/zeyple/keys
 | 
				
			||||||
 | 
					chmod 700 /var/lib/zeyple/keys
 | 
				
			||||||
 | 
					chown -R 600:600 /var/lib/zeyple/keys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Fix Postfix permissions
 | 
				
			||||||
 | 
					chgrp -R postdrop /var/spool/postfix/public
 | 
				
			||||||
 | 
					chgrp -R postdrop /var/spool/postfix/maildrop
 | 
				
			||||||
 | 
					postfix set-permissions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Check Postfix configuration
 | 
				
			||||||
postconf -c /opt/postfix/conf
 | 
					postconf -c /opt/postfix/conf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if [[ $? != 0 ]]; then
 | 
					if [[ $? != 0 ]]; then
 | 
				
			||||||
	echo "Postfix configuration error, refusing to start."
 | 
						echo "Postfix configuration error, refusing to start."
 | 
				
			||||||
	exit 1
 | 
						exit 1
 | 
				
			||||||
else
 | 
					else
 | 
				
			||||||
	postfix -c /opt/postfix/conf start
 | 
						postfix -c /opt/postfix/conf start
 | 
				
			||||||
 | 
						supervisorctl restart postfix-maillog
 | 
				
			||||||
	sleep 126144000
 | 
						sleep 126144000
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,17 @@ command=/opt/postfix.sh
 | 
				
			|||||||
autorestart=true
 | 
					autorestart=true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[program:postfix-maillog]
 | 
					[program:postfix-maillog]
 | 
				
			||||||
command=/usr/bin/tail -f /var/log/mail.log
 | 
					command=/bin/tail -f /var/log/zeyple.log /var/log/mail.log
 | 
				
			||||||
stdout_logfile=/dev/fd/1
 | 
					stdout_logfile=/dev/stdout
 | 
				
			||||||
stdout_logfile_maxbytes=0
 | 
					stdout_logfile_maxbytes=0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[unix_http_server]
 | 
				
			||||||
 | 
					file=/var/tmp/supervisord.sock  
 | 
				
			||||||
 | 
					chmod=0770  
 | 
				
			||||||
 | 
					chown=nobody:nogroup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[supervisorctl]
 | 
				
			||||||
 | 
					serverurl=unix:///var/tmp/supervisord.sock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[rpcinterface:supervisor]
 | 
				
			||||||
 | 
					supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										9
									
								
								data/Dockerfiles/postfix/zeyple.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								data/Dockerfiles/postfix/zeyple.conf
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					[zeyple]
 | 
				
			||||||
 | 
					log_file = /var/log/zeyple.log
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[gpg]
 | 
				
			||||||
 | 
					home = /var/lib/zeyple/keys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[relay]
 | 
				
			||||||
 | 
					host = localhost
 | 
				
			||||||
 | 
					port = 10026
 | 
				
			||||||
							
								
								
									
										274
									
								
								data/Dockerfiles/postfix/zeyple.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										274
									
								
								data/Dockerfiles/postfix/zeyple.py
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,274 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import email
 | 
				
			||||||
 | 
					import email.mime.multipart
 | 
				
			||||||
 | 
					import email.mime.application
 | 
				
			||||||
 | 
					import email.encoders
 | 
				
			||||||
 | 
					import smtplib
 | 
				
			||||||
 | 
					import copy
 | 
				
			||||||
 | 
					from io import BytesIO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    from configparser import SafeConfigParser  # Python 3
 | 
				
			||||||
 | 
					except ImportError:
 | 
				
			||||||
 | 
					    from ConfigParser import SafeConfigParser  # Python 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import gpgme
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Boiler plate to avoid dependency on six
 | 
				
			||||||
 | 
					# BBB: Python 2.7 support
 | 
				
			||||||
 | 
					PY3K = sys.version_info > (3, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def message_from_binary(message):
 | 
				
			||||||
 | 
					    if PY3K:
 | 
				
			||||||
 | 
					        return email.message_from_bytes(message)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return email.message_from_string(message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def as_binary_string(email):
 | 
				
			||||||
 | 
					    if PY3K:
 | 
				
			||||||
 | 
					        return email.as_bytes()
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return email.as_string()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def encode_string(string):
 | 
				
			||||||
 | 
					    if isinstance(string, bytes):
 | 
				
			||||||
 | 
					        return string
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return string.encode('utf-8')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					__title__ = 'Zeyple'
 | 
				
			||||||
 | 
					__version__ = '1.2.0'
 | 
				
			||||||
 | 
					__author__ = 'Cédric Félizard'
 | 
				
			||||||
 | 
					__license__ = 'AGPLv3+'
 | 
				
			||||||
 | 
					__copyright__ = 'Copyright 2012-2016 Cédric Félizard'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Zeyple:
 | 
				
			||||||
 | 
					    """Zeyple Encrypts Your Precious Log Emails"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, config_fname='zeyple.conf'):
 | 
				
			||||||
 | 
					        self.config = self.load_configuration(config_fname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        log_file = self.config.get('zeyple', 'log_file')
 | 
				
			||||||
 | 
					        logging.basicConfig(
 | 
				
			||||||
 | 
					            filename=log_file, level=logging.DEBUG,
 | 
				
			||||||
 | 
					            format='%(asctime)s %(process)s %(levelname)s %(message)s'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        logging.info("Zeyple ready to encrypt outgoing emails")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def load_configuration(self, filename):
 | 
				
			||||||
 | 
					        """Reads and parses the config file"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        config = SafeConfigParser()
 | 
				
			||||||
 | 
					        config.read([
 | 
				
			||||||
 | 
					            os.path.join('/etc/', filename),
 | 
				
			||||||
 | 
					            filename,
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					        if not config.sections():
 | 
				
			||||||
 | 
					            raise IOError('Cannot open config file.')
 | 
				
			||||||
 | 
					        return config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def gpg(self):
 | 
				
			||||||
 | 
					        protocol = gpgme.PROTOCOL_OpenPGP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.config.has_option('gpg', 'executable'):
 | 
				
			||||||
 | 
					            executable = self.config.get('gpg', 'executable')
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            executable = None  # Default value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        home_dir = self.config.get('gpg', 'home')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx = gpgme.Context()
 | 
				
			||||||
 | 
					        ctx.set_engine_info(protocol, executable, home_dir)
 | 
				
			||||||
 | 
					        ctx.armor = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return ctx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def process_message(self, message_data, recipients):
 | 
				
			||||||
 | 
					        """Encrypts the message with recipient keys"""
 | 
				
			||||||
 | 
					        message_data = encode_string(message_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        in_message = message_from_binary(message_data)
 | 
				
			||||||
 | 
					        logging.info(
 | 
				
			||||||
 | 
					            "Processing outgoing message %s", in_message['Message-id'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not recipients:
 | 
				
			||||||
 | 
					            logging.warn("Cannot find any recipients, ignoring")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sent_messages = []
 | 
				
			||||||
 | 
					        for recipient in recipients:
 | 
				
			||||||
 | 
					            logging.info("Recipient: %s", recipient)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            key_id = self._user_key(recipient)
 | 
				
			||||||
 | 
					            logging.info("Key ID: %s", key_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if key_id:
 | 
				
			||||||
 | 
					                out_message = self._encrypt_message(in_message, key_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # Delete Content-Transfer-Encoding if present to default to
 | 
				
			||||||
 | 
					                # "7bit" otherwise Thunderbird seems to hang in some cases.
 | 
				
			||||||
 | 
					                del out_message["Content-Transfer-Encoding"]
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                logging.warn("No keys found, message will be sent unencrypted")
 | 
				
			||||||
 | 
					                out_message = copy.copy(in_message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self._add_zeyple_header(out_message)
 | 
				
			||||||
 | 
					            self._send_message(out_message, recipient)
 | 
				
			||||||
 | 
					            sent_messages.append(out_message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return sent_messages
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_version_part(self):
 | 
				
			||||||
 | 
					        ret = email.mime.application.MIMEApplication(
 | 
				
			||||||
 | 
					            'Version: 1\n',
 | 
				
			||||||
 | 
					            'pgp-encrypted',
 | 
				
			||||||
 | 
					            email.encoders.encode_noop,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        ret.add_header(
 | 
				
			||||||
 | 
					            'Content-Description',
 | 
				
			||||||
 | 
					            "PGP/MIME version identification",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        return ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_encrypted_part(self, payload):
 | 
				
			||||||
 | 
					        ret = email.mime.application.MIMEApplication(
 | 
				
			||||||
 | 
					            payload,
 | 
				
			||||||
 | 
					            'octet-stream',
 | 
				
			||||||
 | 
					            email.encoders.encode_noop,
 | 
				
			||||||
 | 
					            name="encrypted.asc",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        ret.add_header('Content-Description', "OpenPGP encrypted message")
 | 
				
			||||||
 | 
					        ret.add_header(
 | 
				
			||||||
 | 
					            'Content-Disposition',
 | 
				
			||||||
 | 
					            'inline',
 | 
				
			||||||
 | 
					            filename='encrypted.asc',
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        return ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _encrypt_message(self, in_message, key_id):
 | 
				
			||||||
 | 
					        if in_message.is_multipart():
 | 
				
			||||||
 | 
					            # get the body (after the first \n\n)
 | 
				
			||||||
 | 
					            payload = in_message.as_string().split("\n\n", 1)[1].strip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # prepend the Content-Type including the boundary
 | 
				
			||||||
 | 
					            content_type = "Content-Type: " + in_message["Content-Type"]
 | 
				
			||||||
 | 
					            payload = content_type + "\n\n" + payload
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            message = email.message.Message()
 | 
				
			||||||
 | 
					            message.set_payload(payload)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            payload = message.get_payload()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            payload = in_message.get_payload()
 | 
				
			||||||
 | 
					            payload = encode_string(payload)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            quoted_printable = email.charset.Charset('ascii')
 | 
				
			||||||
 | 
					            quoted_printable.body_encoding = email.charset.QP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            message = email.mime.nonmultipart.MIMENonMultipart(
 | 
				
			||||||
 | 
					                'text', 'plain', charset='utf-8'
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            message.set_payload(payload, charset=quoted_printable)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            mixed = email.mime.multipart.MIMEMultipart(
 | 
				
			||||||
 | 
					                'mixed',
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					                [message],
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # remove superfluous header
 | 
				
			||||||
 | 
					            del mixed['MIME-Version']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            payload = as_binary_string(mixed)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        encrypted_payload = self._encrypt_payload(payload, [key_id])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        version = self._get_version_part()
 | 
				
			||||||
 | 
					        encrypted = self._get_encrypted_part(encrypted_payload)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        out_message = copy.copy(in_message)
 | 
				
			||||||
 | 
					        out_message.preamble = "This is an OpenPGP/MIME encrypted " \
 | 
				
			||||||
 | 
					                               "message (RFC 4880 and 3156)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if 'Content-Type' not in out_message:
 | 
				
			||||||
 | 
					            out_message['Content-Type'] = 'multipart/encrypted'
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            out_message.replace_header(
 | 
				
			||||||
 | 
					                'Content-Type',
 | 
				
			||||||
 | 
					                'multipart/encrypted',
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        out_message.set_param('protocol', 'application/pgp-encrypted')
 | 
				
			||||||
 | 
					        out_message.set_payload([version, encrypted])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return out_message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _encrypt_payload(self, payload, key_ids):
 | 
				
			||||||
 | 
					        """Encrypts the payload with the given keys"""
 | 
				
			||||||
 | 
					        payload = encode_string(payload)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        plaintext = BytesIO(payload)
 | 
				
			||||||
 | 
					        ciphertext = BytesIO()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.gpg.armor = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        recipient = [self.gpg.get_key(key_id) for key_id in key_ids]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.gpg.encrypt(recipient, gpgme.ENCRYPT_ALWAYS_TRUST,
 | 
				
			||||||
 | 
					                         plaintext, ciphertext)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return ciphertext.getvalue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _user_key(self, email):
 | 
				
			||||||
 | 
					        """Returns the GPG key for the given email address"""
 | 
				
			||||||
 | 
					        logging.info("Trying to encrypt for %s", email)
 | 
				
			||||||
 | 
					        keys = [key for key in self.gpg.keylist(email)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if keys:
 | 
				
			||||||
 | 
					            key = keys.pop()  # NOTE: looks like keys[0] is the master key
 | 
				
			||||||
 | 
					            key_id = key.subkeys[0].keyid
 | 
				
			||||||
 | 
					            return key_id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _add_zeyple_header(self, message):
 | 
				
			||||||
 | 
					        if self.config.has_option('zeyple', 'add_header') and \
 | 
				
			||||||
 | 
					           self.config.getboolean('zeyple', 'add_header'):
 | 
				
			||||||
 | 
					            message.add_header(
 | 
				
			||||||
 | 
					                'X-Zeyple',
 | 
				
			||||||
 | 
					                "processed by {0} v{1}".format(__title__, __version__)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _send_message(self, message, recipient):
 | 
				
			||||||
 | 
					        """Sends the given message through the SMTP relay"""
 | 
				
			||||||
 | 
					        logging.info("Sending message %s", message['Message-id'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        smtp = smtplib.SMTP(self.config.get('relay', 'host'),
 | 
				
			||||||
 | 
					                            self.config.get('relay', 'port'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        smtp.sendmail(message['From'], recipient, message.as_string())
 | 
				
			||||||
 | 
					        smtp.quit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        logging.info("Message %s sent", message['Message-id'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    recipients = sys.argv[1:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # BBB: Python 2.7 support
 | 
				
			||||||
 | 
					    binary_stdin = sys.stdin.buffer if PY3K else sys.stdin
 | 
				
			||||||
 | 
					    message = binary_stdin.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    zeyple = Zeyple()
 | 
				
			||||||
 | 
					    zeyple.process_message(message, recipients)
 | 
				
			||||||
@ -1,16 +1,11 @@
 | 
				
			|||||||
FROM ubuntu:xenial
 | 
					FROM debian:jessie-slim
 | 
				
			||||||
MAINTAINER Andre Peters <andre.peters@servercow.de>
 | 
					MAINTAINER Andre Peters <andre.peters@servercow.de>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENV DEBIAN_FRONTEND noninteractive
 | 
					ENV DEBIAN_FRONTEND noninteractive
 | 
				
			||||||
ENV LC_ALL C
 | 
					ENV LC_ALL C
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN dpkg-divert --local --rename --add /sbin/initctl \
 | 
					 | 
				
			||||||
    && ln -sf /bin/true /sbin/initctl \
 | 
					 | 
				
			||||||
    && dpkg-divert --local --rename --add /usr/bin/ischroot \
 | 
					 | 
				
			||||||
    && ln -sf /bin/true /usr/bin/ischroot
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN apt-key adv --fetch-keys http://rspamd.com/apt-stable/gpg.key \
 | 
					RUN apt-key adv --fetch-keys http://rspamd.com/apt-stable/gpg.key \
 | 
				
			||||||
	&& echo "deb http://rspamd.com/apt-stable/ xenial main" > /etc/apt/sources.list.d/rspamd.list \
 | 
						&& echo "deb http://rspamd.com/apt-stable/ jessie main" > /etc/apt/sources.list.d/rspamd.list \
 | 
				
			||||||
	&& apt-get update \
 | 
						&& apt-get update \
 | 
				
			||||||
	&& apt-get --no-install-recommends -y --force-yes install rmilter cron syslog-ng syslog-ng-core supervisor
 | 
						&& apt-get --no-install-recommends -y --force-yes install rmilter cron syslog-ng syslog-ng-core supervisor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,16 +1,11 @@
 | 
				
			|||||||
FROM ubuntu:xenial
 | 
					FROM debian:jessie-slim
 | 
				
			||||||
MAINTAINER Andre Peters <andre.peters@servercow.de>
 | 
					MAINTAINER Andre Peters <andre.peters@servercow.de>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENV DEBIAN_FRONTEND noninteractive
 | 
					ENV DEBIAN_FRONTEND noninteractive
 | 
				
			||||||
ENV LC_ALL C
 | 
					ENV LC_ALL C
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN dpkg-divert --local --rename --add /sbin/initctl \
 | 
					 | 
				
			||||||
    && ln -sf /bin/true /sbin/initctl \
 | 
					 | 
				
			||||||
    && dpkg-divert --local --rename --add /usr/bin/ischroot \
 | 
					 | 
				
			||||||
    && ln -sf /bin/true /usr/bin/ischroot
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN apt-key adv --fetch-keys http://rspamd.com/apt-stable/gpg.key \
 | 
					RUN apt-key adv --fetch-keys http://rspamd.com/apt-stable/gpg.key \
 | 
				
			||||||
    && echo "deb http://rspamd.com/apt-stable/ xenial main" > /etc/apt/sources.list.d/rspamd.list \
 | 
					    && echo "deb http://rspamd.com/apt-stable/ jessie main" > /etc/apt/sources.list.d/rspamd.list \
 | 
				
			||||||
    && apt-get update \
 | 
					    && apt-get update \
 | 
				
			||||||
    && apt-get -y install rspamd ca-certificates python-pip
 | 
					    && apt-get -y install rspamd ca-certificates python-pip
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,17 +1,12 @@
 | 
				
			|||||||
FROM ubuntu:xenial
 | 
					FROM debian:jessie-slim
 | 
				
			||||||
MAINTAINER Andre Peters <andre.peters@servercow.de>
 | 
					MAINTAINER Andre Peters <andre.peters@servercow.de>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENV DEBIAN_FRONTEND noninteractive
 | 
					ENV DEBIAN_FRONTEND noninteractive
 | 
				
			||||||
ENV LC_ALL C
 | 
					ENV LC_ALL C
 | 
				
			||||||
ENV GOSU_VERSION 1.9
 | 
					ENV GOSU_VERSION 1.9
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN dpkg-divert --local --rename --add /sbin/initctl \
 | 
					 | 
				
			||||||
    && ln -sf /bin/true /sbin/initctl \
 | 
					 | 
				
			||||||
    && dpkg-divert --local --rename --add /usr/bin/ischroot \
 | 
					 | 
				
			||||||
    && ln -sf /bin/true /usr/bin/ischroot
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN apt-get update \
 | 
					RUN apt-get update \
 | 
				
			||||||
	&& apt-get install -y --no-install-recommends apt-transport-https \
 | 
						&& apt-get install -y --no-install-recommends apt-transport-https gnupg \
 | 
				
			||||||
		ca-certificates \
 | 
							ca-certificates \
 | 
				
			||||||
		wget \
 | 
							wget \
 | 
				
			||||||
		syslog-ng \
 | 
							syslog-ng \
 | 
				
			||||||
@ -29,8 +24,11 @@ RUN apt-get update \
 | 
				
			|||||||
    && chmod +x /usr/local/bin/gosu \
 | 
					    && chmod +x /usr/local/bin/gosu \
 | 
				
			||||||
    && gosu nobody true
 | 
					    && gosu nobody true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN mkdir /usr/share/doc/sogo
 | 
				
			||||||
 | 
					RUN touch /usr/share/doc/sogo/empty.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN apt-key adv --keyserver keys.gnupg.net --recv-key 0x810273C4 \
 | 
					RUN apt-key adv --keyserver keys.gnupg.net --recv-key 0x810273C4 \
 | 
				
			||||||
	&& echo "deb http://packages.inverse.ca/SOGo/nightly/3/ubuntu/ xenial xenial" > /etc/apt/sources.list.d/sogo.list \
 | 
						&& echo "deb http://packages.inverse.ca/SOGo/nightly/3/debian/ jessie jessie" > /etc/apt/sources.list.d/sogo.list \
 | 
				
			||||||
	&& apt-get update \
 | 
						&& apt-get update \
 | 
				
			||||||
	&& apt-get -y --force-yes install sogo sogo-activesync 
 | 
						&& apt-get -y --force-yes install sogo sogo-activesync 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -42,10 +40,6 @@ RUN echo '0 0 * * *   sogo   /usr/sbin/sogo-tool update-autoreply -p /etc/sogo/s
 | 
				
			|||||||
COPY ./reconf-domains.sh /
 | 
					COPY ./reconf-domains.sh /
 | 
				
			||||||
COPY supervisord.conf /etc/supervisor/supervisord.conf
 | 
					COPY supervisord.conf /etc/supervisor/supervisord.conf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#EXPOSE 20000
 | 
					 | 
				
			||||||
#EXPOSE 9191
 | 
					 | 
				
			||||||
#EXPOSE 9192
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
 | 
					CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
 | 
					RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
 | 
				
			||||||
 | 
				
			|||||||
@ -10,9 +10,8 @@ disable_plaintext_auth = yes
 | 
				
			|||||||
login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k"
 | 
					login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k"
 | 
				
			||||||
mail_home = /var/vmail/%d/%n
 | 
					mail_home = /var/vmail/%d/%n
 | 
				
			||||||
mail_location = maildir:~/
 | 
					mail_location = maildir:~/
 | 
				
			||||||
mail_plugins = quota acl zlib antispam
 | 
					mail_plugins = quota acl zlib #mail_crypt
 | 
				
			||||||
auth_username_chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@
 | 
					ssl_protocols = !SSLv3
 | 
				
			||||||
ssl_protocols = !SSLv3 !SSLv2
 | 
					 | 
				
			||||||
ssl_prefer_server_ciphers = yes
 | 
					ssl_prefer_server_ciphers = yes
 | 
				
			||||||
ssl_cipher_list = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA
 | 
					ssl_cipher_list = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA
 | 
				
			||||||
ssl_options = no_compression
 | 
					ssl_options = no_compression
 | 
				
			||||||
@ -24,12 +23,12 @@ auth_master_user_separator = *
 | 
				
			|||||||
mail_prefetch_count = 30
 | 
					mail_prefetch_count = 30
 | 
				
			||||||
passdb {
 | 
					passdb {
 | 
				
			||||||
  driver = passwd-file
 | 
					  driver = passwd-file
 | 
				
			||||||
  args = /etc/dovecot/dovecot-master.passwd
 | 
					  args = /usr/local/etc/dovecot/dovecot-master.passwd
 | 
				
			||||||
  master = yes
 | 
					  master = yes
 | 
				
			||||||
  pass = yes
 | 
					  pass = yes
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
passdb {
 | 
					passdb {
 | 
				
			||||||
  args = /etc/dovecot/sql/dovecot-mysql.conf
 | 
					  args = /usr/local/etc/dovecot/sql/dovecot-mysql.conf
 | 
				
			||||||
  driver = sql
 | 
					  driver = sql
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
namespace inbox {
 | 
					namespace inbox {
 | 
				
			||||||
@ -202,15 +201,15 @@ listen = *,[::]
 | 
				
			|||||||
ssl_cert = </etc/ssl/mail/cert.pem
 | 
					ssl_cert = </etc/ssl/mail/cert.pem
 | 
				
			||||||
ssl_key = </etc/ssl/mail/key.pem
 | 
					ssl_key = </etc/ssl/mail/key.pem
 | 
				
			||||||
userdb {
 | 
					userdb {
 | 
				
			||||||
  args = /etc/dovecot/sql/dovecot-mysql.conf
 | 
					  args = /usr/local/etc/dovecot/sql/dovecot-mysql.conf
 | 
				
			||||||
  driver = sql
 | 
					  driver = sql
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
protocol imap {
 | 
					protocol imap {
 | 
				
			||||||
  mail_plugins = quota imap_quota imap_acl acl zlib imap_zlib antispam
 | 
					  mail_plugins = quota imap_quota imap_acl acl zlib imap_zlib imap_sieve #mail_crypt
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
protocol lmtp {
 | 
					protocol lmtp {
 | 
				
			||||||
  mail_plugins = quota sieve acl zlib
 | 
					  mail_plugins = quota sieve acl zlib #mail_crypt
 | 
				
			||||||
  auth_socket_path = /var/run/dovecot/auth-master
 | 
					  auth_socket_path = /usr/local/var/run/dovecot/auth-master
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
protocol sieve {
 | 
					protocol sieve {
 | 
				
			||||||
  managesieve_logout_format = bytes=%i/%o
 | 
					  managesieve_logout_format = bytes=%i/%o
 | 
				
			||||||
@ -221,22 +220,31 @@ plugin {
 | 
				
			|||||||
  acl = vfile
 | 
					  acl = vfile
 | 
				
			||||||
  quota = dict:Userquota::proxy::sqlquota
 | 
					  quota = dict:Userquota::proxy::sqlquota
 | 
				
			||||||
  quota_rule2 = Trash:storage=+100%%
 | 
					  quota_rule2 = Trash:storage=+100%%
 | 
				
			||||||
  antispam_backend = mailtrain
 | 
					 | 
				
			||||||
  antispam_spam    = Junk
 | 
					 | 
				
			||||||
  antispam_trash   = Trash
 | 
					 | 
				
			||||||
  antispam_mail_sendmail = /usr/local/bin/rspamd-pipe
 | 
					 | 
				
			||||||
  antispam_mail_spam     = learn_spam
 | 
					 | 
				
			||||||
  antispam_mail_notspam  = learn_ham
 | 
					 | 
				
			||||||
  # Do not complain about empty parameter
 | 
					 | 
				
			||||||
  antispam_mail_sendmail_args = --blind
 | 
					 | 
				
			||||||
  sieve = /var/vmail/sieve/%u.sieve
 | 
					  sieve = /var/vmail/sieve/%u.sieve
 | 
				
			||||||
 | 
					  sieve_plugins = sieve_imapsieve sieve_extprograms
 | 
				
			||||||
 | 
					  # From elsewhere to Spam folder
 | 
				
			||||||
 | 
					  imapsieve_mailbox1_name = Junk
 | 
				
			||||||
 | 
					  imapsieve_mailbox1_causes = COPY
 | 
				
			||||||
 | 
					  imapsieve_mailbox1_before = file:/usr/local/lib/dovecot/sieve/report-spam.sieve
 | 
				
			||||||
 | 
					  # END
 | 
				
			||||||
 | 
					  # From Spam folder to elsewhere
 | 
				
			||||||
 | 
					  imapsieve_mailbox2_name = *
 | 
				
			||||||
 | 
					  imapsieve_mailbox2_from = Junk
 | 
				
			||||||
 | 
					  imapsieve_mailbox2_causes = COPY
 | 
				
			||||||
 | 
					  imapsieve_mailbox2_before = file:/usr/local/lib/dovecot/sieve/report-ham.sieve
 | 
				
			||||||
 | 
					  # END
 | 
				
			||||||
 | 
					  sieve_pipe_bin_dir = /usr/local/lib/dovecot/sieve
 | 
				
			||||||
 | 
					  sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute
 | 
				
			||||||
  sieve_after = /var/vmail/sieve/global.sieve
 | 
					  sieve_after = /var/vmail/sieve/global.sieve
 | 
				
			||||||
  sieve_max_script_size = 1M
 | 
					  sieve_max_script_size = 1M
 | 
				
			||||||
  sieve_quota_max_scripts = 0
 | 
					  sieve_quota_max_scripts = 0
 | 
				
			||||||
  sieve_quota_max_storage = 0
 | 
					  sieve_quota_max_storage = 0
 | 
				
			||||||
 | 
					  #mail_crypt_global_private_key = </mail_crypt/ecprivkey.pem
 | 
				
			||||||
 | 
					  #mail_crypt_global_public_key = </mail_crypt/ecpubkey.pem
 | 
				
			||||||
 | 
					  #mail_crypt_save_version = 2
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
dict {
 | 
					dict {
 | 
				
			||||||
  sqlquota = mysql:/etc/dovecot/sql/dovecot-dict-sql.conf
 | 
					  sqlquota = mysql:/usr/local/etc/dovecot/sql/dovecot-dict-sql.conf
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
remote 127.0.0.1 {
 | 
					remote 127.0.0.1 {
 | 
				
			||||||
  disable_plaintext_auth = no
 | 
					  disable_plaintext_auth = no
 | 
				
			||||||
 | 
				
			|||||||
@ -18,6 +18,11 @@ server {
 | 
				
			|||||||
  access_log /var/log/nginx/access.log;
 | 
					  access_log /var/log/nginx/access.log;
 | 
				
			||||||
  root /web;
 | 
					  root /web;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  location /api/v1/ {
 | 
				
			||||||
 | 
					    try_files $uri $uri/ /json_api.php?$args;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  rewrite ^/api/v1/([^/]+)/([^/]+)/?$ /json_api.php?action=$1&object=$2? last;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  location ^~ /.well-known/acme-challenge/ {
 | 
					  location ^~ /.well-known/acme-challenge/ {
 | 
				
			||||||
	  allow all;
 | 
						  allow all;
 | 
				
			||||||
    default_type "text/plain";
 | 
					    default_type "text/plain";
 | 
				
			||||||
@ -166,6 +171,11 @@ server {
 | 
				
			|||||||
  access_log /var/log/nginx/access.log;
 | 
					  access_log /var/log/nginx/access.log;
 | 
				
			||||||
  root /web;
 | 
					  root /web;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  location /api/v1/ {
 | 
				
			||||||
 | 
					    try_files $uri $uri/ /json_api.php?$args;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  rewrite ^/api/v1/([^/]+)/([^/]+)/?$ /json_api.php?action=$1&object=$2? last;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  location ^~ /.well-known/acme-challenge/ {
 | 
					  location ^~ /.well-known/acme-challenge/ {
 | 
				
			||||||
	  allow all;
 | 
						  allow all;
 | 
				
			||||||
    default_type "text/plain";
 | 
					    default_type "text/plain";
 | 
				
			||||||
 | 
				
			|||||||
@ -91,3 +91,4 @@ smtpd_milters = inet:rmilter:9900
 | 
				
			|||||||
non_smtpd_milters = inet:rmilter:9900
 | 
					non_smtpd_milters = inet:rmilter:9900
 | 
				
			||||||
milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
 | 
					milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
 | 
				
			||||||
mydestination = localhost.localdomain, localhost
 | 
					mydestination = localhost.localdomain, localhost
 | 
				
			||||||
 | 
					#content_filter=zeyple
 | 
				
			||||||
 | 
				
			|||||||
@ -16,6 +16,7 @@ smtp_enforced_tls      unix  -       -       n       -       -       smtp
 | 
				
			|||||||
  -o smtp_tls_security_level=encrypt
 | 
					  -o smtp_tls_security_level=encrypt
 | 
				
			||||||
  -o syslog_name=enforced-tls-smtp
 | 
					  -o syslog_name=enforced-tls-smtp
 | 
				
			||||||
  -o smtp_delivery_status_filter=pcre:/opt/postfix/conf/smtp_dsn_filter
 | 
					  -o smtp_delivery_status_filter=pcre:/opt/postfix/conf/smtp_dsn_filter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
tlsproxy   unix  -       -       n       -       0       tlsproxy
 | 
					tlsproxy   unix  -       -       n       -       0       tlsproxy
 | 
				
			||||||
dnsblog    unix  -       -       n       -       0       dnsblog
 | 
					dnsblog    unix  -       -       n       -       0       dnsblog
 | 
				
			||||||
pickup     fifo  n       -       n       60      1       pickup
 | 
					pickup     fifo  n       -       n       60      1       pickup
 | 
				
			||||||
@ -43,3 +44,14 @@ anvil      unix  -       -       n       -       1       anvil
 | 
				
			|||||||
scache     unix  -       -       n       -       1       scache
 | 
					scache     unix  -       -       n       -       1       scache
 | 
				
			||||||
maildrop   unix  -       n       n       -       -       pipe flags=DRhu
 | 
					maildrop   unix  -       n       n       -       -       pipe flags=DRhu
 | 
				
			||||||
    user=vmail argv=/usr/bin/maildrop -d ${recipient}
 | 
					    user=vmail argv=/usr/bin/maildrop -d ${recipient}
 | 
				
			||||||
 | 
					zeyple    unix  -       n       n       -       -       pipe
 | 
				
			||||||
 | 
					  user=zeyple argv=/usr/local/bin/zeyple.py ${recipient}
 | 
				
			||||||
 | 
					127.0.0.1:10026 inet  n       -       n       -       10      smtpd
 | 
				
			||||||
 | 
					  -o content_filter=
 | 
				
			||||||
 | 
					  -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks,no_milters
 | 
				
			||||||
 | 
					  -o smtpd_helo_restrictions=
 | 
				
			||||||
 | 
					  -o smtpd_client_restrictions=
 | 
				
			||||||
 | 
					  -o smtpd_sender_restrictions=
 | 
				
			||||||
 | 
					  -o smtpd_recipient_restrictions=permit_mynetworks,reject
 | 
				
			||||||
 | 
					  -o mynetworks=127.0.0.0/8
 | 
				
			||||||
 | 
					  -o smtpd_authorized_xforward_hosts=127.0.0.0/8
 | 
				
			||||||
 | 
				
			|||||||
@ -350,6 +350,13 @@ elseif (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] ==
 | 
				
			|||||||
							</div>
 | 
												</div>
 | 
				
			||||||
						</div>
 | 
											</div>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
 | 
										<div class="form-group">
 | 
				
			||||||
 | 
											<div class="col-sm-offset-2 col-sm-10">
 | 
				
			||||||
 | 
												<div class="checkbox">
 | 
				
			||||||
 | 
												<label><input type="checkbox" name="delete1" checked> <?=$lang['add']['delete1'];?></label>
 | 
				
			||||||
 | 
												</div>
 | 
				
			||||||
 | 
											</div>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
					<div class="form-group">
 | 
										<div class="form-group">
 | 
				
			||||||
						<div class="col-sm-offset-2 col-sm-10">
 | 
											<div class="col-sm-offset-2 col-sm-10">
 | 
				
			||||||
							<div class="checkbox">
 | 
												<div class="checkbox">
 | 
				
			||||||
 | 
				
			|||||||
@ -81,14 +81,14 @@ $tfa_data = get_tfa();
 | 
				
			|||||||
        <div class="panel-body">
 | 
					        <div class="panel-body">
 | 
				
			||||||
          <form method="post">
 | 
					          <form method="post">
 | 
				
			||||||
            <div class="table-responsive">
 | 
					            <div class="table-responsive">
 | 
				
			||||||
            <table class="table table-striped sortable-theme-bootstrap" data-sortable id="domainadminstable">
 | 
					            <table class="table table-striped" id="domainadminstable">
 | 
				
			||||||
              <thead>
 | 
					              <thead>
 | 
				
			||||||
              <tr>
 | 
					              <tr>
 | 
				
			||||||
                <th class="sort-table" style="min-width: 100px;"><?=$lang['admin']['username'];?></th>
 | 
					                <th style="min-width: 100px;"><?=$lang['admin']['username'];?></th>
 | 
				
			||||||
                <th class="sort-table" style="min-width: 166px;"><?=$lang['admin']['admin_domains'];?></th>
 | 
					                <th style="min-width: 166px;"><?=$lang['admin']['admin_domains'];?></th>
 | 
				
			||||||
                <th class="sort-table" style="min-width: 76px;"><?=$lang['admin']['active'];?></th>
 | 
					                <th style="min-width: 76px;"><?=$lang['admin']['active'];?></th>
 | 
				
			||||||
                <th class="sort-table" style="min-width: 76px;"><?=$lang['tfa']['tfa'];?></th>
 | 
					                <th style="min-width: 76px;"><?=$lang['tfa']['tfa'];?></th>
 | 
				
			||||||
                <th style="text-align: right; min-width: 200px;" data-sortable="false"><?=$lang['admin']['action'];?></th>
 | 
					                <th style="text-align: right; min-width: 200px;"><?=$lang['admin']['action'];?></th>
 | 
				
			||||||
              </tr>
 | 
					              </tr>
 | 
				
			||||||
              </thead>
 | 
					              </thead>
 | 
				
			||||||
              <tbody>
 | 
					              <tbody>
 | 
				
			||||||
@ -299,8 +299,14 @@ $tfa_data = get_tfa();
 | 
				
			|||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</div> <!-- /container -->
 | 
					</div> <!-- /container -->
 | 
				
			||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js" integrity="sha384-YWP9O4NjmcGo4oEJFXvvYSEzuHIvey+LbXkBNJ1Kd0yfugEZN9NCQNpRYBVC1RvA" crossorigin="anonymous"></script>
 | 
					<script type='text/javascript'>
 | 
				
			||||||
<script src="js/sorttable.js"></script>
 | 
					<?php
 | 
				
			||||||
 | 
					$lang_admin = json_encode($lang['admin']);
 | 
				
			||||||
 | 
					echo "var lang = ". $lang_admin . ";\n";
 | 
				
			||||||
 | 
					echo "var pagination_size = '". $PAGINATION_SIZE . "';\n";
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<script src="js/footable.min.js"></script>
 | 
				
			||||||
<script src="js/admin.js"></script>
 | 
					<script src="js/admin.js"></script>
 | 
				
			||||||
<?php
 | 
					<?php
 | 
				
			||||||
require_once("inc/footer.inc.php");
 | 
					require_once("inc/footer.inc.php");
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										13
									
								
								data/web/css/admin.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								data/web/css/admin.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					table.footable>tbody>tr.footable-empty>td {
 | 
				
			||||||
 | 
					  font-size:15px !important;
 | 
				
			||||||
 | 
					  font-style:italic;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.pagination a {
 | 
				
			||||||
 | 
					  text-decoration: none !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.panel panel-default {
 | 
				
			||||||
 | 
					  overflow: visible !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.table-responsive {
 | 
				
			||||||
 | 
					  overflow: visible !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,19 +1,19 @@
 | 
				
			|||||||
.panel-heading div {
 | 
					table.footable>tbody>tr.footable-empty>td {
 | 
				
			||||||
	margin-top: -18px;
 | 
					  font-size:15px !important;
 | 
				
			||||||
	font-size: 15px;
 | 
					  font-style:italic;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.panel-heading div span {
 | 
					.pagination a {
 | 
				
			||||||
	margin-left:5px;
 | 
					  text-decoration: none !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.panel-body {
 | 
					.panel panel-default {
 | 
				
			||||||
	display: none;
 | 
					  overflow: visible !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.clickable {
 | 
					.table-responsive {
 | 
				
			||||||
	cursor: pointer;
 | 
					  overflow: visible !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.progress {
 | 
					.footer-add-item {
 | 
				
			||||||
	margin-bottom: 0px;
 | 
					  text-align:center;
 | 
				
			||||||
}
 | 
					  font-style: italic;
 | 
				
			||||||
.table>thead>tr>th {
 | 
					  display:block;
 | 
				
			||||||
  vertical-align: top !important;
 | 
					  padding: 10px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1,79 +0,0 @@
 | 
				
			|||||||
ul[id*="sortable"] { word-wrap: break-word; list-style-type: none; float: left; padding: 0 15px 0 0; width: 48%; cursor:move}
 | 
					 | 
				
			||||||
ul[id$="sortable-active"] li {cursor:move; }
 | 
					 | 
				
			||||||
ul[id$="sortable-inactive"] li {cursor:move }
 | 
					 | 
				
			||||||
.list-heading { cursor:default !important}
 | 
					 | 
				
			||||||
.ui-state-disabled { cursor:no-drop; color:#ccc; }
 | 
					 | 
				
			||||||
.ui-state-highlight {background: #F5F5F5 !important; height: 41px !important; cursor:move }
 | 
					 | 
				
			||||||
table[data-sortable] {
 | 
					 | 
				
			||||||
  border-collapse: collapse;
 | 
					 | 
				
			||||||
  border-spacing: 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
table[data-sortable] th {
 | 
					 | 
				
			||||||
  vertical-align: bottom;
 | 
					 | 
				
			||||||
  font-weight: bold;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
table[data-sortable] th, table[data-sortable] td {
 | 
					 | 
				
			||||||
  text-align: left;
 | 
					 | 
				
			||||||
  padding: 10px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
table[data-sortable] th:not([data-sortable="false"]) {
 | 
					 | 
				
			||||||
  -webkit-user-select: none;
 | 
					 | 
				
			||||||
  -moz-user-select: none;
 | 
					 | 
				
			||||||
  -ms-user-select: none;
 | 
					 | 
				
			||||||
  -o-user-select: none;
 | 
					 | 
				
			||||||
  user-select: none;
 | 
					 | 
				
			||||||
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
 | 
					 | 
				
			||||||
  -webkit-touch-callout: none;
 | 
					 | 
				
			||||||
  cursor: pointer;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
table[data-sortable] th:after {
 | 
					 | 
				
			||||||
  content: "";
 | 
					 | 
				
			||||||
  visibility: hidden;
 | 
					 | 
				
			||||||
  display: inline-block;
 | 
					 | 
				
			||||||
  vertical-align: inherit;
 | 
					 | 
				
			||||||
  height: 0;
 | 
					 | 
				
			||||||
  width: 0;
 | 
					 | 
				
			||||||
  border-width: 5px;
 | 
					 | 
				
			||||||
  border-style: solid;
 | 
					 | 
				
			||||||
  border-color: transparent;
 | 
					 | 
				
			||||||
  margin-right: 1px;
 | 
					 | 
				
			||||||
  margin-left: 10px;
 | 
					 | 
				
			||||||
  float: right;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
table[data-sortable] th[data-sortable="false"]:after {
 | 
					 | 
				
			||||||
  display: none;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
table[data-sortable] th[data-sorted="true"]:after {
 | 
					 | 
				
			||||||
  visibility: visible;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
table[data-sortable] th[data-sorted-direction="descending"]:after {
 | 
					 | 
				
			||||||
  border-top-color: inherit;
 | 
					 | 
				
			||||||
  margin-top: 8px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
table[data-sortable] th[data-sorted-direction="ascending"]:after {
 | 
					 | 
				
			||||||
  border-bottom-color: inherit;
 | 
					 | 
				
			||||||
  margin-top: 3px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
table[data-sortable].sortable-theme-bootstrap thead th {
 | 
					 | 
				
			||||||
  border-bottom: 2px solid #e0e0e0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
table[data-sortable].sortable-theme-bootstrap th[data-sorted="true"] {
 | 
					 | 
				
			||||||
  color: #3a87ad;
 | 
					 | 
				
			||||||
  background: #d9edf7;
 | 
					 | 
				
			||||||
  border-bottom-color: #bce8f1;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
table[data-sortable].sortable-theme-bootstrap th[data-sorted="true"][data-sorted-direction="descending"]:after {
 | 
					 | 
				
			||||||
  border-top-color: #3a87ad;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
table[data-sortable].sortable-theme-bootstrap th[data-sorted="true"][data-sorted-direction="ascending"]:after {
 | 
					 | 
				
			||||||
  border-bottom-color: #3a87ad;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
table[data-sortable].sortable-theme-bootstrap.sortable-theme-bootstrap-striped tbody > tr:nth-child(odd) > td {
 | 
					 | 
				
			||||||
  background-color: #f9f9f9;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#data td, #no-data td {
 | 
					 | 
				
			||||||
	vertical-align: middle;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.sort-table:hover {
 | 
					 | 
				
			||||||
  border-bottom-color: #00B7DC !important;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -620,6 +620,20 @@ elseif (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] ==
 | 
				
			|||||||
						<input type="text" class="form-control" name="exclude" id="exclude" value="<?=htmlspecialchars($result['exclude'], ENT_QUOTES, 'UTF-8');?>">
 | 
											<input type="text" class="form-control" name="exclude" id="exclude" value="<?=htmlspecialchars($result['exclude'], ENT_QUOTES, 'UTF-8');?>">
 | 
				
			||||||
						</div>
 | 
											</div>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
 | 
										<div class="form-group">
 | 
				
			||||||
 | 
											<div class="col-sm-offset-2 col-sm-10">
 | 
				
			||||||
 | 
												<div class="checkbox">
 | 
				
			||||||
 | 
												<label><input type="checkbox" name="delete2duplicates" <?=($result['delete2duplicates']=="1") ? "checked" : "";?>> <?=$lang['edit']['delete2duplicates'];?></label>
 | 
				
			||||||
 | 
												</div>
 | 
				
			||||||
 | 
											</div>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
										<div class="form-group">
 | 
				
			||||||
 | 
											<div class="col-sm-offset-2 col-sm-10">
 | 
				
			||||||
 | 
												<div class="checkbox">
 | 
				
			||||||
 | 
												<label><input type="checkbox" name="delete1" <?=($result['delete1']=="1") ? "checked" : "";?>> <?=$lang['edit']['delete1'];?></label>
 | 
				
			||||||
 | 
												</div>
 | 
				
			||||||
 | 
											</div>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
					<div class="form-group">
 | 
										<div class="form-group">
 | 
				
			||||||
						<div class="col-sm-offset-2 col-sm-10">
 | 
											<div class="col-sm-offset-2 col-sm-10">
 | 
				
			||||||
							<div class="checkbox">
 | 
												<div class="checkbox">
 | 
				
			||||||
 | 
				
			|||||||
@ -50,11 +50,7 @@ $(document).ready(function() {
 | 
				
			|||||||
          type: "GET",
 | 
					          type: "GET",
 | 
				
			||||||
          cache: false,
 | 
					          cache: false,
 | 
				
			||||||
          dataType: 'script',
 | 
					          dataType: 'script',
 | 
				
			||||||
          url: "json_api.php",
 | 
					          url: "/api/v1/u2f-authentication/<?=(isset($_SESSION['pending_mailcow_cc_username'])) ? $_SESSION['pending_mailcow_cc_username'] : null;?>",
 | 
				
			||||||
          data: {
 | 
					 | 
				
			||||||
            'action':'get_u2f_auth_challenge',
 | 
					 | 
				
			||||||
            'object':'<?=(isset($_SESSION['pending_mailcow_cc_username'])) ? $_SESSION['pending_mailcow_cc_username'] : null;?>',
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          success: function(data){
 | 
					          success: function(data){
 | 
				
			||||||
            data;
 | 
					            data;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
@ -87,11 +83,7 @@ $(document).ready(function() {
 | 
				
			|||||||
        type: "GET",
 | 
					        type: "GET",
 | 
				
			||||||
        cache: false,
 | 
					        cache: false,
 | 
				
			||||||
        dataType: 'script',
 | 
					        dataType: 'script',
 | 
				
			||||||
        url: "json_api.php",
 | 
					        url: "/api/v1/u2f-registration/<?=(isset($_SESSION['mailcow_cc_username'])) ? $_SESSION['mailcow_cc_username'] : null;?>",
 | 
				
			||||||
        data: {
 | 
					 | 
				
			||||||
          'action':'get_u2f_reg_challenge',
 | 
					 | 
				
			||||||
          'object':'<?=(isset($_SESSION['mailcow_cc_username'])) ? $_SESSION['mailcow_cc_username'] : null;?>',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        success: function(data){
 | 
					        success: function(data){
 | 
				
			||||||
          data;
 | 
					          data;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -109,6 +109,11 @@ function init_db_schema() {
 | 
				
			|||||||
  if ($num_results == 0) {
 | 
					  if ($num_results == 0) {
 | 
				
			||||||
    $pdo->query("ALTER TABLE `mailbox` ADD `multiple_bookings` tinyint(1) NOT NULL DEFAULT '0'");
 | 
					    $pdo->query("ALTER TABLE `mailbox` ADD `multiple_bookings` tinyint(1) NOT NULL DEFAULT '0'");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  $stmt = $pdo->query("SHOW COLUMNS FROM `imapsync` LIKE 'delete1'");
 | 
				
			||||||
 | 
					  $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
 | 
				
			||||||
 | 
					  if ($num_results == 0) {
 | 
				
			||||||
 | 
					    $pdo->query("ALTER TABLE `imapsync` ADD `delete1` tinyint(1) NOT NULL DEFAULT '0'");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  $stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE 'wants_tagged_subject'");
 | 
					  $stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE 'wants_tagged_subject'");
 | 
				
			||||||
  $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
 | 
					  $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
 | 
				
			||||||
  if ($num_results == 0) {
 | 
					  if ($num_results == 0) {
 | 
				
			||||||
@ -1075,6 +1080,7 @@ function add_syncjob($postarray) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  isset($postarray['active']) ? $active = '1' : $active = '0';
 | 
					  isset($postarray['active']) ? $active = '1' : $active = '0';
 | 
				
			||||||
  isset($postarray['delete2duplicates']) ? $delete2duplicates = '1' : $delete2duplicates = '0';
 | 
					  isset($postarray['delete2duplicates']) ? $delete2duplicates = '1' : $delete2duplicates = '0';
 | 
				
			||||||
 | 
					  isset($postarray['delete1']) ? $delete1 = '1' : $delete1 = '0';
 | 
				
			||||||
  $port1            = $postarray['port1'];
 | 
					  $port1            = $postarray['port1'];
 | 
				
			||||||
  $host1            = $postarray['host1'];
 | 
					  $host1            = $postarray['host1'];
 | 
				
			||||||
  $password1        = $postarray['password1'];
 | 
					  $password1        = $postarray['password1'];
 | 
				
			||||||
@ -1147,12 +1153,13 @@ function add_syncjob($postarray) {
 | 
				
			|||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    $stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `maxage`, `subfolder2`, `host1`, `authmech1`, `user1`, `password1`, `mins_interval`, `port1`, `enc1`, `delete2duplicates`, `active`)
 | 
					    $stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `delete1`, `maxage`, `subfolder2`, `host1`, `authmech1`, `user1`, `password1`, `mins_interval`, `port1`, `enc1`, `delete2duplicates`, `active`)
 | 
				
			||||||
      VALUES (:user2, :exclude, :maxage, :subfolder2, :host1, :authmech1, :user1, :password1, :mins_interval, :port1, :enc1, :delete2duplicates, :active)");
 | 
					      VALUES (:user2, :exclude, :maxage, :delete1, :subfolder2, :host1, :authmech1, :user1, :password1, :mins_interval, :port1, :enc1, :delete2duplicates, :active)");
 | 
				
			||||||
    $stmt->execute(array(
 | 
					    $stmt->execute(array(
 | 
				
			||||||
      ':user2' => $username,
 | 
					      ':user2' => $username,
 | 
				
			||||||
      ':exclude' => $exclude,
 | 
					      ':exclude' => $exclude,
 | 
				
			||||||
      ':maxage' => $maxage,
 | 
					      ':maxage' => $maxage,
 | 
				
			||||||
 | 
					      ':delete1' => $delete1,
 | 
				
			||||||
      ':subfolder2' => $subfolder2,
 | 
					      ':subfolder2' => $subfolder2,
 | 
				
			||||||
      ':host1' => $host1,
 | 
					      ':host1' => $host1,
 | 
				
			||||||
      ':authmech1' => 'PLAIN',
 | 
					      ':authmech1' => 'PLAIN',
 | 
				
			||||||
@ -1200,6 +1207,7 @@ function edit_syncjob($postarray) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  isset($postarray['active']) ? $active = '1' : $active = '0';
 | 
					  isset($postarray['active']) ? $active = '1' : $active = '0';
 | 
				
			||||||
  isset($postarray['delete2duplicates']) ? $delete2duplicates = '1' : $delete2duplicates = '0';
 | 
					  isset($postarray['delete2duplicates']) ? $delete2duplicates = '1' : $delete2duplicates = '0';
 | 
				
			||||||
 | 
					  isset($postarray['delete1']) ? $delete1 = '1' : $delete1 = '0';
 | 
				
			||||||
  $id               = $postarray['id'];
 | 
					  $id               = $postarray['id'];
 | 
				
			||||||
  $port1            = $postarray['port1'];
 | 
					  $port1            = $postarray['port1'];
 | 
				
			||||||
  $host1            = $postarray['host1'];
 | 
					  $host1            = $postarray['host1'];
 | 
				
			||||||
@ -1273,10 +1281,11 @@ function edit_syncjob($postarray) {
 | 
				
			|||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    $stmt = $pdo->prepare("UPDATE `imapsync` set `maxage` = :maxage, `subfolder2` = :subfolder2, `exclude` = :exclude, `host1` = :host1, `user1` = :user1, `password1` = :password1, `mins_interval` = :mins_interval, `port1` = :port1, `enc1` = :enc1, `delete2duplicates` = :delete2duplicates, `active` = :active
 | 
					    $stmt = $pdo->prepare("UPDATE `imapsync` set `delete1` = :delete1, `maxage` = :maxage, `subfolder2` = :subfolder2, `exclude` = :exclude, `host1` = :host1, `user1` = :user1, `password1` = :password1, `mins_interval` = :mins_interval, `port1` = :port1, `enc1` = :enc1, `delete2duplicates` = :delete2duplicates, `active` = :active
 | 
				
			||||||
      WHERE `user2` = :user2 AND `id` = :id");
 | 
					      WHERE `user2` = :user2 AND `id` = :id");
 | 
				
			||||||
    $stmt->execute(array(
 | 
					    $stmt->execute(array(
 | 
				
			||||||
      ':user2' => $username,
 | 
					      ':user2' => $username,
 | 
				
			||||||
 | 
					      ':delete1' => $delete1,
 | 
				
			||||||
      ':id' => $id,
 | 
					      ':id' => $id,
 | 
				
			||||||
      ':exclude' => $exclude,
 | 
					      ':exclude' => $exclude,
 | 
				
			||||||
      ':maxage' => $maxage,
 | 
					      ':maxage' => $maxage,
 | 
				
			||||||
@ -1757,6 +1766,7 @@ function get_domain_admin_details($domain_admin) {
 | 
				
			|||||||
  try {
 | 
					  try {
 | 
				
			||||||
    $stmt = $pdo->prepare("SELECT
 | 
					    $stmt = $pdo->prepare("SELECT
 | 
				
			||||||
      `tfa`.`active` AS `tfa_active_int`,
 | 
					      `tfa`.`active` AS `tfa_active_int`,
 | 
				
			||||||
 | 
					      CASE `tfa`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `tfa_active`,
 | 
				
			||||||
      `domain_admins`.`username`,
 | 
					      `domain_admins`.`username`,
 | 
				
			||||||
      `domain_admins`.`created`,
 | 
					      `domain_admins`.`created`,
 | 
				
			||||||
      `domain_admins`.`active` AS `active_int`,
 | 
					      `domain_admins`.`active` AS `active_int`,
 | 
				
			||||||
@ -1768,11 +1778,15 @@ function get_domain_admin_details($domain_admin) {
 | 
				
			|||||||
      ':domain_admin' => $domain_admin
 | 
					      ':domain_admin' => $domain_admin
 | 
				
			||||||
    ));
 | 
					    ));
 | 
				
			||||||
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
 | 
					    $row = $stmt->fetch(PDO::FETCH_ASSOC);
 | 
				
			||||||
 | 
					    if (empty($row)) { 
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    $domainadmindata['username'] = $row['username'];
 | 
					    $domainadmindata['username'] = $row['username'];
 | 
				
			||||||
 | 
					    $domainadmindata['tfa_active'] = $row['tfa_active'];
 | 
				
			||||||
    $domainadmindata['active'] = $row['active'];
 | 
					    $domainadmindata['active'] = $row['active'];
 | 
				
			||||||
    $domainadmindata['active_int'] = $row['active_int'];
 | 
					 | 
				
			||||||
    $domainadmindata['tfa_active_int'] = $row['tfa_active_int'];
 | 
					    $domainadmindata['tfa_active_int'] = $row['tfa_active_int'];
 | 
				
			||||||
    $domainadmindata['created'] = $row['created'];
 | 
					    $domainadmindata['active_int'] = $row['active_int'];
 | 
				
			||||||
 | 
					    $domainadmindata['modified'] = $row['created'];
 | 
				
			||||||
    // GET SELECTED
 | 
					    // GET SELECTED
 | 
				
			||||||
    $stmt = $pdo->prepare("SELECT `domain` FROM `domain`
 | 
					    $stmt = $pdo->prepare("SELECT `domain` FROM `domain`
 | 
				
			||||||
      WHERE `domain` IN (
 | 
					      WHERE `domain` IN (
 | 
				
			||||||
@ -1793,6 +1807,9 @@ function get_domain_admin_details($domain_admin) {
 | 
				
			|||||||
    while($row = array_shift($rows)) {
 | 
					    while($row = array_shift($rows)) {
 | 
				
			||||||
      $domainadmindata['unselected_domains'][] = $row['domain'];
 | 
					      $domainadmindata['unselected_domains'][] = $row['domain'];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (!isset($domainadmindata['unselected_domains'])) {
 | 
				
			||||||
 | 
					      $domainadmindata['unselected_domains'] = "";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  catch(PDOException $e) {
 | 
					  catch(PDOException $e) {
 | 
				
			||||||
    $_SESSION['return'] = array(
 | 
					    $_SESSION['return'] = array(
 | 
				
			||||||
@ -2134,6 +2151,14 @@ function edit_domain_admin($postarray) {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (empty($postarray['domain'])) {
 | 
				
			||||||
 | 
					      $_SESSION['return'] = array(
 | 
				
			||||||
 | 
					        'type' => 'danger',
 | 
				
			||||||
 | 
					        'msg' => sprintf($lang['danger']['domain_invalid'])
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
 | 
					    if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
 | 
				
			||||||
      $_SESSION['return'] = array(
 | 
					      $_SESSION['return'] = array(
 | 
				
			||||||
        'type' => 'danger',
 | 
					        'type' => 'danger',
 | 
				
			||||||
@ -2164,7 +2189,7 @@ function edit_domain_admin($postarray) {
 | 
				
			|||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(isset($postarray['domain'])) {
 | 
					    if (isset($postarray['domain'])) {
 | 
				
			||||||
      foreach ($postarray['domain'] as $domain) {
 | 
					      foreach ($postarray['domain'] as $domain) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
          $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
 | 
					          $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
 | 
				
			||||||
@ -2519,6 +2544,14 @@ function mailbox_add_domain($postarray) {
 | 
				
			|||||||
		return false;
 | 
							return false;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ($maxquota == "0" || empty($maxquota)) {
 | 
				
			||||||
 | 
							$_SESSION['return'] = array(
 | 
				
			||||||
 | 
								'type' => 'danger',
 | 
				
			||||||
 | 
								'msg' => sprintf($lang['danger']['maxquota_empty'])
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	isset($postarray['active'])               ? $active = '1'                 : $active = '0';
 | 
						isset($postarray['active'])               ? $active = '1'                 : $active = '0';
 | 
				
			||||||
	isset($postarray['relay_all_recipients'])	? $relay_all_recipients = '1'   : $relay_all_recipients = '0';
 | 
						isset($postarray['relay_all_recipients'])	? $relay_all_recipients = '1'   : $relay_all_recipients = '0';
 | 
				
			||||||
	isset($postarray['backupmx'])             ? $backupmx = '1'               : $backupmx = '0';
 | 
						isset($postarray['backupmx'])             ? $backupmx = '1'               : $backupmx = '0';
 | 
				
			||||||
@ -2623,6 +2656,18 @@ function mailbox_add_alias($postarray) {
 | 
				
			|||||||
		return false;
 | 
							return false;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  $stmt = $pdo->prepare("SELECT `address` FROM `alias`
 | 
				
			||||||
 | 
					    WHERE `address`= :address");
 | 
				
			||||||
 | 
					  $stmt->execute(array(':address' => $address));
 | 
				
			||||||
 | 
					  $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
 | 
				
			||||||
 | 
					  if ($num_results != 0) {
 | 
				
			||||||
 | 
					    $_SESSION['return'] = array(
 | 
				
			||||||
 | 
					      'type' => 'danger',
 | 
				
			||||||
 | 
					      'msg' => sprintf($lang['danger']['is_alias_or_mailbox'], htmlspecialchars($address))
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	foreach ($addresses as $address) {
 | 
						foreach ($addresses as $address) {
 | 
				
			||||||
		if (empty($address)) {
 | 
							if (empty($address)) {
 | 
				
			||||||
			continue;
 | 
								continue;
 | 
				
			||||||
@ -2632,6 +2677,15 @@ function mailbox_add_alias($postarray) {
 | 
				
			|||||||
		$local_part   = strstr($address, '@', true);
 | 
							$local_part   = strstr($address, '@', true);
 | 
				
			||||||
		$address      = $local_part.'@'.$domain;
 | 
							$address      = $local_part.'@'.$domain;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $domaindata = mailbox_get_domain_details($domain);
 | 
				
			||||||
 | 
					    if ($domaindata['aliases_left'] == 0) {
 | 
				
			||||||
 | 
					      $_SESSION['return'] = array(
 | 
				
			||||||
 | 
					        'type' => 'danger',
 | 
				
			||||||
 | 
					        'msg' => sprintf($lang['danger']['max_alias_exceeded'])
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
 | 
								$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
 | 
				
			||||||
				WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)");
 | 
									WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)");
 | 
				
			||||||
@ -3519,6 +3573,14 @@ function mailbox_edit_domain($postarray) {
 | 
				
			|||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ($maxquota == "0" || empty($maxquota)) {
 | 
				
			||||||
 | 
					      $_SESSION['return'] = array(
 | 
				
			||||||
 | 
					        'type' => 'danger',
 | 
				
			||||||
 | 
					        'msg' => sprintf($lang['danger']['maxquota_empty'])
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if ($MailboxData['maxquota'] > $maxquota) {
 | 
					    if ($MailboxData['maxquota'] > $maxquota) {
 | 
				
			||||||
      $_SESSION['return'] = array(
 | 
					      $_SESSION['return'] = array(
 | 
				
			||||||
        'type' => 'danger',
 | 
					        'type' => 'danger',
 | 
				
			||||||
@ -4271,6 +4333,10 @@ function mailbox_get_domain_details($domain) {
 | 
				
			|||||||
      ':domain' => $domain,
 | 
					      ':domain' => $domain,
 | 
				
			||||||
    ));
 | 
					    ));
 | 
				
			||||||
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
 | 
					    $row = $stmt->fetch(PDO::FETCH_ASSOC);
 | 
				
			||||||
 | 
					    if (empty($row)) { 
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $stmt = $pdo->prepare("SELECT COUNT(*) AS `count`, COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` = :domain");
 | 
					    $stmt = $pdo->prepare("SELECT COUNT(*) AS `count`, COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` = :domain");
 | 
				
			||||||
    $stmt->execute(array(':domain' => $row['domain']));
 | 
					    $stmt->execute(array(':domain' => $row['domain']));
 | 
				
			||||||
    $MailboxDataDomain	= $stmt->fetch(PDO::FETCH_ASSOC);
 | 
					    $MailboxDataDomain	= $stmt->fetch(PDO::FETCH_ASSOC);
 | 
				
			||||||
@ -4303,8 +4369,9 @@ function mailbox_get_domain_details($domain) {
 | 
				
			|||||||
    $stmt->execute(array(
 | 
					    $stmt->execute(array(
 | 
				
			||||||
      ':domain' => $domain,
 | 
					      ':domain' => $domain,
 | 
				
			||||||
    ));
 | 
					    ));
 | 
				
			||||||
    $AliasData = $stmt->fetch(PDO::FETCH_ASSOC);
 | 
					    $AliasDataDomain = $stmt->fetch(PDO::FETCH_ASSOC);
 | 
				
			||||||
    (isset($AliasData['alias_count'])) ? $domaindata['aliases_in_domain'] = $AliasData['alias_count'] : $domaindata['aliases_in_domain'] = "0";
 | 
					    (isset($AliasDataDomain['alias_count'])) ? $domaindata['aliases_in_domain'] = $AliasDataDomain['alias_count'] : $domaindata['aliases_in_domain'] = "0";
 | 
				
			||||||
 | 
					    $domaindata['aliases_left'] = $row['aliases']	- $AliasDataDomain['alias_count'];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  catch (PDOException $e) {
 | 
					  catch (PDOException $e) {
 | 
				
			||||||
    $_SESSION['return'] = array(
 | 
					    $_SESSION['return'] = array(
 | 
				
			||||||
 | 
				
			|||||||
@ -16,11 +16,10 @@
 | 
				
			|||||||
<link rel="stylesheet" href="/css/bootstrap-slider.min.css">
 | 
					<link rel="stylesheet" href="/css/bootstrap-slider.min.css">
 | 
				
			||||||
<link rel="stylesheet" href="/css/bootstrap-switch.min.css">
 | 
					<link rel="stylesheet" href="/css/bootstrap-switch.min.css">
 | 
				
			||||||
<link rel="stylesheet" href="/css/footable.bootstrap.min.css">
 | 
					<link rel="stylesheet" href="/css/footable.bootstrap.min.css">
 | 
				
			||||||
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Source+Sans+Pro:400,600,700&subset=latin,latin-ext">
 | 
					 | 
				
			||||||
<link rel="stylesheet" href="/inc/languages.min.css">
 | 
					<link rel="stylesheet" href="/inc/languages.min.css">
 | 
				
			||||||
<link rel="stylesheet" href="/css/mailcow.css">
 | 
					<link rel="stylesheet" href="/css/mailcow.css">
 | 
				
			||||||
<link rel="stylesheet" href="/css/tables.css">
 | 
					 | 
				
			||||||
<?=(preg_match("/mailbox.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/mailbox.css">' : null;?>
 | 
					<?=(preg_match("/mailbox.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/mailbox.css">' : null;?>
 | 
				
			||||||
 | 
					<?=(preg_match("/admin.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/admin.css">' : null;?>
 | 
				
			||||||
<link rel="shortcut icon" href="/favicon.png" type="image/png">
 | 
					<link rel="shortcut icon" href="/favicon.png" type="image/png">
 | 
				
			||||||
<link rel="icon" href="/favicon.png" type="image/png">
 | 
					<link rel="icon" href="/favicon.png" type="image/png">
 | 
				
			||||||
</head>
 | 
					</head>
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@ $(document).ready(function() {
 | 
				
			|||||||
	// add.php
 | 
						// add.php
 | 
				
			||||||
	// Get max. possible quota for a domain when domain field changes
 | 
						// Get max. possible quota for a domain when domain field changes
 | 
				
			||||||
	$('#addSelectDomain').on('change', function() {
 | 
						$('#addSelectDomain').on('change', function() {
 | 
				
			||||||
		$.get("json_api.php", { action:"get_domain_details", object:this.value }, function(data){
 | 
							$.get("/api/v1/domain/" + this.value, function(data){
 | 
				
			||||||
      var result = jQuery.parseJSON( data );
 | 
					      var result = jQuery.parseJSON( data );
 | 
				
			||||||
      max_new_mailbox_quota = ( result.max_new_mailbox_quota / 1048576);
 | 
					      max_new_mailbox_quota = ( result.max_new_mailbox_quota / 1048576);
 | 
				
			||||||
			if (max_new_mailbox_quota != '0') {
 | 
								if (max_new_mailbox_quota != '0') {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,31 +1,42 @@
 | 
				
			|||||||
$(document).ready(function() {
 | 
					$(document).ready(function() {
 | 
				
			||||||
	// Postfix restrictions, drag and drop functions
 | 
					  $.ajax({
 | 
				
			||||||
	$( "[id*=srr-sortable]" ).sortable({
 | 
					    dataType: 'json',
 | 
				
			||||||
		items: "li:not(.list-heading)",
 | 
					    url: '/api/v1/domain-admin/all',
 | 
				
			||||||
		cancel: ".ui-state-disabled",
 | 
					    jsonp: false,
 | 
				
			||||||
		connectWith: "[id*=srr-sortable]",
 | 
					    error: function () {
 | 
				
			||||||
		dropOnEmpty: true,
 | 
					      alert('Cannot draw domain administrator table');
 | 
				
			||||||
		placeholder: "ui-state-highlight"
 | 
					    },
 | 
				
			||||||
 | 
					    success: function (data) {
 | 
				
			||||||
 | 
					      $.each(data, function (i, item) {
 | 
				
			||||||
 | 
					        item.action = '<div class="btn-group">' +
 | 
				
			||||||
 | 
					          '<a href="/edit.php?domainadmin=' + encodeURI(item.username) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
 | 
				
			||||||
 | 
					          '<a href="/delete.php?domainadmin=' + encodeURI(item.username) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
 | 
				
			||||||
 | 
										'</div>';
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
	$( "[id*=ssr-sortable]" ).sortable({
 | 
					      $('#domainadminstable').footable({
 | 
				
			||||||
		items: "li:not(.list-heading)",
 | 
					        "columns": [
 | 
				
			||||||
		cancel: ".ui-state-disabled",
 | 
					          {"sorted": true,"name":"username","title":lang.username,"style":{"width":"250px"}},
 | 
				
			||||||
		connectWith: "[id*=ssr-sortable]",
 | 
					          {"name":"selected_domains","title":lang.admin_domains,"breakpoints":"xs sm"},
 | 
				
			||||||
		dropOnEmpty: true,
 | 
					          {"name":"tfa_active","title":"TFA", "filterable": false,"style":{"maxWidth":"80px","width":"80px"}},
 | 
				
			||||||
		placeholder: "ui-state-highlight"
 | 
					          {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active},
 | 
				
			||||||
 | 
					          {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "rows": data,
 | 
				
			||||||
 | 
					        "empty": lang.empty,
 | 
				
			||||||
 | 
					        "paging": {
 | 
				
			||||||
 | 
					          "enabled": true,
 | 
				
			||||||
 | 
					          "limit": 5,
 | 
				
			||||||
 | 
					          "size": pagination_size
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "filtering": {
 | 
				
			||||||
 | 
					          "enabled": true,
 | 
				
			||||||
 | 
					          "position": "left",
 | 
				
			||||||
 | 
					          "placeholder": lang.filter_table
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "sorting": {
 | 
				
			||||||
 | 
					          "enabled": true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
	$('#srr_form').submit(function(){
 | 
					    }
 | 
				
			||||||
		var srr_joined_vals = $("[id^=srr-sortable-active] li").map(function() {
 | 
					 | 
				
			||||||
			return $(this).data("value");
 | 
					 | 
				
			||||||
		}).get().join(', ');
 | 
					 | 
				
			||||||
		var input = $("<input>").attr("type", "hidden").attr("name", "srr_value").val(srr_joined_vals);
 | 
					 | 
				
			||||||
		$('#srr_form').append($(input));
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
	$('#ssr_form').submit(function(){
 | 
					 | 
				
			||||||
		var ssr_joined_vals = $("[id^=ssr-sortable-active] li").map(function() {
 | 
					 | 
				
			||||||
			return $(this).data("value");
 | 
					 | 
				
			||||||
		}).get().join(', ');
 | 
					 | 
				
			||||||
		var input = $("<input>").attr("type", "hidden").attr("name", "ssr_value").val(ssr_joined_vals);
 | 
					 | 
				
			||||||
		$('#ssr_form').append($(input));
 | 
					 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@ -15,7 +15,7 @@ $(document).ready(function() {
 | 
				
			|||||||
  
 | 
					  
 | 
				
			||||||
  $.ajax({
 | 
					  $.ajax({
 | 
				
			||||||
    dataType: 'json',
 | 
					    dataType: 'json',
 | 
				
			||||||
    url: '/json_api.php?action=domain_table_data',
 | 
					    url: '/api/v1/domain/all',
 | 
				
			||||||
    jsonp: false,
 | 
					    jsonp: false,
 | 
				
			||||||
    error: function () {
 | 
					    error: function () {
 | 
				
			||||||
      alert('Cannot draw domain table');
 | 
					      alert('Cannot draw domain table');
 | 
				
			||||||
@ -70,7 +70,7 @@ $(document).ready(function() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  $.ajax({
 | 
					  $.ajax({
 | 
				
			||||||
    dataType: 'json',
 | 
					    dataType: 'json',
 | 
				
			||||||
    url: '/json_api.php?action=mailbox_table_data',
 | 
					    url: '/api/v1/mailbox/all',
 | 
				
			||||||
    jsonp: false,
 | 
					    jsonp: false,
 | 
				
			||||||
    error: function () {
 | 
					    error: function () {
 | 
				
			||||||
      alert('Cannot draw mailbox table');
 | 
					      alert('Cannot draw mailbox table');
 | 
				
			||||||
@ -102,12 +102,12 @@ $(document).ready(function() {
 | 
				
			|||||||
          {"sorted": true,"name":"username","title":lang.username,"style":{"width":"250px"}},
 | 
					          {"sorted": true,"name":"username","title":lang.username,"style":{"width":"250px"}},
 | 
				
			||||||
          {"name":"name","title":lang.fname,"breakpoints":"xs sm"},
 | 
					          {"name":"name","title":lang.fname,"breakpoints":"xs sm"},
 | 
				
			||||||
          {"name":"domain","title":lang.domain,"breakpoints":"xs sm"},
 | 
					          {"name":"domain","title":lang.domain,"breakpoints":"xs sm"},
 | 
				
			||||||
          {"name":"quota","title":lang.domain_quota},
 | 
					          {"name":"quota","style":{"whiteSpace":"nowrap"},"title":lang.domain_quota},
 | 
				
			||||||
          {"name":"spam_aliases","filterable": false,"title":lang.spam_aliases,"breakpoints":"xs sm"},
 | 
					          {"name":"spam_aliases","filterable": false,"title":lang.spam_aliases,"breakpoints":"xs sm md"},
 | 
				
			||||||
          {"name":"in_use","filterable": false,"type":"html","title":lang.in_use},
 | 
					          {"name":"in_use","filterable": false,"style":{"whiteSpace":"nowrap"},"type":"html","title":lang.in_use},
 | 
				
			||||||
          {"name":"messages","filterable": false,"style":{"width":"90px"},"title":lang.msg_num,"breakpoints":"xs sm"},
 | 
					          {"name":"messages","filterable": false,"style":{"whiteSpace":"nowrap"},"title":lang.msg_num,"breakpoints":"xs sm md"},
 | 
				
			||||||
          {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active},
 | 
					          {"name":"active","filterable": false,"style":{"whiteSpace":"nowrap"},"title":lang.active},
 | 
				
			||||||
          {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","width":"290px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
 | 
					          {"name":"action","filterable": false,"sortable": false,"style":{"whiteSpace":"nowrap","text-align":"right","width":"290px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        "empty": lang.empty,
 | 
					        "empty": lang.empty,
 | 
				
			||||||
        "rows": data,
 | 
					        "rows": data,
 | 
				
			||||||
@ -130,7 +130,7 @@ $(document).ready(function() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  $.ajax({
 | 
					  $.ajax({
 | 
				
			||||||
    dataType: 'json',
 | 
					    dataType: 'json',
 | 
				
			||||||
    url: '/json_api.php?action=resource_table_data',
 | 
					    url: '/api/v1/resource/all',
 | 
				
			||||||
    jsonp: false,
 | 
					    jsonp: false,
 | 
				
			||||||
    error: function () {
 | 
					    error: function () {
 | 
				
			||||||
      alert('Cannot draw resource table');
 | 
					      alert('Cannot draw resource table');
 | 
				
			||||||
@ -172,7 +172,7 @@ $(document).ready(function() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  $.ajax({
 | 
					  $.ajax({
 | 
				
			||||||
    dataType: 'json',
 | 
					    dataType: 'json',
 | 
				
			||||||
    url: '/json_api.php?action=domain_alias_table_data',
 | 
					    url: '/api/v1/alias-domain/all',
 | 
				
			||||||
    jsonp: false,
 | 
					    jsonp: false,
 | 
				
			||||||
    error: function () {
 | 
					    error: function () {
 | 
				
			||||||
      alert('Cannot draw alias domain table');
 | 
					      alert('Cannot draw alias domain table');
 | 
				
			||||||
@ -212,7 +212,7 @@ $(document).ready(function() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  $.ajax({
 | 
					  $.ajax({
 | 
				
			||||||
    dataType: 'json',
 | 
					    dataType: 'json',
 | 
				
			||||||
    url: '/json_api.php?action=alias_table_data',
 | 
					    url: '/api/v1/alias/all',
 | 
				
			||||||
    jsonp: false,
 | 
					    jsonp: false,
 | 
				
			||||||
    error: function () {
 | 
					    error: function () {
 | 
				
			||||||
      alert('Cannot draw alias table');
 | 
					      alert('Cannot draw alias table');
 | 
				
			||||||
 | 
				
			|||||||
@ -1,236 +0,0 @@
 | 
				
			|||||||
(function() {
 | 
					 | 
				
			||||||
  var SELECTOR, addEventListener, clickEvents, numberRegExp, sortable, touchDevice, trimRegExp;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  SELECTOR = 'table[data-sortable]';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  numberRegExp = /^-?[£$¤]?[\d,.]+%?$/;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  trimRegExp = /^\s+|\s+$/g;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  clickEvents = ['click'];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  touchDevice = 'ontouchstart' in document.documentElement;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (touchDevice) {
 | 
					 | 
				
			||||||
    clickEvents.push('touchstart');
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  addEventListener = function(el, event, handler) {
 | 
					 | 
				
			||||||
    if (el.addEventListener != null) {
 | 
					 | 
				
			||||||
      return el.addEventListener(event, handler, false);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      return el.attachEvent("on" + event, handler);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  sortable = {
 | 
					 | 
				
			||||||
    init: function(options) {
 | 
					 | 
				
			||||||
      var table, tables, _i, _len, _results;
 | 
					 | 
				
			||||||
      if (options == null) {
 | 
					 | 
				
			||||||
        options = {};
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      if (options.selector == null) {
 | 
					 | 
				
			||||||
        options.selector = SELECTOR;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      tables = document.querySelectorAll(options.selector);
 | 
					 | 
				
			||||||
      _results = [];
 | 
					 | 
				
			||||||
      for (_i = 0, _len = tables.length; _i < _len; _i++) {
 | 
					 | 
				
			||||||
        table = tables[_i];
 | 
					 | 
				
			||||||
        _results.push(sortable.initTable(table));
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return _results;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    initTable: function(table) {
 | 
					 | 
				
			||||||
      var i, th, ths, _i, _len, _ref;
 | 
					 | 
				
			||||||
      if (((_ref = table.tHead) != null ? _ref.rows.length : void 0) !== 1) {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      if (table.getAttribute('data-sortable-initialized') === 'true') {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      table.setAttribute('data-sortable-initialized', 'true');
 | 
					 | 
				
			||||||
      ths = table.querySelectorAll('th');
 | 
					 | 
				
			||||||
      for (i = _i = 0, _len = ths.length; _i < _len; i = ++_i) {
 | 
					 | 
				
			||||||
        th = ths[i];
 | 
					 | 
				
			||||||
        if (th.getAttribute('data-sortable') !== 'false') {
 | 
					 | 
				
			||||||
          sortable.setupClickableTH(table, th, i);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return table;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    setupClickableTH: function(table, th, i) {
 | 
					 | 
				
			||||||
      var eventName, onClick, type, _i, _len, _results;
 | 
					 | 
				
			||||||
      type = sortable.getColumnType(table, i);
 | 
					 | 
				
			||||||
      onClick = function(e) {
 | 
					 | 
				
			||||||
        var compare, item, newSortedDirection, position, row, rowArray, sorted, sortedDirection, tBody, ths, value, _compare, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _m, _ref, _ref1;
 | 
					 | 
				
			||||||
        if (e.handled !== true) {
 | 
					 | 
				
			||||||
          e.handled = true;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        sorted = this.getAttribute('data-sorted') === 'true';
 | 
					 | 
				
			||||||
        sortedDirection = this.getAttribute('data-sorted-direction');
 | 
					 | 
				
			||||||
        if (sorted) {
 | 
					 | 
				
			||||||
          newSortedDirection = sortedDirection === 'ascending' ? 'descending' : 'ascending';
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          newSortedDirection = type.defaultSortDirection;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        ths = this.parentNode.querySelectorAll('th');
 | 
					 | 
				
			||||||
        for (_i = 0, _len = ths.length; _i < _len; _i++) {
 | 
					 | 
				
			||||||
          th = ths[_i];
 | 
					 | 
				
			||||||
          th.setAttribute('data-sorted', 'false');
 | 
					 | 
				
			||||||
          th.removeAttribute('data-sorted-direction');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        this.setAttribute('data-sorted', 'true');
 | 
					 | 
				
			||||||
        this.setAttribute('data-sorted-direction', newSortedDirection);
 | 
					 | 
				
			||||||
        tBody = table.tBodies[0];
 | 
					 | 
				
			||||||
        rowArray = [];
 | 
					 | 
				
			||||||
        if (!sorted) {
 | 
					 | 
				
			||||||
          if (type.compare != null) {
 | 
					 | 
				
			||||||
            _compare = type.compare;
 | 
					 | 
				
			||||||
          } else {
 | 
					 | 
				
			||||||
            _compare = function(a, b) {
 | 
					 | 
				
			||||||
              return b - a;
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          compare = function(a, b) {
 | 
					 | 
				
			||||||
            if (a[0] === b[0]) {
 | 
					 | 
				
			||||||
              return a[2] - b[2];
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (type.reverse) {
 | 
					 | 
				
			||||||
              return _compare(b[0], a[0]);
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
              return _compare(a[0], b[0]);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          };
 | 
					 | 
				
			||||||
          _ref = tBody.rows;
 | 
					 | 
				
			||||||
          for (position = _j = 0, _len1 = _ref.length; _j < _len1; position = ++_j) {
 | 
					 | 
				
			||||||
            row = _ref[position];
 | 
					 | 
				
			||||||
            value = sortable.getNodeValue(row.cells[i]);
 | 
					 | 
				
			||||||
            if (type.comparator != null) {
 | 
					 | 
				
			||||||
              value = type.comparator(value);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            rowArray.push([value, row, position]);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          rowArray.sort(compare);
 | 
					 | 
				
			||||||
          for (_k = 0, _len2 = rowArray.length; _k < _len2; _k++) {
 | 
					 | 
				
			||||||
            row = rowArray[_k];
 | 
					 | 
				
			||||||
            tBody.appendChild(row[1]);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          _ref1 = tBody.rows;
 | 
					 | 
				
			||||||
          for (_l = 0, _len3 = _ref1.length; _l < _len3; _l++) {
 | 
					 | 
				
			||||||
            item = _ref1[_l];
 | 
					 | 
				
			||||||
            rowArray.push(item);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          rowArray.reverse();
 | 
					 | 
				
			||||||
          for (_m = 0, _len4 = rowArray.length; _m < _len4; _m++) {
 | 
					 | 
				
			||||||
            row = rowArray[_m];
 | 
					 | 
				
			||||||
            tBody.appendChild(row);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (typeof window['CustomEvent'] === 'function') {
 | 
					 | 
				
			||||||
          return typeof table.dispatchEvent === "function" ? table.dispatchEvent(new CustomEvent('Sortable.sorted', {
 | 
					 | 
				
			||||||
            bubbles: true
 | 
					 | 
				
			||||||
          })) : void 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      _results = [];
 | 
					 | 
				
			||||||
      for (_i = 0, _len = clickEvents.length; _i < _len; _i++) {
 | 
					 | 
				
			||||||
        eventName = clickEvents[_i];
 | 
					 | 
				
			||||||
        _results.push(addEventListener(th, eventName, onClick));
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return _results;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    getColumnType: function(table, i) {
 | 
					 | 
				
			||||||
      var row, specified, text, type, _i, _j, _len, _len1, _ref, _ref1, _ref2;
 | 
					 | 
				
			||||||
      specified = (_ref = table.querySelectorAll('th')[i]) != null ? _ref.getAttribute('data-sortable-type') : void 0;
 | 
					 | 
				
			||||||
      if (specified != null) {
 | 
					 | 
				
			||||||
        return sortable.typesObject[specified];
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      _ref1 = table.tBodies[0].rows;
 | 
					 | 
				
			||||||
      for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
 | 
					 | 
				
			||||||
        row = _ref1[_i];
 | 
					 | 
				
			||||||
        text = sortable.getNodeValue(row.cells[i]);
 | 
					 | 
				
			||||||
        _ref2 = sortable.types;
 | 
					 | 
				
			||||||
        for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
 | 
					 | 
				
			||||||
          type = _ref2[_j];
 | 
					 | 
				
			||||||
          if (type.match(text)) {
 | 
					 | 
				
			||||||
            return type;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return sortable.typesObject.alpha;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    getNodeValue: function(node) {
 | 
					 | 
				
			||||||
      var dataValue;
 | 
					 | 
				
			||||||
      if (!node) {
 | 
					 | 
				
			||||||
        return '';
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      dataValue = node.getAttribute('data-value');
 | 
					 | 
				
			||||||
      if (dataValue !== null) {
 | 
					 | 
				
			||||||
        return dataValue;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      if (typeof node.innerText !== 'undefined') {
 | 
					 | 
				
			||||||
        return node.innerText.replace(trimRegExp, '');
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return node.textContent.replace(trimRegExp, '');
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    setupTypes: function(types) {
 | 
					 | 
				
			||||||
      var type, _i, _len, _results;
 | 
					 | 
				
			||||||
      sortable.types = types;
 | 
					 | 
				
			||||||
      sortable.typesObject = {};
 | 
					 | 
				
			||||||
      _results = [];
 | 
					 | 
				
			||||||
      for (_i = 0, _len = types.length; _i < _len; _i++) {
 | 
					 | 
				
			||||||
        type = types[_i];
 | 
					 | 
				
			||||||
        _results.push(sortable.typesObject[type.name] = type);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return _results;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  sortable.setupTypes([
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'numeric',
 | 
					 | 
				
			||||||
      defaultSortDirection: 'descending',
 | 
					 | 
				
			||||||
      match: function(a) {
 | 
					 | 
				
			||||||
        return a.match(numberRegExp);
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      comparator: function(a) {
 | 
					 | 
				
			||||||
        return parseFloat(a.replace(/[^0-9.-]/g, ''), 10) || 0;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
      name: 'date',
 | 
					 | 
				
			||||||
      defaultSortDirection: 'ascending',
 | 
					 | 
				
			||||||
      reverse: true,
 | 
					 | 
				
			||||||
      match: function(a) {
 | 
					 | 
				
			||||||
        return !isNaN(Date.parse(a));
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      comparator: function(a) {
 | 
					 | 
				
			||||||
        return Date.parse(a) || 0;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
      name: 'alpha',
 | 
					 | 
				
			||||||
      defaultSortDirection: 'ascending',
 | 
					 | 
				
			||||||
      match: function() {
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      compare: function(a, b) {
 | 
					 | 
				
			||||||
        return a.localeCompare(b);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  setTimeout(sortable.init, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (typeof define === 'function' && define.amd) {
 | 
					 | 
				
			||||||
    define(function() {
 | 
					 | 
				
			||||||
      return sortable;
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  } else if (typeof exports !== 'undefined') {
 | 
					 | 
				
			||||||
    module.exports = sortable;
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    window.Sortable = sortable;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}).call(this);
 | 
					 | 
				
			||||||
@ -2,10 +2,13 @@
 | 
				
			|||||||
require_once 'inc/prerequisites.inc.php';
 | 
					require_once 'inc/prerequisites.inc.php';
 | 
				
			||||||
error_reporting(E_ALL);
 | 
					error_reporting(E_ALL);
 | 
				
			||||||
if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_username'])) {
 | 
					if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_username'])) {
 | 
				
			||||||
  if (isset($_GET['action'])) {
 | 
					  if (isset($_GET['action']) && isset($_GET['object'])) {
 | 
				
			||||||
    $action = $_GET['action'];
 | 
					    $action = filter_input(INPUT_GET, 'action',  FILTER_SANITIZE_STRING);
 | 
				
			||||||
 | 
					    $object   = filter_input(INPUT_GET, 'object',  FILTER_SANITIZE_STRING);
 | 
				
			||||||
    switch ($action) {
 | 
					    switch ($action) {
 | 
				
			||||||
      case "domain_table_data":
 | 
					      case "domain":
 | 
				
			||||||
 | 
					        switch ($object) {
 | 
				
			||||||
 | 
					          case "all":
 | 
				
			||||||
            $domains = mailbox_get_domains();
 | 
					            $domains = mailbox_get_domains();
 | 
				
			||||||
            if (!empty($domains)) {
 | 
					            if (!empty($domains)) {
 | 
				
			||||||
              foreach ($domains as $domain) {
 | 
					              foreach ($domains as $domain) {
 | 
				
			||||||
@ -22,7 +25,21 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
 | 
				
			|||||||
              echo '{}';
 | 
					              echo '{}';
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          break;
 | 
					          break;
 | 
				
			||||||
      case "mailbox_table_data":
 | 
					
 | 
				
			||||||
 | 
					          default:
 | 
				
			||||||
 | 
					            $data = mailbox_get_domain_details($object);
 | 
				
			||||||
 | 
					            if (!isset($data) || empty($data)) {
 | 
				
			||||||
 | 
					              echo '{}';
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					              echo json_encode(mailbox_get_domain_details($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      case "mailbox":
 | 
				
			||||||
 | 
					        switch ($object) {
 | 
				
			||||||
 | 
					          case "all":
 | 
				
			||||||
            $domains = mailbox_get_domains();
 | 
					            $domains = mailbox_get_domains();
 | 
				
			||||||
            if (!empty($domains)) {
 | 
					            if (!empty($domains)) {
 | 
				
			||||||
              foreach ($domains as $domain) {
 | 
					              foreach ($domains as $domain) {
 | 
				
			||||||
@ -44,7 +61,22 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
 | 
				
			|||||||
              echo '{}';
 | 
					              echo '{}';
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          break;
 | 
					          break;
 | 
				
			||||||
      case "resource_table_data":
 | 
					
 | 
				
			||||||
 | 
					          default:
 | 
				
			||||||
 | 
					            $data = mailbox_get_mailbox_details($object);
 | 
				
			||||||
 | 
					            if (!isset($data) || empty($data)) {
 | 
				
			||||||
 | 
					              echo '{}';
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					              echo json_encode(mailbox_get_mailbox_details($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      case "resource":
 | 
				
			||||||
 | 
					        switch ($object) {
 | 
				
			||||||
 | 
					          case "all":
 | 
				
			||||||
            $domains = mailbox_get_domains();
 | 
					            $domains = mailbox_get_domains();
 | 
				
			||||||
            if (!empty($domains)) {
 | 
					            if (!empty($domains)) {
 | 
				
			||||||
              foreach ($domains as $domain) {
 | 
					              foreach ($domains as $domain) {
 | 
				
			||||||
@ -66,7 +98,22 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
 | 
				
			|||||||
              echo '{}';
 | 
					              echo '{}';
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          break;
 | 
					          break;
 | 
				
			||||||
      case "domain_alias_table_data":
 | 
					
 | 
				
			||||||
 | 
					          default:
 | 
				
			||||||
 | 
					            $data = mailbox_get_resource_details($object);
 | 
				
			||||||
 | 
					            if (!isset($data) || empty($data)) {
 | 
				
			||||||
 | 
					              echo '{}';
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					              echo json_encode(mailbox_get_resource_details($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      case "alias-domain":
 | 
				
			||||||
 | 
					        switch ($object) {
 | 
				
			||||||
 | 
					          case "all":
 | 
				
			||||||
            $domains = mailbox_get_domains();
 | 
					            $domains = mailbox_get_domains();
 | 
				
			||||||
            if (!empty($domains)) {
 | 
					            if (!empty($domains)) {
 | 
				
			||||||
              foreach ($domains as $domain) {
 | 
					              foreach ($domains as $domain) {
 | 
				
			||||||
@ -88,7 +135,21 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
 | 
				
			|||||||
              echo '{}';
 | 
					              echo '{}';
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          break;
 | 
					          break;
 | 
				
			||||||
      case "alias_table_data":
 | 
					
 | 
				
			||||||
 | 
					          default:
 | 
				
			||||||
 | 
					            $data = mailbox_get_alias_domains($object);
 | 
				
			||||||
 | 
					            if (!isset($data) || empty($data)) {
 | 
				
			||||||
 | 
					              echo '{}';
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					              echo json_encode(mailbox_get_alias_domains($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      case "alias":
 | 
				
			||||||
 | 
					        switch ($object) {
 | 
				
			||||||
 | 
					          case "all":
 | 
				
			||||||
            $domains = array_merge(mailbox_get_domains(), mailbox_get_alias_domains());
 | 
					            $domains = array_merge(mailbox_get_domains(), mailbox_get_alias_domains());
 | 
				
			||||||
            if (!empty($domains)) {
 | 
					            if (!empty($domains)) {
 | 
				
			||||||
              foreach ($domains as $domain) {
 | 
					              foreach ($domains as $domain) {
 | 
				
			||||||
@ -110,55 +171,68 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
 | 
				
			|||||||
              echo '{}';
 | 
					              echo '{}';
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          break;
 | 
					          break;
 | 
				
			||||||
      case "get_mailbox_details":
 | 
					
 | 
				
			||||||
        if (!isset($_GET['object'])) { return false; }
 | 
					          default:
 | 
				
			||||||
        $object = $_GET['object'];
 | 
					            $data = mailbox_get_alias_details($object);
 | 
				
			||||||
        $data = mailbox_get_mailbox_details($object);
 | 
					 | 
				
			||||||
            if (!isset($data) || empty($data)) {
 | 
					            if (!isset($data) || empty($data)) {
 | 
				
			||||||
              echo '{}';
 | 
					              echo '{}';
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else {
 | 
					            else {
 | 
				
			||||||
          echo json_encode(mailbox_get_mailbox_details($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
 | 
					              echo json_encode(mailbox_get_alias_details($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          break;
 | 
					          break;
 | 
				
			||||||
      case "get_domain_details":
 | 
					        }
 | 
				
			||||||
        if (!isset($_GET['object'])) { return false; }
 | 
					      break;
 | 
				
			||||||
        $object = $_GET['object'];
 | 
					      case "domain-admin":
 | 
				
			||||||
        $data = mailbox_get_domain_details($object);
 | 
					        switch ($object) {
 | 
				
			||||||
 | 
					          case "all":
 | 
				
			||||||
 | 
					            $domain_admins = get_domain_admins();
 | 
				
			||||||
 | 
					            if (!empty($domain_admins)) {
 | 
				
			||||||
 | 
					              foreach ($domain_admins as $domain_admin) {
 | 
				
			||||||
 | 
					                $data[] = get_domain_admin_details($domain_admin);
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
              if (!isset($data) || empty($data)) {
 | 
					              if (!isset($data) || empty($data)) {
 | 
				
			||||||
                echo '{}';
 | 
					                echo '{}';
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
              else {
 | 
					              else {
 | 
				
			||||||
          echo json_encode(mailbox_get_domain_details($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
 | 
					                echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					              echo '{}';
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          break;
 | 
					          break;
 | 
				
			||||||
      case "get_u2f_reg_challenge":
 | 
					
 | 
				
			||||||
        if (!isset($_GET['object'])) { return false; }
 | 
					          default:
 | 
				
			||||||
        $object = $_GET['object'];
 | 
					            $data = get_domain_admin_details($object);
 | 
				
			||||||
        if (
 | 
					            if (!isset($data) || empty($data)) {
 | 
				
			||||||
          ($_SESSION["mailcow_cc_role"] == "admin" || $_SESSION["mailcow_cc_role"] == "domainadmin")
 | 
					              echo '{}';
 | 
				
			||||||
          &&
 | 
					            }
 | 
				
			||||||
          ($_SESSION["mailcow_cc_username"] == $object)
 | 
					            else {
 | 
				
			||||||
        ) {
 | 
					              echo json_encode(get_domain_admin_details($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      case "u2f-registration":
 | 
				
			||||||
 | 
					        if (($_SESSION["mailcow_cc_role"] == "admin" || $_SESSION["mailcow_cc_role"] == "domainadmin") && $_SESSION["mailcow_cc_username"] == $object) {
 | 
				
			||||||
          $data = $u2f->getRegisterData(get_u2f_registrations($object));
 | 
					          $data = $u2f->getRegisterData(get_u2f_registrations($object));
 | 
				
			||||||
          list($req, $sigs) = $data;
 | 
					          list($req, $sigs) = $data;
 | 
				
			||||||
          $_SESSION['regReq'] = json_encode($req);
 | 
					          $_SESSION['regReq'] = json_encode($req);
 | 
				
			||||||
          echo 'var req = ' . json_encode($req) . '; var sigs = ' . json_encode($sigs) . ';';
 | 
					          echo 'var req = ' . json_encode($req) . '; var sigs = ' . json_encode($sigs) . ';';
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else {
 | 
					        else {
 | 
				
			||||||
          echo '{}';
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
      case "get_u2f_auth_challenge":
 | 
					      case "u2f-authentication":
 | 
				
			||||||
        if (!isset($_GET['object'])) { return false; }
 | 
					 | 
				
			||||||
        $object = $_GET['object'];
 | 
					 | 
				
			||||||
        if (isset($_SESSION['pending_mailcow_cc_username']) && $_SESSION['pending_mailcow_cc_username'] == $object) {
 | 
					        if (isset($_SESSION['pending_mailcow_cc_username']) && $_SESSION['pending_mailcow_cc_username'] == $object) {
 | 
				
			||||||
          $reqs = json_encode($u2f->getAuthenticateData(get_u2f_registrations($object)));
 | 
					          $reqs = json_encode($u2f->getAuthenticateData(get_u2f_registrations($object)));
 | 
				
			||||||
          $_SESSION['authReq']  = $reqs;
 | 
					          $_SESSION['authReq']  = $reqs;
 | 
				
			||||||
          echo 'var req = ' . $reqs . ';';
 | 
					          echo 'var req = ' . $reqs . ';';
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else {
 | 
					        else {
 | 
				
			||||||
          echo '{}';
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
      default:
 | 
					      default:
 | 
				
			||||||
 | 
				
			|||||||
@ -36,6 +36,7 @@ $lang['danger']['object_exists'] = 'Objekt %s existiert bereits';
 | 
				
			|||||||
$lang['danger']['domain_exists'] = 'Domain %s existiert bereits';
 | 
					$lang['danger']['domain_exists'] = 'Domain %s existiert bereits';
 | 
				
			||||||
$lang['danger']['alias_goto_identical'] = 'Alias- und Ziel-Adresse dürfen nicht identisch sein';
 | 
					$lang['danger']['alias_goto_identical'] = 'Alias- und Ziel-Adresse dürfen nicht identisch sein';
 | 
				
			||||||
$lang['danger']['aliasd_targetd_identical'] = 'Alias-Domain darf nicht gleich Ziel-Domain sein';
 | 
					$lang['danger']['aliasd_targetd_identical'] = 'Alias-Domain darf nicht gleich Ziel-Domain sein';
 | 
				
			||||||
 | 
					$lang['danger']['maxquota_empty'] = 'Max. Speicherplatz pro Mailbox darf nicht 0 sein.';
 | 
				
			||||||
$lang['success']['alias_added'] = 'Alias-Adresse(n) wurden angelegt';
 | 
					$lang['success']['alias_added'] = 'Alias-Adresse(n) wurden angelegt';
 | 
				
			||||||
$lang['success']['alias_modified'] = 'Änderungen an Alias %s wurden gespeichert';
 | 
					$lang['success']['alias_modified'] = 'Änderungen an Alias %s wurden gespeichert';
 | 
				
			||||||
$lang['success']['aliasd_modified'] = 'Änderungen an Alias-Domain %s wurden gespeichert';
 | 
					$lang['success']['aliasd_modified'] = 'Änderungen an Alias-Domain %s wurden gespeichert';
 | 
				
			||||||
@ -70,6 +71,7 @@ $lang['danger']['is_spam_alias'] = '%s lautet bereits eine Spam-Alias-Adresse';
 | 
				
			|||||||
$lang['danger']['quota_not_0_not_numeric'] = 'Speicherplatz muss numerisch und >= 0 sein';
 | 
					$lang['danger']['quota_not_0_not_numeric'] = 'Speicherplatz muss numerisch und >= 0 sein';
 | 
				
			||||||
$lang['danger']['domain_not_found'] = 'Domain "%s" nicht gefunden.';
 | 
					$lang['danger']['domain_not_found'] = 'Domain "%s" nicht gefunden.';
 | 
				
			||||||
$lang['danger']['max_mailbox_exceeded'] = 'Anzahl an Mailboxen überschritten (%d von %d)';
 | 
					$lang['danger']['max_mailbox_exceeded'] = 'Anzahl an Mailboxen überschritten (%d von %d)';
 | 
				
			||||||
 | 
					$lang['danger']['max_alias_exceeded'] = 'Anzahl an Alias-Adressen überschritten';
 | 
				
			||||||
$lang['danger']['mailbox_quota_exceeded'] = 'Speicherplatz überschreitet das Limit (max. %d MiB)';
 | 
					$lang['danger']['mailbox_quota_exceeded'] = 'Speicherplatz überschreitet das Limit (max. %d MiB)';
 | 
				
			||||||
$lang['danger']['mailbox_quota_left_exceeded'] = 'Nicht genügend Speicherplatz vorhanden (Speicherplatz anwendbar: %d MiB)';
 | 
					$lang['danger']['mailbox_quota_left_exceeded'] = 'Nicht genügend Speicherplatz vorhanden (Speicherplatz anwendbar: %d MiB)';
 | 
				
			||||||
$lang['success']['mailbox_added'] = 'Mailbox %s wurde angelegt';
 | 
					$lang['success']['mailbox_added'] = 'Mailbox %s wurde angelegt';
 | 
				
			||||||
@ -145,7 +147,7 @@ $lang['user']['spamfilter_default_score'] = 'Standardwert:';
 | 
				
			|||||||
$lang['user']['spamfilter_hint'] = 'Der erste Wert beschreibt den "low spam score", der zweite Wert den "high spam score".';
 | 
					$lang['user']['spamfilter_hint'] = 'Der erste Wert beschreibt den "low spam score", der zweite Wert den "high spam score".';
 | 
				
			||||||
$lang['user']['spamfilter_table_domain_policy'] = "n.v. (Domainrichtlinie)";
 | 
					$lang['user']['spamfilter_table_domain_policy'] = "n.v. (Domainrichtlinie)";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$lang['user']['tls_policy_warning'] = '<strong>Vorsicht:</strong> Entscheiden Sie sich unverschlüsselte Verbindungen abzulehnen, kann dies dazu führen, dass Kontakte Sie nicht mehr erreichen.<br />Nachrichten, die die Richtlinie nicht erfüllen, werden durch einen Hard-Fail im Mailsystem abgewiesen.';
 | 
					$lang['user']['tls_policy_warning'] = '<strong>Vorsicht:</strong> Entscheiden Sie sich unverschlüsselte Verbindungen abzulehnen, kann dies dazu führen, dass Kontakte Sie nicht mehr erreichen.<br />Nachrichten, die die Richtlinie nicht erfüllen, werden durch einen Hard-Fail im Mailsystem abgewiesen.<br />Diese Einstellung ist aktiv für die primäre Mailbox, für alle Alias-Adressen, die dieser Mailbox <b>direkt zugeordnet</b> sind (lediglich eine einzige Ziel-Adresse) und der Adressen, die sich aus Alias-Domains ergeben. Ausgeschlossen sind temporäre Aliasse ("Spam-Alias-Adressen"), Catch-All Alias-Adressen sowie Alias-Adressen mit mehreren Zielen.';
 | 
				
			||||||
$lang['user']['tls_policy'] = 'Verschlüsselungsrichtlinie';
 | 
					$lang['user']['tls_policy'] = 'Verschlüsselungsrichtlinie';
 | 
				
			||||||
$lang['user']['tls_enforce_in'] = 'TLS eingehend erzwingen';
 | 
					$lang['user']['tls_enforce_in'] = 'TLS eingehend erzwingen';
 | 
				
			||||||
$lang['user']['tls_enforce_out'] = 'TLS ausgehend erzwingen';
 | 
					$lang['user']['tls_enforce_out'] = 'TLS ausgehend erzwingen';
 | 
				
			||||||
@ -246,6 +248,7 @@ $lang['mailbox']['add_domain_alias'] = 'Domain-Alias hinzufügen';
 | 
				
			|||||||
$lang['mailbox']['add_mailbox'] = 'Mailbox hinzufügen';
 | 
					$lang['mailbox']['add_mailbox'] = 'Mailbox hinzufügen';
 | 
				
			||||||
$lang['mailbox']['add_resource'] = 'Ressource hinzufügen';
 | 
					$lang['mailbox']['add_resource'] = 'Ressource hinzufügen';
 | 
				
			||||||
$lang['mailbox']['add_alias'] = 'Alias hinzufügen';
 | 
					$lang['mailbox']['add_alias'] = 'Alias hinzufügen';
 | 
				
			||||||
 | 
					$lang['mailbox']['empty'] = 'Keine Einträge vorhanden';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$lang['info']['no_action'] = 'Keine Aktion anwendbar';
 | 
					$lang['info']['no_action'] = 'Keine Aktion anwendbar';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -326,6 +329,9 @@ $lang['add']['subfolder2'] = 'Sync into subfolder on destination';
 | 
				
			|||||||
$lang['add']['mins_interval'] = 'Abrufintervall (Minuten)';
 | 
					$lang['add']['mins_interval'] = 'Abrufintervall (Minuten)';
 | 
				
			||||||
$lang['add']['exclude'] = 'Elemente ausschließen (Regex)';
 | 
					$lang['add']['exclude'] = 'Elemente ausschließen (Regex)';
 | 
				
			||||||
$lang['add']['delete2duplicates'] = 'Lösche Duplikate im Ziel';
 | 
					$lang['add']['delete2duplicates'] = 'Lösche Duplikate im Ziel';
 | 
				
			||||||
 | 
					$lang['add']['delete1'] = 'Lösche Nachricht nach Übertragung vom Quell-Server';
 | 
				
			||||||
 | 
					$lang['edit']['delete2duplicates'] = 'Lösche Duplikate im Ziel';
 | 
				
			||||||
 | 
					$lang['edit']['delete1'] = 'Lösche Nachricht nach Übertragung vom Quell-Server';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$lang['add']['title'] = 'Objekt anlegen';
 | 
					$lang['add']['title'] = 'Objekt anlegen';
 | 
				
			||||||
$lang['add']['domain'] = 'Domain';
 | 
					$lang['add']['domain'] = 'Domain';
 | 
				
			||||||
@ -449,4 +455,6 @@ $lang['admin']['site_not_found'] = 'Kann mailcow Site-Konfiguration nicht finden
 | 
				
			|||||||
$lang['admin']['public_folder_empty'] = 'Public folder name must not be empty'; // NEEDS TRANSLATION
 | 
					$lang['admin']['public_folder_empty'] = 'Public folder name must not be empty'; // NEEDS TRANSLATION
 | 
				
			||||||
$lang['admin']['set_rr_failed'] = 'Kann Postfix Restriktionen nicht setzen';
 | 
					$lang['admin']['set_rr_failed'] = 'Kann Postfix Restriktionen nicht setzen';
 | 
				
			||||||
$lang['admin']['no_record'] = 'Kein Eintrag';
 | 
					$lang['admin']['no_record'] = 'Kein Eintrag';
 | 
				
			||||||
 | 
					$lang['admin']['filter_table'] = 'Tabelle Filtern';
 | 
				
			||||||
 | 
					$lang['admin']['empty'] = 'Keine Einträge vorhanden';
 | 
				
			||||||
?>
 | 
					?>
 | 
				
			||||||
 | 
				
			|||||||
@ -38,6 +38,7 @@ $lang['danger']['object_exists'] = "Object %s already exists";
 | 
				
			|||||||
$lang['danger']['domain_exists'] = "Domain %s already exists";
 | 
					$lang['danger']['domain_exists'] = "Domain %s already exists";
 | 
				
			||||||
$lang['danger']['alias_goto_identical'] = "Alias and goto address must not be identical";
 | 
					$lang['danger']['alias_goto_identical'] = "Alias and goto address must not be identical";
 | 
				
			||||||
$lang['danger']['aliasd_targetd_identical'] = "Alias domain must not be equal to target domain";
 | 
					$lang['danger']['aliasd_targetd_identical'] = "Alias domain must not be equal to target domain";
 | 
				
			||||||
 | 
					$lang['danger']['maxquota_empty'] = 'Max. quota per mailbox must not be 0.';
 | 
				
			||||||
$lang['success']['alias_added'] = "Alias address/es has/have been added";
 | 
					$lang['success']['alias_added'] = "Alias address/es has/have been added";
 | 
				
			||||||
$lang['success']['alias_modified'] = "Changes to alias have been saved";
 | 
					$lang['success']['alias_modified'] = "Changes to alias have been saved";
 | 
				
			||||||
$lang['success']['aliasd_modified'] = "Changes to alias domain have been saved";
 | 
					$lang['success']['aliasd_modified'] = "Changes to alias domain have been saved";
 | 
				
			||||||
@ -72,6 +73,7 @@ $lang['danger']['is_spam_alias'] = "%s is already known as a spam alias address"
 | 
				
			|||||||
$lang['danger']['quota_not_0_not_numeric'] = "Quota must be numeric and >= 0";
 | 
					$lang['danger']['quota_not_0_not_numeric'] = "Quota must be numeric and >= 0";
 | 
				
			||||||
$lang['danger']['domain_not_found'] = "Domain not found.";
 | 
					$lang['danger']['domain_not_found'] = "Domain not found.";
 | 
				
			||||||
$lang['danger']['max_mailbox_exceeded'] = "Max. mailboxes exceeded (%d of %d)";
 | 
					$lang['danger']['max_mailbox_exceeded'] = "Max. mailboxes exceeded (%d of %d)";
 | 
				
			||||||
 | 
					$lang['danger']['max_alias_exceeded'] = 'Max. aliases exceeded';
 | 
				
			||||||
$lang['danger']['mailbox_quota_exceeded'] = "Quota exceeds the domain limit (max. %d MiB)";
 | 
					$lang['danger']['mailbox_quota_exceeded'] = "Quota exceeds the domain limit (max. %d MiB)";
 | 
				
			||||||
$lang['danger']['mailbox_quota_left_exceeded'] = "Not enough space left (space left: %d MiB)";
 | 
					$lang['danger']['mailbox_quota_left_exceeded'] = "Not enough space left (space left: %d MiB)";
 | 
				
			||||||
$lang['success']['mailbox_added'] = "Mailbox %s has been added";
 | 
					$lang['success']['mailbox_added'] = "Mailbox %s has been added";
 | 
				
			||||||
@ -147,7 +149,7 @@ $lang['user']['spamfilter_default_score'] = 'Default values:';
 | 
				
			|||||||
$lang['user']['spamfilter_hint'] = 'The first value describes the "low spam score", the second represents the "high spam score".';
 | 
					$lang['user']['spamfilter_hint'] = 'The first value describes the "low spam score", the second represents the "high spam score".';
 | 
				
			||||||
$lang['user']['spamfilter_table_domain_policy'] = "n/a (domain policy)";
 | 
					$lang['user']['spamfilter_table_domain_policy'] = "n/a (domain policy)";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$lang['user']['tls_policy_warning'] = '<strong>Warning:</strong> If you decide to enforce encrypted mail transfer, you may lose emails.<br />Messages to not satisfy the policy will be bounced with a hard fail by the mail system.';
 | 
					$lang['user']['tls_policy_warning'] = '<strong>Warning:</strong> If you decide to enforce encrypted mail transfer, you may lose emails.<br />Messages to not satisfy the policy will be bounced with a hard fail by the mail system.<br />This option applies to your primary email address (login name), all addresses derived from alias domains as well as alias addresses <b>with only this single mailbox</b> as target.';
 | 
				
			||||||
$lang['user']['tls_policy'] = 'Encryption policy';
 | 
					$lang['user']['tls_policy'] = 'Encryption policy';
 | 
				
			||||||
$lang['user']['tls_enforce_in'] = 'Enforce TLS incoming';
 | 
					$lang['user']['tls_enforce_in'] = 'Enforce TLS incoming';
 | 
				
			||||||
$lang['user']['tls_enforce_out'] = 'Enforce TLS outgoing';
 | 
					$lang['user']['tls_enforce_out'] = 'Enforce TLS outgoing';
 | 
				
			||||||
@ -249,6 +251,7 @@ $lang['mailbox']['add_mailbox'] = 'Add mailbox';
 | 
				
			|||||||
$lang['mailbox']['add_resource'] = 'Add resource';
 | 
					$lang['mailbox']['add_resource'] = 'Add resource';
 | 
				
			||||||
$lang['mailbox']['add_alias'] = 'Add alias';
 | 
					$lang['mailbox']['add_alias'] = 'Add alias';
 | 
				
			||||||
$lang['mailbox']['add_domain_record_first'] = 'Please add a domain first';
 | 
					$lang['mailbox']['add_domain_record_first'] = 'Please add a domain first';
 | 
				
			||||||
 | 
					$lang['mailbox']['empty'] = 'No results';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$lang['info']['no_action'] = 'No action applicable';
 | 
					$lang['info']['no_action'] = 'No action applicable';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -330,6 +333,9 @@ $lang['add']['maxage'] = 'Maximum age of messages that will be polled from remot
 | 
				
			|||||||
$lang['add']['subfolder2'] = 'Sync into subfolder on destination';
 | 
					$lang['add']['subfolder2'] = 'Sync into subfolder on destination';
 | 
				
			||||||
$lang['add']['exclude'] = 'Exclude objects (regex)';
 | 
					$lang['add']['exclude'] = 'Exclude objects (regex)';
 | 
				
			||||||
$lang['add']['delete2duplicates'] = 'Delete duplicates on destination';
 | 
					$lang['add']['delete2duplicates'] = 'Delete duplicates on destination';
 | 
				
			||||||
 | 
					$lang['add']['delete1'] = 'Delete from source when completed';
 | 
				
			||||||
 | 
					$lang['edit']['delete2duplicates'] = 'Delete duplicates on destination';
 | 
				
			||||||
 | 
					$lang['edit']['delete1'] = 'Delete from source when completed';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$lang['add']['title'] = 'Add object';
 | 
					$lang['add']['title'] = 'Add object';
 | 
				
			||||||
$lang['add']['domain'] = 'Domain';
 | 
					$lang['add']['domain'] = 'Domain';
 | 
				
			||||||
@ -460,4 +466,6 @@ $lang['admin']['site_not_found'] = 'Cannot locate mailcow site configuration';
 | 
				
			|||||||
$lang['admin']['public_folder_empty'] = 'Public folder name must not be empty';
 | 
					$lang['admin']['public_folder_empty'] = 'Public folder name must not be empty';
 | 
				
			||||||
$lang['admin']['set_rr_failed'] = 'Cannot set Postfix restrictions';
 | 
					$lang['admin']['set_rr_failed'] = 'Cannot set Postfix restrictions';
 | 
				
			||||||
$lang['admin']['no_record'] = 'No record';
 | 
					$lang['admin']['no_record'] = 'No record';
 | 
				
			||||||
 | 
					$lang['admin']['filter_table'] = 'Filter table';
 | 
				
			||||||
 | 
					$lang['admin']['empty'] = 'No results';
 | 
				
			||||||
?>
 | 
					?>
 | 
				
			||||||
 | 
				
			|||||||
@ -321,6 +321,7 @@ $lang['add']['maxage'] = 'Maximum age of messages that will be polled from remot
 | 
				
			|||||||
$lang['add']['subfolder2'] = "Синхронизировать в подпапку по назначению";
 | 
					$lang['add']['subfolder2'] = "Синхронизировать в подпапку по назначению";
 | 
				
			||||||
$lang['add']['exclude'] = "Исключить объекты (regex)";
 | 
					$lang['add']['exclude'] = "Исключить объекты (regex)";
 | 
				
			||||||
$lang['add']['delete2duplicates'] = "Удалить дубликаты в получателях";
 | 
					$lang['add']['delete2duplicates'] = "Удалить дубликаты в получателях";
 | 
				
			||||||
 | 
					$lang['edit']['delete2duplicates'] = "Удалить дубликаты в получателях";
 | 
				
			||||||
$lang['add']['title'] = "Добавить объект";
 | 
					$lang['add']['title'] = "Добавить объект";
 | 
				
			||||||
$lang['add']['domain'] = "Домен";
 | 
					$lang['add']['domain'] = "Домен";
 | 
				
			||||||
$lang['add']['active'] = "Активный";
 | 
					$lang['add']['active'] = "Активный";
 | 
				
			||||||
 | 
				
			|||||||
@ -5,33 +5,11 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
 | 
				
			|||||||
require_once "inc/header.inc.php";
 | 
					require_once "inc/header.inc.php";
 | 
				
			||||||
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
 | 
					$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
 | 
				
			||||||
?>
 | 
					?>
 | 
				
			||||||
<style>
 | 
					 | 
				
			||||||
table.footable>tbody>tr.footable-empty>td {
 | 
					 | 
				
			||||||
  font-size:15px !important;
 | 
					 | 
				
			||||||
  font-style:italic;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.pagination a {
 | 
					 | 
				
			||||||
  text-decoration: none !important;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.panel panel-default {
 | 
					 | 
				
			||||||
  overflow: visible !important;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.table-responsive {
 | 
					 | 
				
			||||||
  overflow: visible !important;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.footer-add-item {
 | 
					 | 
				
			||||||
  text-align:center;
 | 
					 | 
				
			||||||
  font-style: italic;
 | 
					 | 
				
			||||||
  display:block;
 | 
					 | 
				
			||||||
  padding: 10px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
<div class="container">
 | 
					<div class="container">
 | 
				
			||||||
	<div class="row">
 | 
						<div class="row">
 | 
				
			||||||
		<div class="col-md-12">
 | 
							<div class="col-md-12">
 | 
				
			||||||
			<div class="panel panel-default">
 | 
								<div class="panel panel-default">
 | 
				
			||||||
				<div class="panel-heading">
 | 
									<div class="panel-heading">
 | 
				
			||||||
				<h3 class="panel-title"><?=$lang['mailbox']['domains'];?></h3>
 | 
					 | 
				
			||||||
				<div class="pull-right">
 | 
									<div class="pull-right">
 | 
				
			||||||
				<?php
 | 
									<?php
 | 
				
			||||||
				if ($_SESSION['mailcow_cc_role'] == "admin"):
 | 
									if ($_SESSION['mailcow_cc_role'] == "admin"):
 | 
				
			||||||
@ -41,6 +19,7 @@ table.footable>tbody>tr.footable-empty>td {
 | 
				
			|||||||
				endif;
 | 
									endif;
 | 
				
			||||||
				?>
 | 
									?>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
 | 
					        <h3 class="panel-title"><?=$lang['mailbox']['domains'];?></h3>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
        <div class="table-responsive">
 | 
					        <div class="table-responsive">
 | 
				
			||||||
          <table id="domain_table" class="table table-striped"></table>
 | 
					          <table id="domain_table" class="table table-striped"></table>
 | 
				
			||||||
@ -49,14 +28,15 @@ table.footable>tbody>tr.footable-empty>td {
 | 
				
			|||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div class="row">
 | 
						<div class="row">
 | 
				
			||||||
		<div class="col-md-12">
 | 
							<div class="col-md-12">
 | 
				
			||||||
			<div class="panel panel-default">
 | 
								<div class="panel panel-default">
 | 
				
			||||||
				<div class="panel-heading">
 | 
									<div class="panel-heading">
 | 
				
			||||||
					<h3 class="panel-title"><?=$lang['mailbox']['mailboxes'];?></h3>
 | 
					 | 
				
			||||||
					<div class="pull-right">
 | 
										<div class="pull-right">
 | 
				
			||||||
						<a href="/add.php?mailbox"><span class="glyphicon glyphicon-plus"></span></a>
 | 
											<a href="/add.php?mailbox"><span class="glyphicon glyphicon-plus"></span></a>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
 | 
										<h3 class="panel-title"><?=$lang['mailbox']['mailboxes'];?></h3>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
        <div class="table-responsive">
 | 
					        <div class="table-responsive">
 | 
				
			||||||
          <table id="mailbox_table" class="table table-striped"></table>
 | 
					          <table id="mailbox_table" class="table table-striped"></table>
 | 
				
			||||||
@ -65,14 +45,15 @@ table.footable>tbody>tr.footable-empty>td {
 | 
				
			|||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div class="row">
 | 
						<div class="row">
 | 
				
			||||||
		<div class="col-md-12">
 | 
							<div class="col-md-12">
 | 
				
			||||||
			<div class="panel panel-default">
 | 
								<div class="panel panel-default">
 | 
				
			||||||
				<div class="panel-heading">
 | 
									<div class="panel-heading">
 | 
				
			||||||
					<h3 class="panel-title"><?=$lang['mailbox']['resources'];?></h3>
 | 
					 | 
				
			||||||
					<div class="pull-right">
 | 
										<div class="pull-right">
 | 
				
			||||||
						<a href="/add.php?resource"><span class="glyphicon glyphicon-plus"></span></a>
 | 
											<a href="/add.php?resource"><span class="glyphicon glyphicon-plus"></span></a>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
 | 
										<h3 class="panel-title"><?=$lang['mailbox']['resources'];?></h3>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
        <div class="table-responsive">
 | 
					        <div class="table-responsive">
 | 
				
			||||||
          <table id="resources_table" class="table table-striped"></table>
 | 
					          <table id="resources_table" class="table table-striped"></table>
 | 
				
			||||||
@ -80,14 +61,15 @@ table.footable>tbody>tr.footable-empty>td {
 | 
				
			|||||||
        <span class="footer-add-item"><a href="/add.php?resource"><?=$lang['mailbox']['add_resource'];?></a></span>			</div>
 | 
					        <span class="footer-add-item"><a href="/add.php?resource"><?=$lang['mailbox']['add_resource'];?></a></span>			</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div class="row">
 | 
						<div class="row">
 | 
				
			||||||
		<div class="col-md-12">
 | 
							<div class="col-md-12">
 | 
				
			||||||
			<div class="panel panel-default">
 | 
								<div class="panel panel-default">
 | 
				
			||||||
				<div class="panel-heading">
 | 
									<div class="panel-heading">
 | 
				
			||||||
					<h3 class="panel-title"><?=$lang['mailbox']['domain_aliases'];?></h3>
 | 
					 | 
				
			||||||
					<div class="pull-right">
 | 
										<div class="pull-right">
 | 
				
			||||||
						<a href="/add.php?aliasdomain"><span class="glyphicon glyphicon-plus"></span></a>
 | 
											<a href="/add.php?aliasdomain"><span class="glyphicon glyphicon-plus"></span></a>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
 | 
										<h3 class="panel-title"><?=$lang['mailbox']['domain_aliases'];?></h3>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
        <div class="table-responsive">
 | 
					        <div class="table-responsive">
 | 
				
			||||||
          <table id="aliasdomain_table" class="table table-striped"></table>
 | 
					          <table id="aliasdomain_table" class="table table-striped"></table>
 | 
				
			||||||
@ -100,10 +82,10 @@ table.footable>tbody>tr.footable-empty>td {
 | 
				
			|||||||
		<div class="col-md-12">
 | 
							<div class="col-md-12">
 | 
				
			||||||
			<div class="panel panel-default">
 | 
								<div class="panel panel-default">
 | 
				
			||||||
				<div class="panel-heading">
 | 
									<div class="panel-heading">
 | 
				
			||||||
					<h3 class="panel-title"><?=$lang['mailbox']['aliases'];?></h3>
 | 
					 | 
				
			||||||
					<div class="pull-right">
 | 
										<div class="pull-right">
 | 
				
			||||||
						<a href="/add.php?alias"><span class="glyphicon glyphicon-plus"></span></a>
 | 
											<a href="/add.php?alias"><span class="glyphicon glyphicon-plus"></span></a>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
 | 
										<h3 class="panel-title"><?=$lang['mailbox']['aliases'];?></h3>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
        <div class="table-responsive">
 | 
					        <div class="table-responsive">
 | 
				
			||||||
          <table id="alias_table" class="table table-striped"></table>
 | 
					          <table id="alias_table" class="table table-striped"></table>
 | 
				
			||||||
 | 
				
			|||||||
@ -405,7 +405,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
 | 
				
			|||||||
	</div>
 | 
						</div>
 | 
				
			||||||
	<div role="tabpanel" class="tab-pane" id="Syncjobs">
 | 
						<div role="tabpanel" class="tab-pane" id="Syncjobs">
 | 
				
			||||||
		<div class="table-responsive">
 | 
							<div class="table-responsive">
 | 
				
			||||||
		<table class="table table-striped sortable-theme-bootstrap" data-sortable id="timelimitedaliases">
 | 
							<table class="table table-striped" id="timelimitedaliases">
 | 
				
			||||||
			<thead>
 | 
								<thead>
 | 
				
			||||||
			<tr>
 | 
								<tr>
 | 
				
			||||||
				<th class="sort-table" style="min-width: 96px;">Server:Port</th>
 | 
									<th class="sort-table" style="min-width: 96px;">Server:Port</th>
 | 
				
			||||||
@ -416,7 +416,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
 | 
				
			|||||||
				<th class="sort-table" style="min-width: 35px;"><?=$lang['user']['last_run'];?></th>
 | 
									<th class="sort-table" style="min-width: 35px;"><?=$lang['user']['last_run'];?></th>
 | 
				
			||||||
				<th class="sort-table" style="min-width: 35px;">Log</th>
 | 
									<th class="sort-table" style="min-width: 35px;">Log</th>
 | 
				
			||||||
				<th class="sort-table" style="max-width: 95px;"><?=$lang['user']['active'];?></th>
 | 
									<th class="sort-table" style="max-width: 95px;"><?=$lang['user']['active'];?></th>
 | 
				
			||||||
				<th style="text-align: right; min-width: 200px;" data-sortable="false"><?=$lang['user']['action'];?></th>
 | 
									<th style="text-align: right; min-width: 200px;"><?=$lang['user']['action'];?></th>
 | 
				
			||||||
			</tr>
 | 
								</tr>
 | 
				
			||||||
			</thead>
 | 
								</thead>
 | 
				
			||||||
			<tbody>
 | 
								<tbody>
 | 
				
			||||||
 | 
				
			|||||||
@ -151,10 +151,11 @@ services:
 | 
				
			|||||||
      depends_on:
 | 
					      depends_on:
 | 
				
			||||||
        - bind9-mailcow
 | 
					        - bind9-mailcow
 | 
				
			||||||
      volumes:
 | 
					      volumes:
 | 
				
			||||||
        - ./data/conf/dovecot:/etc/dovecot
 | 
					        - ./data/conf/dovecot:/usr/local/etc/dovecot
 | 
				
			||||||
        - ./data/assets/ssl:/etc/ssl/mail/:ro
 | 
					        - ./data/assets/ssl:/etc/ssl/mail/:ro
 | 
				
			||||||
        - ./data/conf/sogo/:/etc/sogo/
 | 
					        - ./data/conf/sogo/:/etc/sogo/
 | 
				
			||||||
        - vmail-vol-1:/var/vmail
 | 
					        - vmail-vol-1:/var/vmail
 | 
				
			||||||
 | 
					        - crypt-vol-1:/mail_crypt/
 | 
				
			||||||
      environment:
 | 
					      environment:
 | 
				
			||||||
        - DBNAME=${DBNAME}
 | 
					        - DBNAME=${DBNAME}
 | 
				
			||||||
        - DBUSER=${DBUSER}
 | 
					        - DBUSER=${DBUSER}
 | 
				
			||||||
@ -184,6 +185,7 @@ services:
 | 
				
			|||||||
        - ./data/conf/postfix:/opt/postfix/conf
 | 
					        - ./data/conf/postfix:/opt/postfix/conf
 | 
				
			||||||
        - ./data/assets/ssl:/etc/ssl/mail/:ro
 | 
					        - ./data/assets/ssl:/etc/ssl/mail/:ro
 | 
				
			||||||
        - postfix-vol-1:/var/spool/postfix
 | 
					        - postfix-vol-1:/var/spool/postfix
 | 
				
			||||||
 | 
					        - crypt-vol-1:/var/lib/zeyple
 | 
				
			||||||
      environment:
 | 
					      environment:
 | 
				
			||||||
        - DBNAME=${DBNAME}
 | 
					        - DBNAME=${DBNAME}
 | 
				
			||||||
        - DBUSER=${DBUSER}
 | 
					        - DBUSER=${DBUSER}
 | 
				
			||||||
@ -266,3 +268,4 @@ volumes:
 | 
				
			|||||||
  redis-vol-1:
 | 
					  redis-vol-1:
 | 
				
			||||||
  rspamd-vol-1:
 | 
					  rspamd-vol-1:
 | 
				
			||||||
  postfix-vol-1:
 | 
					  postfix-vol-1:
 | 
				
			||||||
 | 
					  crypt-vol-1:
 | 
				
			||||||
 | 
				
			|||||||
@ -136,6 +136,39 @@ server {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Optional: Setup a relayhost
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Insert these lines to `data/conf/postfix/main.cf`. "relayhost" does already exist (empty), just change its value.
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					relayhost = [your-relayhost]:587
 | 
				
			||||||
 | 
					smtp_sasl_password_maps = hash:/opt/postfix/conf/smarthost_passwd
 | 
				
			||||||
 | 
					smtp_sasl_auth_enable = yes
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Create the credentials file:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					echo "your-relayhost username:password" > data/conf/postfix/smarthost_passwd
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Run:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					docker-compose exec postfix-mailcow postmap /opt/postfix/conf/smarthost_passwd
 | 
				
			||||||
 | 
					docker-compose exec postfix-mailcow chown root:postfix /opt/postfix/conf/smarthost_passwd /opt/postfix/conf/smarthost_passwd.db
 | 
				
			||||||
 | 
					docker-compose exec postfix-mailcow chmod 660 /opt/postfix/conf/smarthost_passwd /opt/postfix/conf/smarthost_passwd.db
 | 
				
			||||||
 | 
					docker-compose exec postfix-mailcow postfix reload
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Install a local MTA
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The easiest option would be to disable the listener on port 25/tcp.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Postfix** users disable the listener by commenting the following line (starting with `smtp` or `25`) in `/etc/postfix/master.cf`:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					#smtp      inet  n       -       -       -       -       smtpd
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					Restart Postfix after applying your changes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Sender and receiver model
 | 
					## Sender and receiver model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
When a mailbox is created, a user is allowed to send mail from and receive mail for his own mailbox address.
 | 
					When a mailbox is created, a user is allowed to send mail from and receive mail for his own mailbox address.
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,7 @@
 | 
				
			|||||||
## Install mailcow
 | 
					## Install mailcow
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**WARNING**: Please use Ubuntu 16.04 instead of Debian 8 or [switch to the kernel 4.9 from jessie backports](https://packages.debian.org/jessie-backports/linux-image-amd64) because there is a bug (kernel panic) with the kernel 3.16 when running docker containers with healthchecks! Full details here: [github.com/docker/docker/issues/30402](https://github.com/docker/docker/issues/30402) and [forum.mailcow.email/t/solved-mailcow-docker-causes-kernel-panic-edit/448](https://forum.mailcow.email/t/solved-mailcow-docker-causes-kernel-panic-edit/448)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You need Docker and Docker Compose.
 | 
					You need Docker and Docker Compose.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1\. Learn how to install [Docker](https://docs.docker.com/engine/installation/linux/) and [Docker Compose](https://docs.docker.com/compose/install/).
 | 
					1\. Learn how to install [Docker](https://docs.docker.com/engine/installation/linux/) and [Docker Compose](https://docs.docker.com/compose/install/).
 | 
				
			||||||
@ -35,6 +37,8 @@ nano mailcow.conf
 | 
				
			|||||||
```
 | 
					```
 | 
				
			||||||
If you plan to use a reverse proxy, you can, for example, bind HTTPS to 127.0.0.1 on port 8443 and HTTP to 127.0.0.1 on port 8080.
 | 
					If you plan to use a reverse proxy, you can, for example, bind HTTPS to 127.0.0.1 on port 8443 and HTTP to 127.0.0.1 on port 8080.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You may need to stop an existing pre-installed MTA which blocks port 25/tcp. See [this chapter](https://andryyy.github.io/mailcow-dockerized/first_steps/#install-a-local-mta) to learn how to reconfigure Postfix to run besides mailcow after a successful installation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
5\. Pull the images and run the composer file. The paramter `-d` will start mailcow: dockerized detached:
 | 
					5\. Pull the images and run the composer file. The paramter `-d` will start mailcow: dockerized detached:
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
docker-compose pull
 | 
					docker-compose pull
 | 
				
			||||||
 | 
				
			|||||||
@ -215,6 +215,51 @@ source mailcow.conf
 | 
				
			|||||||
docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME} < backup_file.sql
 | 
					docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME} < backup_file.sql
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Reset MySQL passwords
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Stop the stack by running `docker-compose stop`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					When the containers came to a stop, run this command:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					docker-compose run --rm --entrypoint '/bin/sh -c "gosu mysql mysqld --skip-grant-tables & sleep 10 && mysql -hlocalhost -uroot && exit 0"' mysql-mailcow
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**1\. Find database name**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					MariaDB [(none)]> show databases;
 | 
				
			||||||
 | 
					+--------------------+
 | 
				
			||||||
 | 
					| Database           |
 | 
				
			||||||
 | 
					+--------------------+
 | 
				
			||||||
 | 
					| information_schema |
 | 
				
			||||||
 | 
					| mailcow_database   | <=====
 | 
				
			||||||
 | 
					| mysql              |
 | 
				
			||||||
 | 
					| performance_schema |
 | 
				
			||||||
 | 
					+--------------------+
 | 
				
			||||||
 | 
					4 rows in set (0.00 sec)
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**2\. Reset one or more users**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Both "password" and "authentication_string" exist. Currently "password" is used, but better set both.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					MariaDB [(none)]> SELECT user FROM mysql.user;
 | 
				
			||||||
 | 
					+--------------+
 | 
				
			||||||
 | 
					| user         |
 | 
				
			||||||
 | 
					+--------------+
 | 
				
			||||||
 | 
					| mailcow_user | <===== 
 | 
				
			||||||
 | 
					| root         |
 | 
				
			||||||
 | 
					+--------------+
 | 
				
			||||||
 | 
					2 rows in set (0.00 sec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MariaDB [(none)]> FLUSH PRIVILEGES;
 | 
				
			||||||
 | 
					MariaDB [(none)]> UPDATE mysql.user SET authentication_string = PASSWORD('gotr00t'), password = PASSWORD('gotr00t') WHERE User = 'root' AND Host = '%';
 | 
				
			||||||
 | 
					MariaDB [(none)]> UPDATE mysql.user SET authentication_string = PASSWORD('mookuh'), password = PASSWORD('mookuh') WHERE User = 'mailcow' AND Host = '%';
 | 
				
			||||||
 | 
					MariaDB [(none)]> FLUSH PRIVILEGES;
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Debugging
 | 
					## Debugging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You can use `docker-compose logs $service-name` for all containers.
 | 
					You can use `docker-compose logs $service-name` for all containers.
 | 
				
			||||||
@ -223,6 +268,8 @@ Run `docker-compose logs` for all logs at once.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Follow the log output by running docker-compose with `logs -f`.
 | 
					Follow the log output by running docker-compose with `logs -f`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Limit the output by calling logs with `--tail=300` like `docker-compose logs --tail=300 mysql-mailcow`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Redirect port 80 to 443
 | 
					## Redirect port 80 to 443
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Since February the 28th 2017 mailcow does come with port 80 and 443 enabled.
 | 
					Since February the 28th 2017 mailcow does come with port 80 and 443 enabled.
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user