diff --git a/Dockerfile b/Dockerfile index 267c86d4..f2c83054 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,9 +32,11 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get update -q --fix-missing && \ opendmarc \ p7zip \ postfix \ + postfix-ldap \ pyzor \ razor \ rsyslog \ + sasl2-bin \ spamassassin \ unzip \ && \ diff --git a/Makefile b/Makefile index 18545b96..ac3abe43 100644 --- a/Makefile +++ b/Makefile @@ -76,6 +76,31 @@ run: -e SSL_CERT_PATH=/tmp/docker-mailserver/letsencrypt/mail.my-domain.com/fullchain.pem \ -e SSL_KEY_PATH=/tmp/docker-mailserver/letsencrypt/mail.my-domain.com/privkey.pem \ -h mail.my-domain.com -t $(NAME) + docker run -d --name mail_enable_ldap \ + -v "`pwd`/test/config":/tmp/docker-mailserver \ + -v "`pwd`/test":/tmp/docker-mailserver-test \ + -e LDAP=1 \ + -e SMTP_ONLY=1 \ + -h mail.my-domain.com -t $(NAME) + docker run -d --name mail_enable_sasl_ldap \ + -v "`pwd`/test/config":/tmp/docker-mailserver \ + -v "`pwd`/test":/tmp/docker-mailserver-test \ + -e SASLAUTHD=1 \ + -e "SASL_LDAP_SERVER=192.168.0.100" \ + -e SASL_LDAP_PROTO= \ + -e "SASL_LDAP_BIND_DN=cn=Administrator,cn=Users,dc=my,dc=domain" \ + -e SASL_LDAP_PASSWORD=test \ + -e "SASL_LDAP_SEARCH_BASE=dc=my,dc=domain" \ + -e "SASL_LDAP_FILTER=(&(sAMAccountName=%U)(objectClass=person))" \ + -e SASL_MECHANISMS=ldap \ + -e SMTP_ONLY=1 \ + -h mail.my-domain.com -t $(NAME) + docker run -d --name mail_enable_kopano \ + -v "`pwd`/test/config":/tmp/docker-mailserver \ + -v "`pwd`/test":/tmp/docker-mailserver-test \ + -e KOPANO=1 \ + -e SMTP_ONLY=1 \ + -h mail.my-domain.com -t $(NAME) # Wait for containers to fully start sleep 20 diff --git a/README.md b/README.md index 71ae5f8e..ca9cee00 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ Includes: - opendmarc - fail2ban - fetchmail +- saslauthd +- saslauthd ldap support +- postfix ldap support +- kopano support - basic [sieve support](https://github.com/tomav/docker-mailserver/wiki/Configure-Sieve-filters) using dovecot - [LetsEncrypt](https://letsencrypt.org/) and self-signed certificates - [integration tests](https://travis-ci.org/tomav/docker-mailserver) @@ -147,7 +151,6 @@ Otherwise, `iptables` won't be able to ban IPs. - custom => Enables custom certificates - manual => Let's you manually specify locations of your SSL certificates for non-standard cases - self-signed => Enables self-signed certificates - Please read [the SSL page in the wiki](https://github.com/tomav/docker-mailserver/wiki/Configure-SSL) for more information. ##### PERMIT_DOCKER @@ -157,6 +160,68 @@ Set different options for mynetworks option (can be overwrite in postfix-main.cf - host => Add docker host (ipv4 only) - network => Add all docker containers (ipv4 only) +##### SASL_MECHANISMS + - empty => pam + - ldap => authenticate against ldap server + - shadow => authenticate against local user db + - mysql => authenticate against mysql db + - rimap => authenticate against imap server + - NOTE: can be a list of mechanisms like pam ldap shadow + +##### SASL_MECH_OPTIONS + - empty => None + - e.g. with sasl_mechanism rimap you need to specify the ip-address/servername of the imap server ==> xxx.xxx.xxx.xxx + +##### SASL_LDAP_SERVER + - empty => localhost + +##### SASL_LDAP_PROTO + - empty => ldap:// + - 1 => ldaps:// + +##### SASL_LDAP_BIND_DN + - empty => anonymous bind + - specify an object with priviliges to search the directory tree + - e.g. active directory: SASL_BIND_DN=cn=Administrator,cn=Users,dc=mydomain,dc=net + - e.g. openldap: SASL_BIND_DN=cn=admin,dc=mydomain,dc=net + +##### SASL_LDAP_PASSWORD + - empty => anonymous bind + +##### SASL_LDAP_SEARCH_BASE + - empty => Reverting to SASL_MECHANISM pam + - specify the search base + +##### SASL_LDAP_FILTER + - empty => default filter (uid=%u) + - e.g. for active directory: (&(sAMAccountName=%U)(objectClass=person)) + - e.g. for openldap: (&(uid=%U)(objectClass=person)) + +##### LDAP + - **empty** => LDAP support disabled + - 1 => set virtual_mailbox_maps = ldap:/etc/postfix/ldap-users.cf and virtual_alias_maps = ldap:/etc//postfix/ldap-aliases.cf + - $SMTP_ONLY must be set to 1 + +##### LDAP_SERVER_HOST + - => Specify the dns-name/ip-address where the ldap-server + - NOTE: If you going to use the mailserver in combination with docker-compose you can set the service name here + +##### LDAP_SEARCH_BASE + - => e.g. LDAP_SEARCH_BASE=dc=mydomain,dc=loc + +##### LDAP_BIND_DN + - => take a look at examples of SASL_LDAP_BIND_DN + +##### LDAP_BIND_PW + - => Specify the password to bind against ldap + +##### KOPANO + - **empty** => LDAP support disabled disabled + - 1 => set virtual_transport = ltmp:${KOPANO_DAGENT}:2003 + +##### KOPANO_DAGENT + - => Specify the dns-name/ip-address where the kopano-dagent can be reached + ##### VIRUSMAILS_DELETE_DELAY Set how many days a virusmail will stay on the server before being deleted diff --git a/config/postfix-ldap-aliases.cf b/config/postfix-ldap-aliases.cf new file mode 100644 index 00000000..a0389705 --- /dev/null +++ b/config/postfix-ldap-aliases.cf @@ -0,0 +1,9 @@ +server_host = localhost +search_base = ou=Users,dc=example,dc=com +version = 3 +scope = sub +query_filter = (&(objectClass=posixAccount)(kopanoAliases=%s)) +result_attribute = mail +bind_dn = +bind_pw = +bind = yes diff --git a/config/postfix-ldap-aliases.cf.activedirectory b/config/postfix-ldap-aliases.cf.activedirectory new file mode 100644 index 00000000..7fb66cf8 --- /dev/null +++ b/config/postfix-ldap-aliases.cf.activedirectory @@ -0,0 +1,10 @@ +server_host = 192.168.0.100 +search_base = ou=Users,dc=example,dc=local +version = 3 +bind = yes +bind_dn = cn=kopano,ou=Users,dc=example,dc=local +bind_pw = secret +scope = sub +query_filter = (&(objectClass=user)(otherMailbox=%s)) +result_attribute = mail +bind = yes diff --git a/config/postfix-ldap-aliases.cf.openldap b/config/postfix-ldap-aliases.cf.openldap new file mode 100644 index 00000000..a0389705 --- /dev/null +++ b/config/postfix-ldap-aliases.cf.openldap @@ -0,0 +1,9 @@ +server_host = localhost +search_base = ou=Users,dc=example,dc=com +version = 3 +scope = sub +query_filter = (&(objectClass=posixAccount)(kopanoAliases=%s)) +result_attribute = mail +bind_dn = +bind_pw = +bind = yes diff --git a/config/postfix-ldap-groups.cf b/config/postfix-ldap-groups.cf new file mode 100644 index 00000000..f91fcc4c --- /dev/null +++ b/config/postfix-ldap-groups.cf @@ -0,0 +1,9 @@ +server_host = localhost +search_base = ou=Groups,dc=exampple,dc=com +version = 3 +query_filter = (&(objectclass=kopano-group)(mail=%s)) +leaf_result_attribute = mail +special_result_attribute = member +bind_dn = +bind_pw = +bind = yes diff --git a/config/postfix-ldap-groups.cf.activedirectory b/config/postfix-ldap-groups.cf.activedirectory new file mode 100644 index 00000000..c6ede4e7 --- /dev/null +++ b/config/postfix-ldap-groups.cf.activedirectory @@ -0,0 +1,10 @@ +server_host = 192.168.0.100 +search_base = ou=groups,dc=example,dc=local +version = 3 +bind = yes +bind_dn = cn=kopano,ou=Users,dc=example,dc=local +bind_pw = secret +query_filter = (&(objectclass=group)(mail=%s)) +leaf_result_attribute = mail +special_result_attribute = member +bind = yes diff --git a/config/postfix-ldap-groups.cf.openldap b/config/postfix-ldap-groups.cf.openldap new file mode 100644 index 00000000..f91fcc4c --- /dev/null +++ b/config/postfix-ldap-groups.cf.openldap @@ -0,0 +1,9 @@ +server_host = localhost +search_base = ou=Groups,dc=exampple,dc=com +version = 3 +query_filter = (&(objectclass=kopano-group)(mail=%s)) +leaf_result_attribute = mail +special_result_attribute = member +bind_dn = +bind_pw = +bind = yes diff --git a/config/postfix-ldap-users.cf b/config/postfix-ldap-users.cf new file mode 100644 index 00000000..b817a580 --- /dev/null +++ b/config/postfix-ldap-users.cf @@ -0,0 +1,9 @@ +server_host = localhost +search_base = ou=Users,dc=example,dc=com +version = 3 +scope = sub +query_filter = (&(objectClass=posixAccount)(mail=%s)) +result_attribute = mail +bind_dn = +bind_pw = +bind = yes diff --git a/config/postfix-ldap-users.cf.activedirectory b/config/postfix-ldap-users.cf.activedirectory new file mode 100644 index 00000000..af4280e4 --- /dev/null +++ b/config/postfix-ldap-users.cf.activedirectory @@ -0,0 +1,10 @@ +server_host = 192.168.0.100 +search_base = ou=Users,dc=example,dc=local +version = 3 +bind = yes +bind_dn = cn=kopano,ou=Users,dc=example,dc=local +bind_pw = secret +scope = sub +query_filter = (&(objectClass=user)(mail=%s)) +result_attribute = mail +bind = yes diff --git a/config/postfix-ldap-users.cf.openldap b/config/postfix-ldap-users.cf.openldap new file mode 100644 index 00000000..b817a580 --- /dev/null +++ b/config/postfix-ldap-users.cf.openldap @@ -0,0 +1,9 @@ +server_host = localhost +search_base = ou=Users,dc=example,dc=com +version = 3 +scope = sub +query_filter = (&(objectClass=posixAccount)(mail=%s)) +result_attribute = mail +bind_dn = +bind_pw = +bind = yes diff --git a/target/start-mailserver.sh b/target/start-mailserver.sh index cb3a9b43..9f5dd7e0 100644 --- a/target/start-mailserver.sh +++ b/target/start-mailserver.sh @@ -69,12 +69,14 @@ if [ -f /tmp/docker-mailserver/postfix-accounts.cf ]; then fi # Copy user provided sieve file, if present test -e /tmp/docker-mailserver/${login}.dovecot.sieve && cp /tmp/docker-mailserver/${login}.dovecot.sieve /var/mail/${domain}/${user}/.dovecot.sieve - echo ${domain} >> /tmp/vhost.tmp done < /tmp/docker-mailserver/postfix-accounts.cf else echo "==> Warning: 'config/docker-mailserver/postfix-accounts.cf' is not provided. No mail account created." fi +# Create default vhost file +echo ${domain} >> /tmp/vhost.tmp + # # Aliases # @@ -258,6 +260,96 @@ case $PERMIT_DOCKER in esac +# +# Ldap +# +for i in 'users' 'groups' 'aliases'; do + fpath="/tmp/docker-mailserver/postfix-ldap-${i}.cf" + if [ -f $fpath ]; then + cp ${fpath} /etc/postfix/ldap-${i}.cf + sed -i -e 's|^server_host.*|server_host = '${LDAP_SERVER_HOST}'|g' \ + -e 's|^search_base.*|search_base = '${LDAP_SEARCH_BASE}'|g' \ + -e 's|^bind_dn.*|bind_dn = '${LDAP_BIND_DN}'|g' \ + -e 's|^bind_pw.*|bind_pw = '${LDAP_BIND_PW}'|g' \ + /etc/postfix/ldap-${i}.cf + else + echo "${fpath} not found" + echo "==> Warning: 'config/postfix-ldap-$i.cf' is not provided." + fi +done + +if [[ $LDAP == 1 ]] && [[ $SMTP_ONLY == 1 ]];then + + echo "Configuring LDAP" + [ -f /etc/postfix/ldap-users.cf ] && \ + postconf -e "virtual_mailbox_maps = ldap:/etc/postfix/ldap-users.cf" || \ + echo '==> Warning: /etc/postfix/ldap-user.cf not found' + + [ -f /etc/postfix/ldap-aliases.cf ] && \ + postconf -e "virtual_alias_maps = ldap:/etc/postfix/ldap-aliases.cf, ldap:/etc/postfix/ldap-groups.cf" || \ + echo '==> Warning: /etc/postfix/ldap-aliases.cf not found' + + [ ! -f /etc/postfix/sasl/smtpd.conf ] && cat > /etc/postfix/sasl/smtpd.conf << EOF +pwcheck_method: saslauthd +mech_list: plain login +EOF +fi + +# +# Saslauthd +# +if [ ${SASLAUTHD} == 1 ];then + echo "Configuring Cyrus SASL" + # checking env vars and setting defaults + [ -z $SASL_MECHANISMS ] && SASL_MECHANISMS=pam + [ -z $SASL_LDAP_SEARCH_BASE ] && SASL_MECHANISMS=pam + [ -z $SASL_LDAP_SERVER ] && SASL_LDAP_SERVER=localhost + [ -z $SASL_LDAP_FILTER ] && SASL_LDAP_FILTER='(uid=%u)' + ([ $SASL_LDAP_PROTO == 0 ] || [ -z $SASL_LDAP_PROTO ]) && SASL_LDAP_PROTO='ldap://' || SASL_LDAP_PROTO='ldaps://' + + if [ ! -f /etc/saslauthd.conf ];then + echo "Creating /etc/saslauthd.conf" + cat > /etc/saslauthd.conf << EOF +ldap_servers: ${SASL_LDAP_PROTO}${SASL_LDAP_SERVER} + +ldap_auth_method: bind +ldap_bind_dn: ${SASL_LDAP_BIND_DN} +ldap_password: ${SASL_LDAP_PASSWORD} + +ldap_search_base: ${SASL_LDAP_SEARCH_BASE} +ldap_filter: ${SASL_LDAP_FILTER} + +ldap_referrals: yes +log_level: 10 +EOF + + fi + + sed -i -e "/^[^#].*smtpd_sasl_type.*/s/^/#/g" \ + -e "/^[^#].*smtpd_sasl_path.*/s/^/#/g" \ + /etc/postfix/master.cf + + sed -i -e "s|^START=.*|START=yes|g" \ + -e "s|^MECHANISMS=.*|MECHANISMS="\"$SASL_MECHANISMS\""|g" \ + -e "s|^MECH_OPTIONS=.*|MECH_OPTIONS="\"$SASL_MECH_OPTIONS\""|g" \ + /etc/default/saslauthd + sed -i -e "/smtpd_sasl_path =.*/d" \ + -e "/smtpd_sasl_type =.*/d" \ + -e "/dovecot_destination_recipient_limit =.*/d" \ + /etc/postfix/main.cf + gpasswd -a postfix sasl +fi + +# +# Kopano +# +if [ $KOPANO == 1 ];then + echo 'Configuring Kopano' + [ ! -z ${KOPANO_DAGENT} ] && \ + postconf -e "virtual_transport = lmtp:${KOPANO_DAGENT}:2003" || \ + echo '$KOPANO_DAGENT not set. Skipping ...' +fi + # # Override Postfix configuration # @@ -378,57 +470,63 @@ if [ "$ENABLE_MANAGESIEVE" = 1 ]; then echo "Sieve management enabled" mv /etc/dovecot/protocols.d/managesieved.protocol.disab /etc/dovecot/protocols.d/managesieved.protocol fi - if [ "$SMTP_ONLY" != 1 ]; then - # Here we are starting sasl and imap, not pop3 because it's disabled by default - echo " * Starting dovecot services" - /usr/sbin/dovecot -c /etc/dovecot/dovecot.conf -fi + # Here we are starting sasl and imap, not pop3 because it's disabled by default + echo " * Starting dovecot services" + /usr/sbin/dovecot -c /etc/dovecot/dovecot.conf -if [ "$ENABLE_POP3" = 1 -a "$SMTP_ONLY" != 1 ]; then - echo "Starting POP3 services" - mv /etc/dovecot/protocols.d/pop3d.protocol.disab /etc/dovecot/protocols.d/pop3d.protocol - /usr/sbin/dovecot reload -fi + if [ "$ENABLE_POP3" = 1 ]; then + echo "Starting POP3 services" + mv /etc/dovecot/protocols.d/pop3d.protocol.disab /etc/dovecot/protocols.d/pop3d.protocol + /usr/sbin/dovecot reload + fi -if [ -f /tmp/docker-mailserver/dovecot.cf ]; then - echo 'Adding file "dovecot.cf" to the Dovecot configuration' - cp /tmp/docker-mailserver/dovecot.cf /etc/dovecot/local.conf - /usr/sbin/dovecot reload + if [ -f /tmp/docker-mailserver/dovecot.cf ]; then + echo 'Adding file "dovecot.cf" to the Dovecot configuration' + cp /tmp/docker-mailserver/dovecot.cf /etc/dovecot/local.conf + /usr/sbin/dovecot reload + fi fi # Enable fetchmail daemon if [ "$ENABLE_FETCHMAIL" = 1 ]; then - /usr/local/bin/setup-fetchmail - echo "Fetchmail enabled" - /etc/init.d/fetchmail start + /usr/local/bin/setup-fetchmail + echo "Fetchmail enabled" + /etc/init.d/fetchmail start fi # Start services related to SMTP if ! [ "$DISABLE_CLAMAV" = 1 ]; then - /etc/init.d/clamav-daemon start + /etc/init.d/clamav-daemon start fi # Copy user provided configuration files if provided if [ -f /tmp/docker-mailserver/amavis.cf ]; then - cp /tmp/docker-mailserver/amavis.cf /etc/amavis/conf.d/50-user + cp /tmp/docker-mailserver/amavis.cf /etc/amavis/conf.d/50-user fi if ! [ "$DISABLE_AMAVIS" = 1 ]; then - /etc/init.d/amavis start + /etc/init.d/amavis start fi + +if [[ $SMTP_ONLY == 1 ]] && [[ $LDAP == 1 ]]; then + /etc/init.d/saslauthd start +fi + /etc/init.d/opendkim start /etc/init.d/opendmarc start /etc/init.d/postfix start if [ "$ENABLE_FAIL2BAN" = 1 ]; then - echo "Starting fail2ban service" - touch /var/log/auth.log - /etc/init.d/fail2ban start + echo "Starting fail2ban service" + touch /var/log/auth.log + /etc/init.d/fail2ban start fi -echo "Listing users" -/usr/sbin/dovecot user '*' +if [ ! "$SMTP_ONLY" == 1 ];then + echo "Listing users" + /usr/sbin/dovecot user '*' +fi echo "Starting..." tail -f /var/log/mail/mail.log diff --git a/test/config/postfix-ldap-aliases.cf b/test/config/postfix-ldap-aliases.cf new file mode 100644 index 00000000..7fb66cf8 --- /dev/null +++ b/test/config/postfix-ldap-aliases.cf @@ -0,0 +1,10 @@ +server_host = 192.168.0.100 +search_base = ou=Users,dc=example,dc=local +version = 3 +bind = yes +bind_dn = cn=kopano,ou=Users,dc=example,dc=local +bind_pw = secret +scope = sub +query_filter = (&(objectClass=user)(otherMailbox=%s)) +result_attribute = mail +bind = yes diff --git a/test/config/postfix-ldap-groups.cf b/test/config/postfix-ldap-groups.cf new file mode 100644 index 00000000..c6ede4e7 --- /dev/null +++ b/test/config/postfix-ldap-groups.cf @@ -0,0 +1,10 @@ +server_host = 192.168.0.100 +search_base = ou=groups,dc=example,dc=local +version = 3 +bind = yes +bind_dn = cn=kopano,ou=Users,dc=example,dc=local +bind_pw = secret +query_filter = (&(objectclass=group)(mail=%s)) +leaf_result_attribute = mail +special_result_attribute = member +bind = yes diff --git a/test/config/postfix-ldap-users.cf b/test/config/postfix-ldap-users.cf new file mode 100644 index 00000000..af4280e4 --- /dev/null +++ b/test/config/postfix-ldap-users.cf @@ -0,0 +1,10 @@ +server_host = 192.168.0.100 +search_base = ou=Users,dc=example,dc=local +version = 3 +bind = yes +bind_dn = cn=kopano,ou=Users,dc=example,dc=local +bind_pw = secret +scope = sub +query_filter = (&(objectClass=user)(mail=%s)) +result_attribute = mail +bind = yes