#!/bin/bash
#
# Mail Service

PATH=$HOME/.docker/cli-plugins:/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

config_mail() {
  echo -ne "* Configuring mail container.."

  if [ ! -d "/federated/apps/mail" ]; then
    mkdir -p /federated/apps/mail/data/root/certs &> /dev/null
    mkdir -p /federated/apps/mail/data/var/mail &> /dev/null
    mkdir -p /federated/apps/mail/data/var/mail-state &> /dev/null
    mkdir -p /federated/apps/mail/data/var/log/mail &> /dev/null
    mkdir -p /federated/apps/mail/data/tmp/docker-mailserver &> /dev/null
    mkdir -p /federated/apps/mail/data/etc/dovecot/conf.d &> /dev/null
    mkdir -p /federated/apps/mail/data/home &> /dev/null
    cp /federated/certs/certs/$DOMAIN.crt /federated/certs/private/$DOMAIN.key /federated/apps/mail/data/root/certs/
    chgrp 5000 /federated/apps/mail/data/home
    chmod g+rwx /federated/apps/mail/data/home
  fi

cat > /federated/apps/mail/docker-compose.yml <<EOF
services:
  mail:
    image: docker.io/mailserver/docker-mailserver:\${IMAGE_VERSION}
    container_name: mail
    hostname: mail.$DOMAIN
    restart: always
    networks:
      core:
        ipv4_address: 192.168.0.16
    ports:
      - "25:25"
      - "143:143"
      - "465:465"
      - "587:587"
      - "993:993"
      - "4190:4190"
    volumes:
      - ./data/etc/dovecot/conf.d/90-sieve.conf:/etc/dovecot/conf.d/90-sieve.conf
      - ./data/home:/home
      - ./data/root/certs:/root/certs
      - ./data/var/mail:/var/mail/
      - ./data/var/mail-state:/var/mail-state/
      - ./data/var/log/mail:/var/log/mail/
      - ./data/tmp/docker-mailserver:/tmp/docker-mailserver/
      - /etc/localtime:/etc/localtime:ro
    env_file:
      - ./.env
    cap_add:
      - NET_ADMIN
      - SYS_PTRACE

networks:
  core:
    external: true
EOF

LDAP_SECRET=`cat /federated/apps/ldap/.ldap.secret`

cat > /federated/apps/mail/.env <<EOF
IMAGE_VERSION="$(current_version mail)"
ENABLE_SPAMASSASSIN=1
ENABLE_SPAMASSASSIN_KAM=1
SPAMASSASSIN_SPAM_TO_INBOX=1
ENABLE_CLAMAV=0
ENABLE_FAIL2BAN=1
ENABLE_POSTGREY=1
ENABLE_OPENDKIM=1
ENBALE_OPENDMARC=1
ONE_DIR=1
DMS_DEBUG=0
LOG_LEVEL=debug
ACCOUNT_PROVISIONER=LDAP
SSL_TYPE=manual
SSL_CERT_PATH=/root/certs/$DOMAIN.crt
SSL_KEY_PATH=/root/certs/$DOMAIN.key
LDAP_START_TLS=yes
DOVECOT_TLS=yes
SASLAUTHD_LDAP_START_TLS=yes
LDAP_SERVER_HOST=ldap://ldap.$DOMAIN
LDAP_SEARCH_BASE=ou=people,dc=federatedcomputer,dc=cloud
LDAP_BIND_DN=cn=admin,dc=federatedcomputer,dc=cloud
LDAP_BIND_PW=$LDAP_SECRET
LDAP_QUERY_FILTER_USER=(&(mail=%s)(mailEnabled=TRUE))
LDAP_QUERY_FILTER_GROUP=(&(mailGroupMember=%s)(mailEnabled=TRUE))
LDAP_QUERY_FILTER_ALIAS=(&(mailAlias=%s)(mailEnabled=TRUE))
LDAP_QUERY_FILTER_DOMAIN=(|(mail=*@%s)(mailAlias=*@%s))
# DOVECOT
DOVECOT_PASS_FILTER=(&(objectClass=inetOrgPerson)(mail=%u))
DOVECOT_USER_FILTER=(&(objectClass=inetOrgPerson)(mail=%u))
DOVECOT_USER_ATTRS=homeDirectory=home,=uid=5000,=gid=5000
# SASLAUTHD
ENABLE_SASLAUTHD=1
SASLAUTHD_MECHANISMS=ldap
SASLAUTHD_LDAP_SERVER=ldap://ldap.$DOMAIN
SASLAUTHD_LDAP_BIND_DN=cn=admin,dc=federatedcomputer,dc=cloud
SASLAUTHD_LDAP_PASSWORD=$LDAP_SECRET
SASLAUTHD_LDAP_SEARCH_BASE=ou=people,dc=federatedcomputer,dc=cloud
SASLAUTHD_LDAP_FILTER=(&(objectClass=inetOrgPerson)(mail=%U@%r))
POSTMASTER_ADDRESS=postmaster@localhost.localdomain
POSTFIX_MESSAGE_SIZE_LIMIT=100000000
ENABLE_MANAGESIEVE=1
EOF
chmod 600 /federated/apps/mail/.env

cat > /federated/apps/mail/data/tmp/docker-mailserver/postfix-main.cf <<'EOF'
smtpd_sender_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unknown_sender_domain, reject_sender_login_mismatch, reject_unknown_reverse_client_hostname, reject_unknown_client_hostname
smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_invalid_helo_hostname, check_policy_service unix:private/policyd-spf
smtpd_sender_login_maps = ldap:/etc/postfix/ldap-aliases.cf
smtpd_helo_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, reject_unknown_helo_hostname
EOF

cat > /federated/apps/mail/data/etc/dovecot/conf.d/90-sieve.conf <<'EOF'
##
## Settings for the Sieve interpreter
##

# Do not forget to enable the Sieve plugin in 15-lda.conf and 20-lmtp.conf
# by adding it to the respective mail_plugins= settings.

plugin {
  # The path to the user's main active script. If ManageSieve is used, this the
  # location of the symbolic link controlled by ManageSieve.
  sieve = ~/.dovecot.sieve

  # The default Sieve script when the user has none. This is a path to a global
  # sieve script file, which gets executed ONLY if user's private Sieve script
  # doesn't exist. Be sure to pre-compile this script manually using the sievec
  # command line tool.
  # --> See sieve_before fore executing scripts before the user's personal
  #     script.
  #sieve_default = /var/lib/dovecot/sieve/default.sieve

  # Directory for :personal include scripts for the include extension. This
  # is also where the ManageSieve service stores the user's scripts.
  sieve_dir = ~/sieve

  # Directory for :global include scripts for the include extension.
  #sieve_global_dir =

  # Path to a script file or a directory containing script files that need to be
  # executed before the user's script. If the path points to a directory, all
  # the Sieve scripts contained therein (with the proper .sieve extension) are
  # executed. The order of execution within a directory is determined by the
  # file names, using a normal 8bit per-character comparison. Multiple script
  # file or directory paths can be specified by appending an increasing number.
  sieve_before = /usr/lib/dovecot/sieve-global/before/
  #sieve_before2 =
  #sieve_before3 = (etc...)

  # Identical to sieve_before, only the specified scripts are executed after the
  # user's script (only when keep is still in effect!). Multiple script file or
  # directory paths can be specified by appending an increasing number.
  sieve_after = /usr/lib/dovecot/sieve-global/after/
  #sieve_after2 =
  #sieve_after2 = (etc...)

  # Which Sieve language extensions are available to users. By default, all
  # supported extensions are available, except for deprecated extensions or
  # those that are still under development. Some system administrators may want
  # to disable certain Sieve extensions or enable those that are not available
  # by default. This setting can use '+' and '-' to specify differences relative
  # to the default. For example `sieve_extensions = +imapflags' will enable the
  # deprecated imapflags extension in addition to all extensions were already
  # enabled by default.
  #sieve_extensions = +notify +imapflags
  sieve_extensions = +notify +imapflags +special-use +vnd.dovecot.pipe +vnd.dovecot.filter

  # Which Sieve language extensions are ONLY available in global scripts. This
  # can be used to restrict the use of certain Sieve extensions to administrator
  # control, for instance when these extensions can cause security concerns.
  # This setting has higher precedence than the `sieve_extensions' setting
  # (above), meaning that the extensions enabled with this setting are never
  # available to the user's personal script no matter what is specified for the
  # `sieve_extensions' setting. The syntax of this setting is similar to the
  # `sieve_extensions' setting, with the difference that extensions are
  # enabled or disabled for exclusive use in global scripts. Currently, no
  # extensions are marked as such by default.
  #sieve_global_extensions =

  # The Pigeonhole Sieve interpreter can have plugins of its own. Using this
  # setting, the used plugins can be specified. Check the Dovecot wiki
  # (wiki2.dovecot.org) or the pigeonhole website
  # (http://pigeonhole.dovecot.org) for available plugins.
  # The sieve_extprograms plugin is included in this release.
  #sieve_plugins =
  sieve_plugins = sieve_imapsieve sieve_extprograms

  # The separator that is expected between the :user and :detail
  # address parts introduced by the subaddress extension. This may
  # also be a sequence of characters (e.g. '--'). The current
  # implementation looks for the separator from the left of the
  # localpart and uses the first one encountered. The :user part is
  # left of the separator and the :detail part is right. This setting
  # is also used by Dovecot's LMTP service.
  #recipient_delimiter = +

  # The maximum size of a Sieve script. The compiler will refuse to compile any
  # script larger than this limit. If set to 0, no limit on the script size is
  # enforced.
  #sieve_max_script_size = 1M

  # The maximum number of actions that can be performed during a single script
  # execution. If set to 0, no limit on the total number of actions is enforced.
  #sieve_max_actions = 32

  # The maximum number of redirect actions that can be performed during a single
  # script execution. If set to 0, no redirect actions are allowed.
  #sieve_max_redirects = 4

  # The maximum number of personal Sieve scripts a single user can have. If set
  # to 0, no limit on the number of scripts is enforced.
  # (Currently only relevant for ManageSieve)
  #sieve_quota_max_scripts = 0

  # The maximum amount of disk storage a single user's scripts may occupy. If
  # set to 0, no limit on the used amount of disk storage is enforced.
  # (Currently only relevant for ManageSieve)
  #sieve_quota_max_storage = 0

  # Locations of programs that can be called by the sieve_extprograms plugin
  sieve_pipe_bin_dir = /usr/lib/dovecot/sieve-pipe
  sieve_filter_bin_dir = /usr/lib/dovecot/sieve-filter
  sieve_vacation_send_from_recipient = yes
}
EOF

cat > /federated/apps/mail/data/tmp/docker-mailserver/fail2ban-jail.cf <<'EOF'
[DEFAULT]

# "bantime" is the number of seconds that a host is banned.
bantime = 3h

# A host is banned if it has generated "maxretry" during the last "findtime"
# seconds.
findtime = 5m

# "maxretry" is the number of failures before a host get banned.
maxretry = 12

# "ignoreip" can be a list of IP addresses, CIDR masks or DNS hosts. Fail2ban
# will not ban a host which matches an address in this list. Several addresses
# can be defined using space (and/or comma) separator.
ignoreip = 127.0.0.1/8,192.168.0.0/16

# default ban action
# nftables-multiport: block IP only on affected port
# nftables-allports:  block IP on all ports
banaction = nftables-allports

[dovecot]
enabled = true

[postfix]
enabled = true

[postfix-sasl]
enabled = true

# This jail is used for manual bans.
# To ban an IP address use: setup.sh fail2ban ban <IP>
[custom]
enabled = true
bantime = 180d
port = smtp,pop3,pop3s,imap,imaps,submission,submissions,sieve
EOF

  echo -ne "done.\n"
}
start_mail() {
  # Start service with command to make sure it's up before proceeding
  start_service "mail" "nc -z 192.168.0.16 25 &> /dev/null" "25"

  # Generate the DKIM DNS key and setup
  run_command "docker exec mail setup config dkim"
  run_command "docker exec mail setup config dkim keysize 2048 domain $DOMAIN"

  docker exec mail bash -c "setup config dkim domain '$DOMAIN' &> /dev/null"
  [ $? -ne 0 ] && fail "Couldn't setup DKIM domain"

  # Insert the DKIM DNS TXT entry into /federated/apps/pdns container
  DKIM_RECORD_STRIP=`cat /federated/apps/mail/data/tmp/docker-mailserver/opendkim/keys/$DOMAIN/mail.txt | sed 's/.*(//'`
  DKIM_RECORD=`echo $DKIM_RECORD_STRIP | sed 's/).*//'`
  docker exec pdns pdnsutil add-record $DOMAIN mail._domainkey TXT 86400 "$DKIM_RECORD" &> /dev/null
  [ $? -ne 0 ] && fail "Couldn't insert DKIM record into /federated/apps/pdns container"

  # Insert the DMARC DNS TXT entry into /federated/apps/pdns container
  docker exec pdns pdnsutil add-record $DOMAIN _dmarc TXT 86400 "\"v=DMARC1; p=quarantine; rua=mailto:admin@$DOMAIN; ruf=mailto:admin@$DOMAIN; sp=none; ri=86400\"" &> /dev/null
  [ $? -ne 0 ] && fail "Couldn't insert DMARC record into /federated/apps/pdns container"

  # Stop and Start mail to reload DKIM
  run_command "/federated/bin/stop mail"
  run_command "/federated/bin/start mail"

  echo -ne "done.\n"
}
uninstall_mail() {
  echo -ne "* Uninstalling mail container.."

  # First stop the service
  cd /federated/apps/mail && docker compose -f docker-compose.yml -p mail down &> /dev/null

  # Delete the app directory
  rm -rf /federated/apps/mail

  # Delete the image
  docker.io/mailserver/docker-mailserver:$IMAGE_VERSION &> /dev/null

  # Delete the DNS record
  docker exec pdns pdnsutil delete-rrset $DOMAIN mail._domainkey TXT
  docker exec pdns pdnsutil delete-rrset $DOMAIN _dmarc TXT

  echo -ne "done.\n"
}
