[Dovecot] Simplify Docker image

[Dovecot] Set Dovecot plugins dynamically via file and exclude Solr if not enabled
[Dovecot] Add new quarantine notification script
This commit is contained in:
andryyy 2019-01-29 00:11:12 +01:00
parent aab692301e
commit f493d3a957
No known key found for this signature in database
GPG Key ID: 8EC34FF2794E25EF
5 changed files with 177 additions and 57 deletions

View File

@ -64,6 +64,9 @@ RUN apt-get update && apt-get -y --no-install-recommends install \
libregexp-common-perl \ libregexp-common-perl \
liburi-perl \ liburi-perl \
lzma-dev \ lzma-dev \
python-redis \
python-jinja2 \
python-mysql.connector \
make \ make \
mysql-client \ mysql-client \
procps \ procps \
@ -73,9 +76,8 @@ RUN apt-get update && apt-get -y --no-install-recommends install \
syslog-ng \ syslog-ng \
syslog-ng-core \ syslog-ng-core \
syslog-ng-mod-redis \ syslog-ng-mod-redis \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/* \
&& curl https://www.dovecot.org/releases/2.3/dovecot-$DOVECOT_VERSION.tar.gz | tar xvz \
RUN curl https://www.dovecot.org/releases/2.3/dovecot-$DOVECOT_VERSION.tar.gz | tar xvz \
&& cd dovecot-$DOVECOT_VERSION \ && cd dovecot-$DOVECOT_VERSION \
&& ./configure --with-solr --with-mysql --with-ldap --with-lzma --with-lz4 --with-ssl=openssl --with-notify=inotify --with-storages=mdbox,sdbox,maildir,mbox,imapc,pop3c --with-bzlib --with-zlib --enable-hardening \ && ./configure --with-solr --with-mysql --with-ldap --with-lzma --with-lz4 --with-ssl=openssl --with-notify=inotify --with-storages=mdbox,sdbox,maildir,mbox,imapc,pop3c --with-bzlib --with-zlib --enable-hardening \
&& make -j3 \ && make -j3 \
@ -89,15 +91,19 @@ RUN curl https://www.dovecot.org/releases/2.3/dovecot-$DOVECOT_VERSION.tar.gz |
&& make install \ && make install \
&& make clean \ && make clean \
&& cd .. \ && cd .. \
&& rm -rf dovecot-2.3-pigeonhole-$PIGEONHOLE_VERSION && rm -rf dovecot-2.3-pigeonhole-$PIGEONHOLE_VERSION \
&& cpanm Data::Uniqid Mail::IMAPClient String::Util \
&& groupadd -g 5000 vmail \
&& groupadd -g 401 dovecot \
&& groupadd -g 402 dovenull \
&& useradd -g vmail -u 5000 vmail -d /var/vmail \
&& useradd -c "Dovecot unprivileged user" -d /dev/null -u 401 -g dovecot -s /bin/false dovecot \
&& useradd -c "Dovecot login user" -d /dev/null -u 402 -g dovenull -s /bin/false dovenull \
&& touch /etc/default/locale \
&& apt-get purge -y build-essential automake autotools-dev default-libmysqlclient-dev libbz2-dev libcurl4-openssl-dev libexpat1-dev liblz-dev liblz4-dev liblzma-dev libpam-dev libssl-dev lzma-dev \
&& apt-get autoremove --purge -y \
&& rm -rf /tmp/* /var/tmp/*
RUN cpanm Data::Uniqid Mail::IMAPClient String::Util
RUN echo '* * * * * root /usr/local/bin/imapsync_cron.pl 2>&1 | /usr/bin/logger' > /etc/cron.d/imapsync
RUN echo '30 3 * * * vmail /usr/local/bin/doveadm quota recalc -A' > /etc/cron.d/dovecot-sync
RUN echo '* * * * * vmail /usr/local/bin/trim_logs.sh >> /dev/console 2>&1' > /etc/cron.d/trim_logs
RUN echo '25 * * * * vmail /usr/local/bin/maildir_gc.sh >> /dev/console 2>&1' > /etc/cron.d/maildir_gc
RUN echo '30 1 * * * root /usr/local/bin/sa-rules.sh >> /dev/console 2>&1' > /etc/cron.d/sa-rules
RUN echo '0 2 * * * root /usr/bin/curl http://solr:8983/solr/dovecot/update?optimize=true >> /dev/console 2>&1' > /etc/cron.d/solr-optimize
COPY trim_logs.sh /usr/local/bin/trim_logs.sh COPY trim_logs.sh /usr/local/bin/trim_logs.sh
COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
COPY imapsync /usr/local/bin/imapsync COPY imapsync /usr/local/bin/imapsync
@ -112,32 +118,7 @@ COPY maildir_gc.sh /usr/local/bin/maildir_gc.sh
COPY docker-entrypoint.sh / COPY docker-entrypoint.sh /
COPY supervisord.conf /etc/supervisor/supervisord.conf COPY supervisord.conf /etc/supervisor/supervisord.conf
COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
COPY quarantine_notify.py /usr/local/bin/quarantine_notify.py
RUN chmod +x /usr/local/lib/dovecot/sieve/rspamd-pipe-ham \
/usr/local/lib/dovecot/sieve/rspamd-pipe-spam \
/usr/local/bin/imapsync_cron.pl \
/usr/local/bin/postlogin.sh \
/usr/local/bin/imapsync \
/usr/local/bin/trim_logs.sh \
/usr/local/bin/sa-rules.sh \
/usr/local/bin/maildir_gc.sh \
/usr/local/sbin/stop-supervisor.sh
RUN groupadd -g 5000 vmail \
&& groupadd -g 401 dovecot \
&& groupadd -g 402 dovenull \
&& useradd -g vmail -u 5000 vmail -d /var/vmail \
&& useradd -c "Dovecot unprivileged user" -d /dev/null -u 401 -g dovecot -s /bin/false dovecot \
&& useradd -c "Dovecot login user" -d /dev/null -u 402 -g dovenull -s /bin/false dovenull
RUN touch /etc/default/locale
RUN apt-get purge -y build-essential automake autotools-dev default-libmysqlclient-dev libbz2-dev libcurl4-openssl-dev libexpat1-dev liblz-dev liblz4-dev liblzma-dev libpam-dev libssl-dev lzma-dev \
&& apt-get autoremove --purge -y
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 rm -rf \
/tmp/* \
/var/tmp/*

View File

@ -7,11 +7,16 @@ while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${D
sleep 2 sleep 2
done done
# Hard-code env vars to scripts due to cron not passing them to the perl script # Hard-code env vars to scripts due to cron not passing them to the scripts
sed -i "/^\$DBUSER/c\\\$DBUSER='${DBUSER}';" /usr/local/bin/imapsync_cron.pl sed -i "s/__DBUSER__/${DBUSER}/g" /usr/local/bin/imapsync_cron.pl
sed -i "/^\$DBPASS/c\\\$DBPASS='${DBPASS}';" /usr/local/bin/imapsync_cron.pl sed -i "s/__DBPASS__/${DBPASS}/g" /usr/local/bin/imapsync_cron.pl
sed -i "/^\$DBNAME/c\\\$DBNAME='${DBNAME}';" /usr/local/bin/imapsync_cron.pl sed -i "s/__DBNAME__/${DBNAME}/g" /usr/local/bin/imapsync_cron.pl
sed -i "s/LOG_LINES/${LOG_LINES}/g" /usr/local/bin/trim_logs.sh
sed -i "s/__DBUSER__/${DBUSER}/g" /usr/local/bin/quarantine_notify.py
sed -i "s/__DBPASS__/${DBPASS}/g" /usr/local/bin/quarantine_notify.py
sed -i "s/__DBNAME__/${DBNAME}/g" /usr/local/bin/quarantine_notify.py
sed -i "s/__LOG_LINES__/${LOG_LINES}/g" /usr/local/bin/trim_logs.sh
# Create missing directories # Create missing directories
[[ ! -d /usr/local/etc/dovecot/sql/ ]] && mkdir -p /usr/local/etc/dovecot/sql/ [[ ! -d /usr/local/etc/dovecot/sql/ ]] && mkdir -p /usr/local/etc/dovecot/sql/
@ -87,7 +92,17 @@ EOF
echo -n ${ACL_ANYONE} > /usr/local/etc/dovecot/acl_anyone echo -n ${ACL_ANYONE} > /usr/local/etc/dovecot/acl_anyone
# Create userdb dict for Dovecot if [[ "${SKIP_SOLR}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo -n 'quota acl zlib listescape mail_crypt mail_crypt_acl mail_log notify' > /usr/local/etc/dovecot/mail_plugins
echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve listescape mail_crypt mail_crypt_acl notify mail_log' > /usr/local/etc/dovecot/mail_plugins_imap
echo -n 'quota sieve acl zlib listescape mail_crypt mail_crypt_acl' > /usr/local/etc/dovecot/mail_plugins_lmtp
else
echo -n 'quota acl zlib listescape mail_crypt mail_crypt_acl mail_log notify fts fts_solr' > /usr/local/etc/dovecot/mail_plugins
echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve listescape mail_crypt mail_crypt_acl notify mail_log fts fts_solr' > /usr/local/etc/dovecot/mail_plugins_imap
echo -n 'quota sieve acl zlib listescape mail_crypt mail_crypt_acl fts fts_solr' > /usr/local/etc/dovecot/mail_plugins_lmtp
fi
chmod 644 /usr/local/etc/dovecot/mail_plugins /usr/local/etc/dovecot/mail_plugins_imap /usr/local/etc/dovecot/mail_plugins_lmtp /templates/quarantine.tpl
cat <<EOF > /usr/local/etc/dovecot/sql/dovecot-dict-sql-userdb.conf cat <<EOF > /usr/local/etc/dovecot/sql/dovecot-dict-sql-userdb.conf
driver = mysql driver = mysql
connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
@ -142,6 +157,24 @@ chown -R vmail:vmail /var/vmail/sieve
chown -R vmail:vmail /var/volatile chown -R vmail:vmail /var/volatile
adduser vmail tty adduser vmail tty
chmod g+rw /dev/console chmod g+rw /dev/console
chmod +x /usr/local/lib/dovecot/sieve/rspamd-pipe-ham \
/usr/local/lib/dovecot/sieve/rspamd-pipe-spam \
/usr/local/bin/imapsync_cron.pl \
/usr/local/bin/postlogin.sh \
/usr/local/bin/imapsync \
/usr/local/bin/trim_logs.sh \
/usr/local/bin/sa-rules.sh \
/usr/local/bin/maildir_gc.sh \
/usr/local/sbin/stop-supervisor.sh
# Setup cronjobs
echo '* * * * * root /usr/local/bin/imapsync_cron.pl 2>&1 | /usr/bin/logger' > /etc/cron.d/imapsync
echo '30 3 * * * vmail /usr/local/bin/doveadm quota recalc -A' > /etc/cron.d/dovecot-sync
echo '* * * * * vmail /usr/local/bin/trim_logs.sh >> /dev/console 2>&1' > /etc/cron.d/trim_logs
echo '25 * * * * vmail /usr/local/bin/maildir_gc.sh >> /dev/console 2>&1' > /etc/cron.d/maildir_gc
echo '30 1 * * * root /usr/local/bin/sa-rules.sh >> /dev/console 2>&1' > /etc/cron.d/sa-rules
echo '0 2 * * * root /usr/bin/curl http://solr:8983/solr/dovecot/update?optimize=true >> /dev/console 2>&1' > /etc/cron.d/solr-optimize
echo '*/20 * * * * vmail /usr/local/bin/quarantine_notify.py >> /dev/console 2>&1' > /etc/cron.d/quarantine_notify
# Fix more than 1 hardlink issue # Fix more than 1 hardlink issue
touch /etc/crontab /etc/cron.*/* touch /etc/crontab /etc/cron.*/*

View File

@ -26,16 +26,12 @@ sub qqw($) {
return @values return @values
} }
$DBNAME = '';
$DBUSER = '';
$DBPASS = '';
$run_dir="/tmp"; $run_dir="/tmp";
$dsn = "DBI:mysql:database=" . $DBNAME . ";mysql_socket=/var/run/mysqld/mysqld.sock"; $dsn = 'DBI:mysql:database=__DBNAME__;mysql_socket=/var/run/mysqld/mysqld.sock';
$lock_file = $run_dir . "/imapsync_busy"; $lock_file = $run_dir . "/imapsync_busy";
$lockmgr = LockFile::Simple->make(-autoclean => 1, -max => 1); $lockmgr = LockFile::Simple->make(-autoclean => 1, -max => 1);
$lockmgr->lock($lock_file) || die "can't lock ${lock_file}"; $lockmgr->lock($lock_file) || die "can't lock ${lock_file}";
$dbh = DBI->connect($dsn, $DBUSER, $DBPASS, { $dbh = DBI->connect($dsn, '__DBUSER__', '__DBPASS__', {
mysql_auto_reconnect => 1, mysql_auto_reconnect => 1,
mysql_enable_utf8mb4 => 1 mysql_enable_utf8mb4 => 1
}); });

View File

@ -0,0 +1,110 @@
#!/usr/bin/python
import smtplib
import os
import mysql.connector
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.Utils import COMMASPACE, formatdate
import cgi
import jinja2
from jinja2 import Template
import json
import redis
import time
while True:
try:
r = redis.StrictRedis(host='redis', decode_responses=True, port=6379, db=0)
r.ping()
except Exception as ex:
print '%s - trying again...' % (ex)
time.sleep(3)
else:
break
time_now = int(time.time())
def query_mysql(query, headers = True, update = False):
while True:
try:
cnx = mysql.connector.connect(unix_socket = '/var/run/mysqld/mysqld.sock', user='__DBUSER__', passwd='__DBPASS__', database='__DBNAME__', charset="utf8")
except Exception as ex:
print '%s - trying again...' % (ex)
time.sleep(3)
else:
break
cur = cnx.cursor()
cur.execute(query)
if not update:
result = []
columns = tuple( [d[0].decode('utf8') for d in cur.description] )
for row in cur:
if headers:
result.append(dict(zip(columns, row)))
else:
result.append(row)
cur.close()
cnx.close()
return result
else:
cnx.commit()
cur.close()
cnx.close()
def notify_rcpt(rcpt, msg_count):
meta_query = query_mysql('SELECT id, subject, sender, created FROM quarantine WHERE notified = 0 AND rcpt = "%s"' % (rcpt))
if r.get('Q_HTML'):
try:
template = Template(r.get('Q_HTML'))
except:
print "Error: Cannot parse quarantine template, falling back to default template."
with open('/templates/quarantine.tpl') as file_:
template = Template(file_.read())
else:
with open('/templates/quarantine.tpl') as file_:
template = Template(file_.read())
html = template.render(meta=meta_query, counter=msg_count)
count = 0
while count < 15:
try:
server = smtplib.SMTP('postfix', 589, 'quarntine')
server.ehlo()
msg = MIMEMultipart('alternative')
msg['From'] = r.get('Q_SENDER') or "quarantine@localhost"
msg['Subject'] = r.get('Q_SUBJ') or "Spam Quarantine Notification"
msg['Date'] = formatdate(localtime = True)
text = "You have %d new items" % (msg_count)
text_part = MIMEText(text, 'plain')
html_part = MIMEText(html, 'html')
msg.attach(text_part)
msg.attach(html_part)
msg['To'] = str(rcpt)
text = msg.as_string()
server.sendmail(msg['From'], msg['To'], text)
server.quit()
for res in meta_query:
query_mysql('UPDATE quarantine SET notified = 1 WHERE id = "%d"' % (res['id']), update = True)
r.hset('Q_LAST_NOTIFIED', record['rcpt'], time_now)
break
except Exception as ex:
print '%s' % (ex)
time.sleep(3)
records = query_mysql('SELECT count(id) AS counter, rcpt FROM quarantine WHERE notified = 0 GROUP BY rcpt')
for record in records:
last_notification = int(r.hget('Q_LAST_NOTIFIED', record['rcpt'])) or 0
attrs_json = query_mysql('SELECT attributes FROM mailbox WHERE username = "%s"' % (record['rcpt']))
attrs = json.loads(str(attrs_json[0]['attributes']))
if attrs['quarantine_notification'] == 'hourly':
if last_notification == 0 or (last_notification + 3600) > time_now:
notify_rcpt(record['rcpt'], record['counter'])
elif attrs['quarantine_notification'] == 'daily':
if last_notification == 0 or (last_notification + 86400) > time_now:
notify_rcpt(record['rcpt'], record['counter'])
elif attrs['quarantine_notification'] == 'weekly':
if last_notification == 0 or (last_notification + 604800) > time_now:
notify_rcpt(record['rcpt'], record['counter'])
else:
break

View File

@ -7,12 +7,12 @@ catch_non_zero() {
echo "Command ${CMD} failed to execute, exit code was ${EC}" echo "Command ${CMD} failed to execute, exit code was ${EC}"
fi fi
} }
catch_non_zero "/usr/bin/redis-cli -h redis LTRIM ACME_LOG 0 LOG_LINES" catch_non_zero "/usr/bin/redis-cli -h redis LTRIM ACME_LOG 0 __LOG_LINES__"
catch_non_zero "/usr/bin/redis-cli -h redis LTRIM POSTFIX_MAILLOG 0 LOG_LINES" catch_non_zero "/usr/bin/redis-cli -h redis LTRIM POSTFIX_MAILLOG 0 __LOG_LINES__"
catch_non_zero "/usr/bin/redis-cli -h redis LTRIM DOVECOT_MAILLOG 0 LOG_LINES" catch_non_zero "/usr/bin/redis-cli -h redis LTRIM DOVECOT_MAILLOG 0 __LOG_LINES__"
catch_non_zero "/usr/bin/redis-cli -h redis LTRIM SOGO_LOG 0 LOG_LINES" catch_non_zero "/usr/bin/redis-cli -h redis LTRIM SOGO_LOG 0 __LOG_LINES__"
catch_non_zero "/usr/bin/redis-cli -h redis LTRIM NETFILTER_LOG 0 LOG_LINES" catch_non_zero "/usr/bin/redis-cli -h redis LTRIM NETFILTER_LOG 0 __LOG_LINES__"
catch_non_zero "/usr/bin/redis-cli -h redis LTRIM AUTODISCOVER_LOG 0 LOG_LINES" catch_non_zero "/usr/bin/redis-cli -h redis LTRIM AUTODISCOVER_LOG 0 __LOG_LINES__"
catch_non_zero "/usr/bin/redis-cli -h redis LTRIM API_LOG 0 LOG_LINES" catch_non_zero "/usr/bin/redis-cli -h redis LTRIM API_LOG 0 __LOG_LINES__"
catch_non_zero "/usr/bin/redis-cli -h redis LTRIM RL_LOG 0 LOG_LINES" catch_non_zero "/usr/bin/redis-cli -h redis LTRIM RL_LOG 0 __LOG_LINES__"