diff --git a/amavis/Dockerfile b/amavis/Dockerfile index e8f94c49..65736c24 100644 --- a/amavis/Dockerfile +++ b/amavis/Dockerfile @@ -1,3 +1,18 @@ FROM alpine -RUN apk add --update amavis spamassassin clamav && rm -rf /var/cache/apk/* +RUN echo "@testing http://nl.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories \ + && apk add --update \ + perl-socket6 \ + perl-io-socket-inet6@testing \ + amavisd-new \ + spamassassin \ + rsyslog \ + wget \ + && rm -rf /var/cache/apk/* + +COPY conf /etc/ +COPY rsyslog.conf /etc/rsyslog.conf + +COPY start.sh /start.sh + +CMD ["/start.sh"] diff --git a/amavis/conf/amavisd.conf b/amavis/conf/amavisd.conf new file mode 100644 index 00000000..df4d060c --- /dev/null +++ b/amavis/conf/amavisd.conf @@ -0,0 +1,165 @@ +use strict; +use Socket; + +############### +# General +############### + +$path = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/sbin:/usr/bin:/bin'; +$max_servers = 2; +$daemon_user = 'amavis'; +$daemon_group = 'amavis'; +$mydomain = $ENV{DOMAIN}; +$myhostname = $ENV{HOSTNAME}; + +$MYHOME = '/var/amavis'; +$TEMPBASE = "$MYHOME/tmp"; +$ENV{TMPDIR} = $TEMPBASE; +$QUARANTINEDIR = '/var/amavis/quarantine'; + +$log_level = 2; +$do_syslog = 1; + +$enable_db = 1; +$nanny_details_level = 2; +$enable_dkim_verification = 1; + +@local_domains_maps = ( [".$mydomain"] ); + +@mynetworks = qw( 127.0.0.0/8 [::1] [FE80::]/10 [FEC0::]/10 + 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 ); +@inet_acl = @mynetworks; + +$unix_socketname = "$MYHOME/amavisd.sock"; +$inet_socket_port = 2525; +$inet_socket_bind = undef; +$forward_method = 'lmtp:lmtp:25'; + +############### +# Policies +############### + +$interface_policy{'2525'} = 'EXT'; + +$policy_bank{'EXT'} = { +}; + +############### +# Notifications +############### +$virus_admin = "$ENV{POSTMASTER}\@$mydomain"; +$mailfrom_notify_admin = "$ENV{POSTMASTER}\@$mydomain"; +$mailfrom_notify_recip = "$ENV{POSTMASTER}\@$mydomain"; +$mailfrom_notify_spamadmin = "$ENV{POSTMASTER}\@$mydomain"; +$mailfrom_to_quarantine = ''; + +@addr_extension_virus_maps = ('virus'); +@addr_extension_banned_maps = ('banned'); +@addr_extension_spam_maps = ('spam'); +@addr_extension_bad_header_maps = ('badh'); +$recipient_delimiter = '+'; + +############### +# Antispam +############### + +$sa_tag_level_deflt = 2.0; # add spam info headers if at, or above that level +$sa_tag2_level_deflt = 6.2; # add 'spam detected' headers at that level +$sa_kill_level_deflt = 6.9; # triggers spam evasive actions (e.g. blocks mail) +$sa_dsn_cutoff_level = 10; # spam level beyond which a DSN is not sent +$sa_crediblefrom_dsn_cutoff_level = 18; # likewise, but for a likely valid From +$penpals_bonus_score = 8; # (no effect without a @storage_sql_dsn database) +$penpals_threshold_high = $sa_kill_level_deflt; # don't waste time on hi spam +$bounce_killer_score = 100; # spam score points to add for joe-jobbed bounces +$sa_mail_body_size_limit = 400*1024; # don't waste time on SA if mail is larger +$sa_local_tests_only = 0; # only tests which do not require internet access? +$sa_spam_subject_tag = ''; + +############### +# Antivirus +############### + +$MAXLEVELS = 14; +$MAXFILES = 3000; +$MIN_EXPANSION_QUOTA = 100*1024; +$MAX_EXPANSION_QUOTA = 500*1024*1024; + +$defang_virus = 1; # MIME-wrap passed infected mail +$defang_banned = 1; # MIME-wrap passed mail containing banned name +$defang_by_ccat{CC_BADH.",3"} = 1; # NUL or CR character in header +$defang_by_ccat{CC_BADH.",5"} = 1; # header line longer than 998 characters +$defang_by_ccat{CC_BADH.",6"} = 1; # header field syntax error + +@av_scanners = ( + ['ClamAV-clamd', + \&ask_daemon, ["CONTSCAN {}\n", inet_ntoa(inet_aton("clamav")).":3310"], + qr/\bOK$/m, qr/\bFOUND$/m, + qr/^.*?: (?!Infected Archive)(.*) FOUND$/m ], +); + +@av_scanners_backup = (); + +############### +# Maps and fine-tuning +############### + +@score_sender_maps = ({ '.' => [], }); + +@keep_decoded_original_maps = (new_RE( + qr'^MAIL$', # let virus scanner see full original message + qr'^MAIL-UNDECIPHERABLE$', # same as ^MAIL$ if mail is undecipherable + qr'^(ASCII(?! cpio)|text|uuencoded|xxencoded|binhex)'i, +)); + +$banned_filename_re = new_RE( + # BLOCKED ANYWHERE + qr'^\.(exe|lha|cab|dll)$', + qr'.\.(exe|vbs|pif|scr|cpl|bat|cmd|com)$'i, + + # BLOCK THE FOLLOWING, EXCEPT WITHIN UNIX ARCHIVES: + [ qr'^\.(gz|bz2)$' => 0 ], + [ qr'^\.(rpm|cpio|tar)$' => 0 ], + qr'.\.(pif|scr)$'i, + + # BLOCK THE FOLLOWING, EXCEPT WITHIN ARCHIVES: + [ qr'^\.(zip|rar|arc|arj|zoo)$'=> 0 ], + qr'^application/x-msdownload$'i, + qr'^application/x-msdos-program$'i, + qr'^application/hta$'i, + + # Block certain double extensions in filenames + qr'^(?!cid:).*\.[^./]*[A-Za-z][^./]*\.\s*(exe|vbs|pif|scr|bat|cmd|com|cpl|dll)[.\s]*$'i, +); + +@decoders = ( + ['mail', \&do_mime_decode], + ['F', \&do_uncompress, ['unfreeze', 'freeze -d', 'melt', 'fcat'] ], + ['Z', \&do_uncompress, ['uncompress', 'gzip -d', 'zcat'] ], + ['gz', \&do_uncompress, 'gzip -d'], + ['gz', \&do_gunzip], + ['bz2', \&do_uncompress, 'bzip2 -d'], + ['xz', \&do_uncompress, ['xzdec', 'xz -dc', 'unxz -c', 'xzcat'] ], + ['lzma', \&do_uncompress, ['lzmadec', 'xz -dc --format=lzma', + 'lzma -dc', 'unlzma -c', 'lzcat', 'lzmadec'] ], + ['lrz', \&do_uncompress, ['lrzip -q -k -d -o -', 'lrzcat -q -k'] ], + ['lzo', \&do_uncompress, 'lzop -d'], + ['lz4', \&do_uncompress, ['lz4c -d'] ], + [['cpio','tar'], \&do_pax_cpio, ['pax', 'gcpio', 'cpio'] ], + ['deb', \&do_ar, 'ar'], + ['rar', \&do_unrar, ['unrar', 'rar'] ], + ['arj', \&do_unarj, ['unarj', 'arj'] ], + ['arc', \&do_arc, ['nomarch', 'arc'] ], + ['zoo', \&do_zoo, ['zoo', 'unzoo'] ], + ['doc', \&do_ole, 'ripole'], + ['cab', \&do_cabextract, 'cabextract'], + ['tnef', \&do_tnef_ext, 'tnef'], + ['tnef', \&do_tnef], + [['zip','kmz'], \&do_7zip, ['7za', '7z'] ], + [['zip','kmz'], \&do_unzip], + ['7z', \&do_7zip, ['7zr', '7za', '7z'] ], + [[qw(gz bz2 Z tar)], \&do_7zip, ['7za', '7z'] ], + [[qw(xz lzma jar cpio arj rar swf lha iso cab deb rpm)], \&do_7zip, '7z' ], + ['exe', \&do_executable, ['unrar','rar'], 'lha', ['unarj','arj'] ], +); + +1; # insure a defined return value diff --git a/amavis/rsyslog.conf b/amavis/rsyslog.conf new file mode 100644 index 00000000..13353b80 --- /dev/null +++ b/amavis/rsyslog.conf @@ -0,0 +1,4 @@ +$ModLoad imuxsock +$template noTimestampFormat,"%syslogtag%%msg%\n" +$ActionFileDefaultTemplate noTimestampFormat +*.*;auth,authpriv.none /dev/stdout diff --git a/amavis/start.sh b/amavis/start.sh new file mode 100755 index 00000000..d2ed0e10 --- /dev/null +++ b/amavis/start.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +# Prepare the databases +sa-update + +# Actually run Amavis +/usr/sbin/amavisd +rsyslogd -n diff --git a/docker-compose.yml b/docker-compose.yml index 22246503..b1dc4c15 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,16 +1,18 @@ http: build: nginx + env_file: freeposte.env ports: - "80:80" - "443:443" links: - - admin - - webmail + - admin:admin + - webmail:webmail volumes: - /tmp/data/certs:/certs imap: build: dovecot + env_file: freeposte.env ports: - "143:143" - "993:993" @@ -21,28 +23,44 @@ imap: smtp: build: postfix + env_file: freeposte.env ports: - "25:25" - "465:465" - "587:587" links: - - imap + - filter:lmtp + - imap:sasl volumes: - /tmp/data/freeposte:/data - /tmp/data/logs:/logs - /tmp/data/certs:/certs +filter: + build: amavis + env_file: freeposte.env + links: + - imap:lmtp + - clamav:clamav + +clamav: + build: clamav + env_file: freeposte.env + volumes: + - /tmp/data/clamav:/data + - /tmp/data/logs:/logs + admin: build: admin + env_file: freeposte.env volumes: - /tmp/data/freeposte:/data - environment: - - DEBUG=True - - SECRET_KEY=mysecretkey webmail: build: roundcube + env_file: freeposte.env links: - - imap + - imap:imap + - smtp:smtp volumes: - /tmp/data/webmail:/data diff --git a/dovecot/conf/dovecot.conf b/dovecot/conf/dovecot.conf index 94c8a15b..97b238c0 100644 --- a/dovecot/conf/dovecot.conf +++ b/dovecot/conf/dovecot.conf @@ -3,8 +3,8 @@ ############### log_path = /dev/stderr protocols = imap lmtp sieve -postmaster_address = %{env:POSTMASTER_ADDRESS} -hostname = %{env:MAIL_HOSTNAME} +postmaster_address = %{env:POSTMASTER}@%{env:DOMAIN} +hostname = %{env:HOSTNAME} mail_plugins = $mail_plugins quota service dict { @@ -135,7 +135,6 @@ service managesieve-login { } plugin { - sieve = ~/.sieve sieve_dir = ~/sieve sieve_before = /var/lib/dovecot/before.sieve sieve_default = /var/lib/dovecot/default.sieve diff --git a/dovecot/start.sh b/dovecot/start.sh index 4c579e54..8912dfe1 100755 --- a/dovecot/start.sh +++ b/dovecot/start.sh @@ -2,7 +2,7 @@ # Fix permissions chown -R mail:mail /mail -chown -R mail:mail /var/lib/dovecot +chown -R mail:mail /var/lib/dovecot # Run dovecot exec /usr/sbin/dovecot -c /etc/dovecot/dovecot.conf -F diff --git a/freeposte.env b/freeposte.env new file mode 100644 index 00000000..011e9e43 --- /dev/null +++ b/freeposte.env @@ -0,0 +1,24 @@ +# Freeposte.io main configuration file +# +# Most configuration variables can be modified through the Web interface, +# these few settings must however be configured before starting the mail +# server and require a restart upon change. + +# Set this to enable debugging globally +DEBUG=True + +# Set to a randomly generated 16 bytes string +SECRET_KEY=AjIZosHieOjzb4i2 + +# Main mail domain +DOMAIN=freeposte.io + +# Exposed mail-server hostname +HOSTNAME=mail.freeposte.io + +# Postmaster local part (will append the main mail domain) +POSTMASTER=admin + +# Networks granted relay permissions, make sure that you include your Docker +# internal network (default to 172.17.0.0/16) +RELAYNETS=172.17.0.0/16 diff --git a/postfix/Dockerfile b/postfix/Dockerfile index 82322d43..b9ab0b15 100644 --- a/postfix/Dockerfile +++ b/postfix/Dockerfile @@ -1,6 +1,6 @@ FROM alpine -RUN apk add --update postfix postfix-sqlite rsyslog && rm -rf /var/cache/apk/* +RUN apk add --update bash postfix postfix-sqlite rsyslog && rm -rf /var/cache/apk/* COPY conf /etc/postfix COPY rsyslog.conf /etc/rsyslog.conf diff --git a/postfix/conf/main.cf b/postfix/conf/main.cf index 71a9dcfa..8a986931 100644 --- a/postfix/conf/main.cf +++ b/postfix/conf/main.cf @@ -2,8 +2,12 @@ # General ############### -# The list of relayed networks is still loaded from a configuration file -mynetworks = 127.0.0.1/32 [::1]/128 +# Main domain and hostname +mydomain = {{ DOMAIN }} +myhostname = {{ HOSTNAME }} +myorigin = $mydomain +# Relayed networks +mynetworks = 127.0.0.1/32 [::1]/128 {{ RELAYNETS }} # Empty alias list to override the configuration variable and disable NIS alias_maps = hash:/etc/aliases # SQLite configuration @@ -25,7 +29,7 @@ smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache ############### smtpd_sasl_local_domain = $myhostname smtpd_sasl_type = dovecot -smtpd_sasl_path = inet:imap:2102 +smtpd_sasl_path = inet:sasl:2102 smtpd_sasl_auth_enable = yes smtpd_sasl_security_options = noanonymous @@ -34,5 +38,5 @@ smtpd_sasl_security_options = noanonymous ############### virtual_mailbox_domains = ${sql}sqlite-virtual_mailbox_domains.cf virtual_alias_maps = ${sql}sqlite-virtual_alias_maps.cf -virtual_transport = lmtp:inet:imap:2525 +virtual_transport = lmtp:inet:lmtp:2525 lmtp_host_lookup = native diff --git a/postfix/start.sh b/postfix/start.sh index 294df491..a0353e16 100755 --- a/postfix/start.sh +++ b/postfix/start.sh @@ -1,4 +1,10 @@ -#!/bin/sh +#!/bin/bash +# Substitute configuration +for VARIABLE in `env | cut -f1 -d=`; do + sed -i "s,{{ $VARIABLE }},${!VARIABLE},g" /etc/postfix/*.cf +done + +# Actually run Postfix /usr/lib/postfix/master & rsyslogd -n