From 98fa66b5ad13c8f203fcfe40d42240417951f218 Mon Sep 17 00:00:00 2001
From: Georg
Date: Mon, 13 Sep 2021 09:40:35 +0200
Subject: Init MC update + Dovecot/SOGo LDAP configuration

Signed-off-by: Georg <georg@lysergic.dev>
---
 mailcow/data/Dockerfiles/ldap/Dockerfile           |  16 ++
 mailcow/data/Dockerfiles/ldap/api.py               |  69 ++++++++
 mailcow/data/Dockerfiles/ldap/filedb.py            |  52 ++++++
 mailcow/data/Dockerfiles/ldap/syncer.py            | 186 +++++++++++++++++++++
 mailcow/data/Dockerfiles/ldap/syscid-ca.crt        |  32 ++++
 .../Dockerfiles/ldap/templates/dovecot/extra.conf  |   4 +
 .../ldap/templates/dovecot/ldap/passdb.conf        |   8 +
 .../Dockerfiles/ldap/templates/sogo/plist_ldap     |  43 +++++
 mailcow/data/conf/dovecot/extra.conf               |   5 +
 mailcow/data/conf/dovecot/ldap/passdb.conf         |   9 +
 mailcow/data/conf/sogo/plist_ldap                  |  44 +++++
 mailcow/docker-compose.override.yml                |  34 ++++
 mailcow/docker-compose.yml                         |  81 +++------
 mailcow/mailcow.conf                               |  13 +-
 14 files changed, 524 insertions(+), 72 deletions(-)
 create mode 100644 mailcow/data/Dockerfiles/ldap/Dockerfile
 create mode 100644 mailcow/data/Dockerfiles/ldap/api.py
 create mode 100644 mailcow/data/Dockerfiles/ldap/filedb.py
 create mode 100644 mailcow/data/Dockerfiles/ldap/syncer.py
 create mode 100644 mailcow/data/Dockerfiles/ldap/syscid-ca.crt
 create mode 100644 mailcow/data/Dockerfiles/ldap/templates/dovecot/extra.conf
 create mode 100644 mailcow/data/Dockerfiles/ldap/templates/dovecot/ldap/passdb.conf
 create mode 100644 mailcow/data/Dockerfiles/ldap/templates/sogo/plist_ldap
 create mode 100644 mailcow/data/conf/dovecot/extra.conf
 create mode 100644 mailcow/data/conf/dovecot/ldap/passdb.conf
 create mode 100644 mailcow/data/conf/sogo/plist_ldap
 create mode 100644 mailcow/docker-compose.override.yml

(limited to 'mailcow')

diff --git a/mailcow/data/Dockerfiles/ldap/Dockerfile b/mailcow/data/Dockerfiles/ldap/Dockerfile
new file mode 100644
index 0000000..9b7f507
--- /dev/null
+++ b/mailcow/data/Dockerfiles/ldap/Dockerfile
@@ -0,0 +1,16 @@
+FROM python:3-alpine
+
+RUN apk --no-cache add build-base openldap-dev python2-dev python3-dev
+RUN pip3 install python-ldap sqlalchemy requests
+
+COPY templates ./templates
+COPY api.py filedb.py syncer.py ./
+
+ADD syscid-ca.crt /usr/local/share/ca-certificates/syscid-ca.crt
+RUN chmod 644 /usr/local/share/ca-certificates/syscid-ca.crt && update-ca-certificates
+
+VOLUME [ "/db" ]
+VOLUME [ "/conf/dovecot" ]
+VOLUME [ "/conf/sogo" ]
+
+ENTRYPOINT [ "python3", "syncer.py" ]
diff --git a/mailcow/data/Dockerfiles/ldap/api.py b/mailcow/data/Dockerfiles/ldap/api.py
new file mode 100644
index 0000000..de056d9
--- /dev/null
+++ b/mailcow/data/Dockerfiles/ldap/api.py
@@ -0,0 +1,69 @@
+import random, string, sys
+import requests
+
+def __post_request(url, json_data):
+    api_url = f"{api_host}/{url}"
+    headers = {'X-API-Key': api_key, 'Content-type': 'application/json'}
+
+    req = requests.post(api_url, headers=headers, json=json_data,verify=is_ssl_verify)
+    rsp = req.json()
+    req.close()
+
+    if isinstance(rsp, list):
+        rsp = rsp[0]
+
+    if not "type" in rsp or not "msg" in rsp:
+        sys.exit(f"API {url}: got response without type or msg from Mailcow API")
+    
+    if rsp['type'] != 'success':
+        sys.exit(f"API {url}: {rsp['type']} - {rsp['msg']}")
+
+def add_user(email, name, active):
+    password = ''.join(random.choices(string.ascii_letters + string.digits, k=20))
+    json_data = {
+        'local_part':email.split('@')[0],
+        'domain':email.split('@')[1],
+        'name':name,
+        'password':password,
+        'password2':password,
+        "active": 1 if active else 0
+    }
+
+    __post_request('api/v1/add/mailbox', json_data)
+
+def edit_user(email, active=None, name=None):
+    attr = {}
+    if (active is not None):
+        attr['active'] = 1 if active else 0
+    if (name is not None):
+        attr['name'] = name
+
+    json_data = {
+        'items': [email],
+        'attr': attr
+    }
+
+    __post_request('api/v1/edit/mailbox', json_data)
+
+def __delete_user(email):
+    json_data = [email]
+
+    __post_request('api/v1/delete/mailbox', json_data)
+
+def check_user(email):
+    url = f"{api_host}/api/v1/get/mailbox/{email}"
+    headers = {'X-API-Key': api_key, 'Content-type': 'application/json'}
+    req = requests.get(url, headers=headers,verify=is_ssl_verify)
+    rsp = req.json()
+    req.close()
+    
+    if not isinstance(rsp, dict):
+        sys.exit("API get/mailbox: got response of a wrong type")
+
+    if (not rsp):
+        return (False, False, None)
+
+    if 'active_int' not in rsp and rsp['type'] == 'error':
+        sys.exit(f"API {url}: {rsp['type']} - {rsp['msg']}")
+    
+    return (True, bool(rsp['active_int']), rsp['name'])
diff --git a/mailcow/data/Dockerfiles/ldap/filedb.py b/mailcow/data/Dockerfiles/ldap/filedb.py
new file mode 100644
index 0000000..10cadb9
--- /dev/null
+++ b/mailcow/data/Dockerfiles/ldap/filedb.py
@@ -0,0 +1,52 @@
+import datetime, os
+
+import logging
+logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%d.%m.%y %H:%M:%S', level=logging.INFO)
+
+import sqlalchemy
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy import create_engine, Column, String, Boolean, DateTime
+from sqlalchemy.orm import sessionmaker
+
+db_file = 'db/ldap-mailcow.sqlite3'
+
+Base = declarative_base()
+
+class DbUser(Base): # type: ignore
+    __tablename__ = 'users'
+    email = Column(String, primary_key=True)
+    active = Column(Boolean, nullable=False)
+    last_seen = Column(DateTime, nullable=False)
+
+Session = sessionmaker()
+
+if not os.path.isfile(db_file):
+    logging.info (f"New database file created: {db_file}")
+
+db_engine = create_engine(f"sqlite:///{db_file}") # echo=True
+Base.metadata.create_all(db_engine)
+Session.configure(bind=db_engine)
+session = Session()
+session_time = datetime.datetime.now()
+
+def get_unchecked_active_users():
+    query = session.query(DbUser.email).filter(DbUser.last_seen != session_time).filter(DbUser.active == True)
+
+    return [x.email for x in query]
+
+def add_user(email, active=True):
+    session.add(DbUser(email=email, active=active, last_seen=session_time))
+    session.commit()
+
+def check_user(email):
+    user = session.query(DbUser).filter_by(email=email).first()
+    if user is None:
+        return (False, False)
+    user.last_seen = session_time
+    session.commit()
+    return (True, user.active)
+
+def user_set_active_to(email, active):
+    user = session.query(DbUser).filter_by(email=email).first()
+    user.active = active
+    session.commit()
\ No newline at end of file
diff --git a/mailcow/data/Dockerfiles/ldap/syncer.py b/mailcow/data/Dockerfiles/ldap/syncer.py
new file mode 100644
index 0000000..e9a3f02
--- /dev/null
+++ b/mailcow/data/Dockerfiles/ldap/syncer.py
@@ -0,0 +1,186 @@
+import sys, os, string, time, datetime
+import ldap
+
+import filedb, api
+
+from string import Template
+from pathlib import Path
+
+import logging
+logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%d.%m.%y %H:%M:%S', level=logging.INFO)
+
+def main():
+    global config
+    config = read_config()
+
+    passdb_conf = read_dovecot_passdb_conf_template()
+    plist_ldap = read_sogo_plist_ldap_template()
+    extra_conf = read_dovecot_extra_conf()
+
+    passdb_conf_changed = apply_config('conf/dovecot/ldap/passdb.conf', config_data = passdb_conf)
+    extra_conf_changed = apply_config('conf/dovecot/extra.conf', config_data = extra_conf)
+    plist_ldap_changed = apply_config('conf/sogo/plist_ldap', config_data = plist_ldap)
+
+    if passdb_conf_changed or extra_conf_changed or plist_ldap_changed:
+        logging.info ("One or more config files have been changed, please make sure to restart dovecot-mailcow and sogo-mailcow!")
+
+    api.api_host = config['API_HOST']
+    api.api_key = config['API_KEY']
+    api.is_ssl_verify = bool(int(config['API_SSL_VERIFY']))
+
+    while (True):
+        sync()
+        interval = int(config['SYNC_INTERVAL'])
+        logging.info(f"Sync finished, sleeping {interval} seconds before next cycle")
+        time.sleep(interval)
+
+def sync():
+    ldap_connector = ldap.initialize(f"{config['LDAP_HOST']}")
+    ldap_connector.set_option(ldap.OPT_REFERRALS, 0)
+    ldap_connector.simple_bind_s(config['LDAP_BIND_DN'], config['LDAP_BIND_DN_PASSWORD'])
+
+    #ldap_results = ldap_connector.search_s(config['LDAP_BASE_DN'], ldap.SCOPE_SUBTREE,
+    #            '(&(objectClass=user)(objectCategory=person))',
+    #            ['userPrincipalName', 'cn', 'userAccountControl'])
+
+    ldap_results = ldap_connector.search_s(config['LDAP_BASE_DN'], ldap.SCOPE_SUBTREE, config['LDAP_FILTER'],
+                                           config['LDAP_FIELDS_MAIL'], config['LDAP_FIELDS_NAME'])
+
+    ldap_results = map(lambda x: (
+        [i.decode() for i in x[1][config['LDAP_FIELDS_MAIL']]],
+        x[1][config['LDAP_FIELDS_NAME']][0].decode(),
+        #False if int(x[1]['userAccountControl'][0].decode()) & 0b10 else True), ldap_results)
+        True), ldap_results)
+
+    filedb.session_time = datetime.datetime.now()
+
+    for (ldap_email, ldap_name, ldap_active) in ldap_results:
+        for email in ldap_email:
+            if email.split('@')[1] not in config['EMAIL_DOMAINS']:
+                continue
+            (db_user_exists, db_user_active) = filedb.check_user(email)
+            (api_user_exists, api_user_active, api_name) = api.check_user(email)
+
+            unchanged = True
+
+            if not db_user_exists:
+                filedb.add_user(email, ldap_active)
+                (db_user_exists, db_user_active) = (True, ldap_active)
+                logging.info (f"Added filedb user: {email} (Active: {ldap_active})")
+                unchanged = False
+
+            if not api_user_exists:
+                api.add_user(email, ldap_name, ldap_active)
+                (api_user_exists, api_user_active, api_name) = (True, ldap_active, ldap_name)
+                logging.info (f"Added Mailcow user: {email} (Active: {ldap_active})")
+                unchanged = False
+
+            if db_user_active != ldap_active:
+                filedb.user_set_active_to(email, ldap_active)
+                logging.info (f"{'Activated' if ldap_active else 'Deactived'} {email} in filedb")
+                unchanged = False
+
+            if api_user_active != ldap_active:
+                api.edit_user(email, active=ldap_active)
+                logging.info (f"{'Activated' if ldap_active else 'Deactived'} {email} in Mailcow")
+                unchanged = False
+
+            if api_name != ldap_name:
+                api.edit_user(email, name=ldap_name)
+                logging.info (f"Changed name of {email} in Mailcow to {ldap_name}")
+                unchanged = False
+
+            if unchanged:
+                logging.info (f"Checked user {email}, unchanged")
+
+    for email in filedb.get_unchecked_active_users():
+        (api_user_exists, api_user_active, _) = api.check_user(email)
+
+        if (api_user_active and api_user_active):
+            api.edit_user(email, active=False)
+            logging.info (f"Deactivated user {email} in Mailcow, not found in LDAP")
+
+        filedb.user_set_active_to(email, False)
+        logging.info (f"Deactivated user {email} in filedb, not found in LDAP")
+
+def apply_config(config_file, config_data):
+    if os.path.isfile(config_file):
+        with open(config_file) as f:
+            old_data = f.read()
+
+        if old_data.strip() == config_data.strip():
+            logging.info(f"Config file {config_file} unchanged")
+            return False
+
+        backup_index = 1
+        backup_file = f"{config_file}.ldap_mailcow_bak"
+        while os.path.exists(backup_file):
+            backup_file = f"{config_file}.ldap_mailcow_bak.{backup_index}"
+            backup_index += 1
+
+        os.rename(config_file, backup_file)
+        logging.info(f"Backed up {config_file} to {backup_file}")
+
+    Path(os.path.dirname(config_file)).mkdir(parents=True, exist_ok=True)
+
+    print(config_data, file=open(config_file, 'w'))
+
+    logging.info(f"Saved generated config file to {config_file}")
+    return True
+
+def read_config():
+    required_config_keys = [
+        'LDAP-MAILCOW_LDAP_HOST', 
+        'LDAP-MAILCOW_LDAP_BASE_DN',
+        'LDAP-MAILCOW_LDAP_BIND_DN', 
+        'LDAP-MAILCOW_LDAP_BIND_DN_PASSWORD',
+        'LDAP-MAILCOW_LDAP_FILTER',
+        'LDAP-MAILCOW_LDAP_FIELDS_MAIL',
+        'LDAP-MAILCOW_LDAP_FIELDS_NAME',
+        'LDAP-MAILCOW_API_HOST', 
+        'LDAP-MAILCOW_API_KEY',
+        'LDAP-MAILCOW_API_SSL_VERIFY',
+        'LDAP-MAILCOW_SYNC_INTERVAL',
+        'LDAP-MAILCOW_EMAIL_DOMAINS'
+    ]
+
+    config = {}
+
+    for config_key in required_config_keys:
+        if config_key not in os.environ:
+            sys.exit (f"Required envrionment value {config_key} is not set")
+
+        config[config_key.replace('LDAP-MAILCOW_', '')] = os.environ[config_key]
+    config['EMAIL_DOMAINS'] = config['EMAIL_DOMAINS'].split(',')
+    return config
+
+def read_dovecot_passdb_conf_template():
+    with open('templates/dovecot/ldap/passdb.conf') as f:
+        data = Template(f.read())
+
+    return data.substitute(
+        ldap_host=config['LDAP_HOST'],
+        ldap_base_dn=config['LDAP_BASE_DN'],
+        ldap_bind_dn=config['LDAP_BIND_DN'],
+        ldap_bind_dn_password=config['LDAP_BIND_DN_PASSWORD']
+        )
+
+def read_sogo_plist_ldap_template():
+    with open('templates/sogo/plist_ldap') as f:
+        data = Template(f.read())
+
+    return data.substitute(
+        ldap_host=config['LDAP_HOST'], 
+        ldap_base_dn=config['LDAP_BASE_DN'],
+        ldap_bind_dn=config['LDAP_BIND_DN'],
+        ldap_bind_dn_password=config['LDAP_BIND_DN_PASSWORD']
+        )
+
+def read_dovecot_extra_conf():
+    with open('templates/dovecot/extra.conf') as f:
+        data = f.read()
+
+    return data
+
+if __name__ == '__main__':
+    main()
diff --git a/mailcow/data/Dockerfiles/ldap/syscid-ca.crt b/mailcow/data/Dockerfiles/ldap/syscid-ca.crt
new file mode 100644
index 0000000..3ac52fa
--- /dev/null
+++ b/mailcow/data/Dockerfiles/ldap/syscid-ca.crt
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFfzCCA2egAwIBAgIUUtCa6jguJLt20qzq/MR82JX/6JQwDQYJKoZIhvcNAQEL
+BQAwTzEUMBIGA1UECgwLTGliZXJ0YUNhc2ExEzARBgNVBAMMCnN5c2NpZC5jb20x
+IjAgBgkqhkiG9w0BCQEWE3N5c3RlbUBseXNlcmdpYy5kZXYwHhcNMjEwODEwMDAy
+MjM2WhcNMjIwODEwMDAyMjM2WjBPMRQwEgYDVQQKDAtMaWJlcnRhQ2FzYTETMBEG
+A1UEAwwKc3lzY2lkLmNvbTEiMCAGCSqGSIb3DQEJARYTc3lzdGVtQGx5c2VyZ2lj
+LmRldjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAO0m7lpEZwGB1pxo
+24RESADjbnk6iGmH783cuMo7jzio6P5vZk8FRD0/8Gmli/sOe8oZ2hGE5sNx4RKK
+4g7kDVnYznHS6k5zpBzU4FP9wVMho/TcfaXlCdSwj6Ih0mLxDYzvX0l12Gi1K9gg
+0HxdG2XPfslQbk6py1jQYVkRjwZIj2ya7t7/fNyn6S7flVUIvvcvZd3eNvAlg4ZU
+wDV1H5mF3s42Iv5TOEYi88n7yXUex5I9xi5NqG/qOuYuC69yYobI/WjfId7bUDPT
+UjZJFD5wHUHwtBmjp2bdyzdl9Z9iJp24jhR3Syi4h/BjYFwUG793PjP8DZBWtrOC
+jHoOwkCyYFfOTa8n+Knb8i2FSuX4TgMZeFwcLpSsecBIjknKHPNYW2NTcP7S/Xbl
+KpP2fpN7JBlR8WTi9+WQZVHuMfU1rjp3Kjwj9dmjcWsOMuMEqJUZMSsMpUBKULbq
+6QFhMPJL+yYDHg1S0E3ymHRU94mlQ57mQwAg0AraCDtdPR3zw+gh2k1hUEkva26G
+zHYigssbhCT81Dp4Ez65tKHZoKYKvgywJ6gb7PURS8Ued8PSDEhJd18WN22l4xQP
+k08fg+mB5gHXNNGzYVETPalrWmk9IHczQnDITWM1hj14VhIcwa9oMNesIllviL7/
+BRjiz6jHZfc3Htp4NZ/5sBgFDUelAgMBAAGjUzBRMB0GA1UdDgQWBBSU/TIhYSYN
+HQ3+7ueHMmkVD8BZRzAfBgNVHSMEGDAWgBSU/TIhYSYNHQ3+7ueHMmkVD8BZRzAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCLf0GU+mrZo4l7qMJz
+WfNFxZv3E5GFRgl1NlUfJM2hnpgqrT7ukXcRnY4n9KtLx14QWHpPdX7KyLsSqx6X
+UoPsJNywYQNVyAQ0qddY4glGV8u6+QE1zN1yUw6CMbsqWz50T76r1Y1CRZuMyffU
+OeVhBM17sWibgDbev0SmG12uYTkq7qmbCKOWUhbaL1jCE1yvu9ZFFXCQ/OaAMyn9
+fYyyn48z7MHsyISuBdAcJkR2JkIgL4oZufw5hcecZ1wcnmYTRm6owuhUsZ2FYXkU
+5o2Pn6nce1QEaUKsik8xBNA0/jIBCkiPDb5/eIA8Yys7pb/DeFEE/X0JM9rhoOk7
+tXOvxV2S7Y1xRqVwa3mrlp/0yXHuBb+u3/1+jsxkaeSPQ8FRejPIZzeOGhVHPaub
+RdvzSO0TBK54vLA0CrkDRLgFdyuzsvm6VMVqGpcKn/aaju7pLI/knJzITaHHxzqV
+wxmA0kf/68+wVvdICZt4R3iqSU9KVmCmh6owTixNTgh1wLmFIyMl2VhOwibFVg4L
+600gIb59wEV4tWnYEX3Ugsw8g0ZXoqPDA27CPlpmgaVXwBv1qssRYnUreZEXxEN3
+4A1UOTdjMPn9v1wqmBwTCb9MULX60byX72YPMOnuSAQyptbXx8oMvInK91T5ivZ0
+JpmySX/Gfpgrr7HSI9+cD3eUcQ==
+-----END CERTIFICATE-----
diff --git a/mailcow/data/Dockerfiles/ldap/templates/dovecot/extra.conf b/mailcow/data/Dockerfiles/ldap/templates/dovecot/extra.conf
new file mode 100644
index 0000000..7e97513
--- /dev/null
+++ b/mailcow/data/Dockerfiles/ldap/templates/dovecot/extra.conf
@@ -0,0 +1,4 @@
+passdb {
+  args = /etc/dovecot/ldap/passdb.conf
+  driver = ldap
+}
diff --git a/mailcow/data/Dockerfiles/ldap/templates/dovecot/ldap/passdb.conf b/mailcow/data/Dockerfiles/ldap/templates/dovecot/ldap/passdb.conf
new file mode 100644
index 0000000..f81a57a
--- /dev/null
+++ b/mailcow/data/Dockerfiles/ldap/templates/dovecot/ldap/passdb.conf
@@ -0,0 +1,8 @@
+uris = $ldap_host
+ldap_version = 3
+base = $ldap_base_dn
+auth_bind = yes
+dn = $ldap_bind_dn
+dnpass = $ldap_bind_dn_password
+pass_attrs = userPassword=password
+pass_filter = (&(memberof=cn=syscid_email_mailcow,ou=syscid-groups,dc=syscid,dc=com)(|(uid=%n)(mail=%u)))
diff --git a/mailcow/data/Dockerfiles/ldap/templates/sogo/plist_ldap b/mailcow/data/Dockerfiles/ldap/templates/sogo/plist_ldap
new file mode 100644
index 0000000..87a0367
--- /dev/null
+++ b/mailcow/data/Dockerfiles/ldap/templates/sogo/plist_ldap
@@ -0,0 +1,43 @@
+<!-- LDAP Configuration -->
+<dict>
+    <key>type</key>
+    <string>ldap</string>
+    <key>id</key>
+    <string>$${line}_ldap</string>
+
+    <key>CNFieldName</key>
+    <string>cn</string>
+    <key>IDFieldName</key>
+    <string>uid</string>
+    <key>UIDFieldName</key>
+    <string>uid</string>
+
+    <key>baseDN</key>
+    <string>$ldap_base_dn</string>
+
+    <key>bindDN</key>
+    <string>$ldap_bind_dn</string>
+    <key>bindPassword</key>
+    <string>$ldap_bind_dn_password</string>
+    <key>bindFields</key>
+    <array>
+        <string>uid</string>
+        <string>mail</string>
+    </array>
+
+    <key>bindAsCurrentUser</key>
+    <string>YES</string>
+
+    <key>hostname</key>
+    <string>$ldap_host</string>
+    <key>canAuthenticate</key>
+    <string>YES</string>
+
+    <key>isAddressBook</key>
+    <string>NO</string>
+    <key>displayName</key>
+    <string>LibertaCasa</string>
+
+    <key>scope</key>
+    <string>SUB</string>
+</dict>
diff --git a/mailcow/data/conf/dovecot/extra.conf b/mailcow/data/conf/dovecot/extra.conf
new file mode 100644
index 0000000..2ec91d8
--- /dev/null
+++ b/mailcow/data/conf/dovecot/extra.conf
@@ -0,0 +1,5 @@
+passdb {
+  args = /etc/dovecot/ldap/passdb.conf
+  driver = ldap
+}
+
diff --git a/mailcow/data/conf/dovecot/ldap/passdb.conf b/mailcow/data/conf/dovecot/ldap/passdb.conf
new file mode 100644
index 0000000..28feedd
--- /dev/null
+++ b/mailcow/data/conf/dovecot/ldap/passdb.conf
@@ -0,0 +1,9 @@
+uris = ldaps://orpheus.syscid.com
+ldap_version = 3
+base = OU=syscid-users,DC=syscid,DC=com
+auth_bind = yes
+dn = $BINDDN
+dnpass = $BINDSEC
+pass_attrs = userPassword=password
+pass_filter = (&(memberof=cn=syscid_email_mailcow,ou=syscid-groups,dc=syscid,dc=com)(|(uid=%n)(mail=%u)))
+
diff --git a/mailcow/data/conf/sogo/plist_ldap b/mailcow/data/conf/sogo/plist_ldap
new file mode 100644
index 0000000..dfa203e
--- /dev/null
+++ b/mailcow/data/conf/sogo/plist_ldap
@@ -0,0 +1,44 @@
+<!-- LDAP Configuration -->
+<dict>
+    <key>type</key>
+    <string>ldap</string>
+    <key>id</key>
+    <string>${line}_ldap</string>
+
+    <key>CNFieldName</key>
+    <string>cn</string>
+    <key>IDFieldName</key>
+    <string>uid</string>
+    <key>UIDFieldName</key>
+    <string>uid</string>
+
+    <key>baseDN</key>
+    <string>OU=syscid-users,DC=syscid,DC=com</string>
+
+    <key>bindDN</key>
+    <string>$BINDDN</string>
+    <key>bindPassword</key>
+    <string>$BINDSEC</string>
+    <key>bindFields</key>
+    <array>
+        <string>uid</string>
+        <string>mail</string>
+    </array>
+
+    <key>bindAsCurrentUser</key>
+    <string>YES</string>
+
+    <key>hostname</key>
+    <string>ldaps://orpheus.syscid.com</string>
+    <key>canAuthenticate</key>
+    <string>YES</string>
+
+    <key>isAddressBook</key>
+    <string>NO</string>
+    <key>displayName</key>
+    <string>LibertaCasa</string>
+
+    <key>scope</key>
+    <string>SUB</string>
+</dict>
+
diff --git a/mailcow/docker-compose.override.yml b/mailcow/docker-compose.override.yml
new file mode 100644
index 0000000..be769e2
--- /dev/null
+++ b/mailcow/docker-compose.override.yml
@@ -0,0 +1,34 @@
+version: '2.1'
+services:
+
+        ldap-mailcow:
+            image: mailcow/ldap
+            network_mode: host
+            container_name: mailcowcustomized_ldap
+            depends_on:
+                - nginx-mailcow
+            volumes:
+                - ./data/ldap:/db:rw
+                - ./data/conf/dovecot:/conf/dovecot:rw
+                - ./data/conf/sogo:/conf/sogo:rw
+            environment:
+                - LDAP-MAILCOW_LDAP_HOST=ldaps://orpheus.syscid.com
+                - LDAP-MAILCOW_LDAP_BASE_DN=OU=syscid-users,DC=syscid,DC=com
+                - LDAP-MAILCOW_LDAP_BIND_DN=$BINDDN
+                - LDAP-MAILCOW_LDAP_BIND_DN_PASSWORD=$BINDSEC
+                - LDAP-MAILCOW_LDAP_FILTER=(objectClass=inetOrgPerson)
+                - LDAP-MAILCOW_LDAP_FIELDS_MAIL=mail
+                - LDAP-MAILCOW_LDAP_FIELDS_NAME=cn
+                - LDAP-MAILCOW_API_HOST=$MAILCOWHOST
+                - LDAP-MAILCOW_API_KEY=$MAILCOWAPI
+                - LDAP-MAILCOW_API_SSL_VERIFY=1
+                - LDAP-MAILCOW_SYNC_INTERVAL=300
+                - LDAP-MAILCOW_EMAIL_DOMAINS=syscid.com
+
+        dovecot-mailcow:
+           extra_hosts:
+                - "orpheus.syscid.com:192.168.0.115"
+
+        sogo-mailcow:
+           extra_hosts:
+                - "orpheus.syscid.com:192.168.0.115"
diff --git a/mailcow/docker-compose.yml b/mailcow/docker-compose.yml
index 3d747c5..b1396c0 100644
--- a/mailcow/docker-compose.yml
+++ b/mailcow/docker-compose.yml
@@ -2,7 +2,7 @@ version: '2.1'
 services:
 
     unbound-mailcow:
-      image: mailcow/unbound:1.13
+      image: mailcow/unbound:1.14
       environment:
         - TZ=${TZ}
       volumes:
@@ -41,7 +41,7 @@ services:
             - mysql
 
     redis-mailcow:
-      image: redis:5-alpine
+      image: redis:6-alpine
       volumes:
         - redis-vol-1:/data/:Z
       restart: always
@@ -49,6 +49,8 @@ services:
         - "${REDIS_PORT:-127.0.0.1:7654}:6379"
       environment:
         - TZ=${TZ}
+      sysctls:
+        - net.core.somaxconn=4096
       networks:
         mailcow-network:
           ipv4_address: ${IPV4_NETWORK:-172.22.1}.249
@@ -56,7 +58,7 @@ services:
             - redis
 
     clamd-mailcow:
-      image: mailcow/clamd:1.40
+      image: mailcow/clamd:1.41
       restart: always
       dns:
         - ${IPV4_NETWORK:-172.22.1}.254
@@ -71,7 +73,7 @@ services:
             - clamd
 
     rspamd-mailcow:
-      image: mailcow/rspamd:1.76
+      image: mailcow/rspamd:1.79
       stop_grace_period: 30s
       depends_on:
         - dovecot-mailcow
@@ -101,7 +103,7 @@ services:
             - rspamd
 
     php-fpm-mailcow:
-      image: mailcow/phpfpm:1.75
+      image: mailcow/phpfpm:1.76
       command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
       depends_on:
         - redis-mailcow
@@ -122,7 +124,6 @@ services:
         - ./data/conf/dovecot/global_sieve_before:/global_sieve/before:z
         - ./data/conf/dovecot/global_sieve_after:/global_sieve/after:z
         - ./data/assets/templates:/tpls:z
-        - ./data/conf/ejabberd/autogen:/ejabberd/:z
         - ./data/conf/nginx/:/etc/nginx/conf.d/:z
       dns:
         - ${IPV4_NETWORK:-172.22.1}.254
@@ -146,8 +147,6 @@ services:
         - SUBMISSION_PORT=${SUBMISSION_PORT:-587}
         - SMTPS_PORT=${SMTPS_PORT:-465}
         - SMTP_PORT=${SMTP_PORT:-25}
-        - XMPP_C2S_PORT=${XMPP_C2S_PORT:-5222}
-        - XMPP_S2S_PORT=${XMPP_S2S_PORT:-5269}
         - API_KEY=${API_KEY:-invalid}
         - API_KEY_READ_ONLY=${API_KEY_READ_ONLY:-invalid}
         - API_ALLOW_FROM=${API_ALLOW_FROM:-invalid}
@@ -164,7 +163,7 @@ services:
             - phpfpm
 
     sogo-mailcow:
-      image: mailcow/sogo:1.99
+      image: mailcow/sogo:1.101
       environment:
         - DBNAME=${DBNAME}
         - DBUSER=${DBUSER}
@@ -184,6 +183,7 @@ services:
       dns:
         - ${IPV4_NETWORK:-172.22.1}.254
       volumes:
+        - ./data/hooks/sogo:/hooks:Z
         - ./data/conf/sogo/:/etc/sogo/:z
         - ./data/web/inc/init_db.inc.php:/init_db.inc.php:Z
         - ./data/conf/sogo/custom-favicon.ico:/usr/lib/GNUstep/SOGo/WebServerResources/img/sogo.ico:z
@@ -212,7 +212,7 @@ services:
             - sogo
 
     dovecot-mailcow:
-      image: mailcow/dovecot:1.145
+      image: mailcow/dovecot:1.156
       depends_on:
         - mysql-mailcow
       dns:
@@ -244,7 +244,7 @@ services:
         - MAILCOW_PASS_SCHEME=${MAILCOW_PASS_SCHEME:-BLF-CRYPT}
         - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1}
         - ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n}
-        - MAILDIR_GC_TIME=${MAILDIR_GC_TIME:-1440}
+        - MAILDIR_GC_TIME=${MAILDIR_GC_TIME:-7200}
         - ACL_ANYONE=${ACL_ANYONE:-disallow}
         - SKIP_SOLR=${SKIP_SOLR:-y}
         - MAILDIR_SUB=${MAILDIR_SUB:-}
@@ -292,7 +292,7 @@ services:
             - dovecot
 
     postfix-mailcow:
-      image: mailcow/postfix:1.61
+      image: mailcow/postfix:1.66
       depends_on:
         - mysql-mailcow
       volumes:
@@ -323,6 +323,7 @@ services:
         - ${IPV4_NETWORK:-172.22.1}.254
       networks:
         mailcow-network:
+          ipv4_address: ${IPV4_NETWORK:-172.22.1}.253
           aliases:
             - postfix
 
@@ -347,7 +348,6 @@ services:
       command: /bin/sh -c "envsubst < /etc/nginx/conf.d/templates/listen_plain.template > /etc/nginx/conf.d/listen_plain.active &&
         envsubst < /etc/nginx/conf.d/templates/listen_ssl.template > /etc/nginx/conf.d/listen_ssl.active &&
         envsubst < /etc/nginx/conf.d/templates/sogo.template > /etc/nginx/conf.d/sogo.active &&
-        . /etc/nginx/conf.d/templates/sogo.auth_request.template.sh > /etc/nginx/conf.d/sogo_proxy_auth.active &&
         . /etc/nginx/conf.d/templates/server_name.template.sh > /etc/nginx/conf.d/server_name.active &&
         . /etc/nginx/conf.d/templates/sites.template.sh > /etc/nginx/conf.d/sites.active &&
         . /etc/nginx/conf.d/templates/sogo_eas.template.sh > /etc/nginx/conf.d/sogo_eas.active &&
@@ -356,7 +356,6 @@ services:
         until ping sogo -c1 > /dev/null; do sleep 1; done &&
         until ping redis -c1 > /dev/null; do sleep 1; done &&
         until ping rspamd -c1 > /dev/null; do sleep 1; done &&
-        until ping ejabberd -c1 > /dev/null; do sleep 1; done &&
         exec nginx -g 'daemon off;'"
       environment:
         - HTTPS_PORT=${HTTPS_PORT:-443}
@@ -387,7 +386,7 @@ services:
     acme-mailcow:
       depends_on:
         - nginx-mailcow
-      image: mailcow/acme:1.79
+      image: mailcow/acme:1.80
       dns:
         - ${IPV4_NETWORK:-172.22.1}.254
       environment:
@@ -423,7 +422,7 @@ services:
             - acme
 
     netfilter-mailcow:
-      image: mailcow/netfilter:1.43
+      image: mailcow/netfilter:1.44
       stop_grace_period: 30s
       depends_on:
         - dovecot-mailcow
@@ -446,7 +445,7 @@ services:
         - /lib/modules:/lib/modules:ro
 
     watchdog-mailcow:
-      image: mailcow/watchdog:1.91
+      image: mailcow/watchdog:1.94
       # Debug
       #command: /watchdog.sh
       dns:
@@ -499,7 +498,6 @@ services:
         - RATELIMIT_THRESHOLD=${RATELIMIT_THRESHOLD:-1}
         - FAIL2BAN_THRESHOLD=${FAIL2BAN_THRESHOLD:-1}
         - ACME_THRESHOLD=${ACME_THRESHOLD:-1}
-        - IPV6NAT_THRESHOLD=${IPV6NAT_THRESHOLD:-1}
         - RSPAMD_THRESHOLD=${RSPAMD_THRESHOLD:-5}
         - OLEFY_THRESHOLD=${OLEFY_THRESHOLD:-5}
         - MAILQ_THRESHOLD=${MAILQ_THRESHOLD:-20}
@@ -510,7 +508,7 @@ services:
             - watchdog
 
     dockerapi-mailcow:
-      image: mailcow/dockerapi:1.38
+      image: mailcow/dockerapi:1.39
       security_opt:
         - label=disable
       restart: always
@@ -544,7 +542,7 @@ services:
             - solr
 
     olefy-mailcow:
-      image: mailcow/olefy:1.7
+      image: mailcow/olefy:1.8
       restart: always
       environment:
         - TZ=${TZ}
@@ -561,44 +559,6 @@ services:
           aliases:
             - olefy
 
-    ejabberd-mailcow:
-      image: mailcow/ejabberd:1.6
-      volumes:
-        - ./data/conf/ejabberd/ejabberd.yml:/home/ejabberd/conf/ejabberd.yml:z
-        - xmpp-vol-1:/home/ejabberd/database:z
-        - xmpp-upload-vol-1:/var/www/upload:z
-        - ./data/assets/ejabberd/sqlite:/sqlite:z
-        - ./data/conf/ejabberd/autogen:/ejabberd/:z
-        - mysql-socket-vol-1:/var/run/mysqld/:z
-        - ./data/assets/ssl:/ssl:ro,z
-      restart: always
-      dns:
-        - ${IPV4_NETWORK:-172.22.1}.254
-      hostname: ejabberd.mailcow.local
-      labels:
-        ofelia.enabled: "true"
-        ofelia.job-exec.ejabberd_certs.schedule: "@every 168h"
-        ofelia.job-exec.ejabberd_certs.command: "/sbin/su-exec ejabberd /home/ejabberd/bin/ejabberdctl --node ejabberd@$${MAILCOW_HOSTNAME} request-certificate all"
-      extra_hosts:
-        - "${MAILCOW_HOSTNAME}:127.0.0.1"
-      environment:
-        - TZ=${TZ}
-        - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
-        - MASTER=${MASTER:-y}
-        - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1}
-        - XMPP_HTTPS_PORT=${XMPP_HTTPS_PORT:-5443}
-        - DBNAME=${DBNAME}
-        - DBUSER=${DBUSER}
-        - DBPASS=${DBPASS}
-      ports:
-        - "${XMPP_C2S_PORT:-5222}:5222"
-        - "${XMPP_S2S_PORT:-5269}:5269"
-        - "${XMPP_HTTPS_PORT:-5443}:5443"
-      networks:
-        mailcow-network:
-          aliases:
-            - ejabberd
-
     ofelia-mailcow:
       image: mcuadros/ofelia:latest
       restart: always
@@ -607,9 +567,10 @@ services:
       depends_on:
         - sogo-mailcow
         - dovecot-mailcow
-        - ejabberd-mailcow
       labels:
         ofelia.enabled: "true"
+      security_opt:
+        - label=disable
       volumes:
         - /var/run/docker.sock:/var/run/docker.sock:ro
       networks:
@@ -671,5 +632,3 @@ volumes:
   crypt-vol-1:
   sogo-web-vol-1:
   sogo-userdata-backup-vol-1:
-  xmpp-vol-1:
-  xmpp-upload-vol-1:
diff --git a/mailcow/mailcow.conf b/mailcow/mailcow.conf
index 2b1f13b..f3904ab 100644
--- a/mailcow/mailcow.conf
+++ b/mailcow/mailcow.conf
@@ -16,8 +16,8 @@ MAILCOW_PASS_SCHEME=BLF-CRYPT
 # SQL database configuration
 # ------------------------------
 
-DBNAME=mailcow
-DBUSER=mailcow
+DBNAME=$DBNAME
+DBUSER=$DBUSER
 
 # Please use long, random alphanumeric strings (A-Za-z0-9)
 
@@ -125,15 +125,6 @@ ENABLE_SSL_SNI=n
 
 # Skip IPv4 check in ACME container - y/n
 
-SKIP_LETS_ENCRYPT=y
-
-# Create seperate certificates for all domains - y/n
-# this will allow adding more than 100 domains, but some email clients will not be able to connect with alternative hostnames
-# see https://wiki.dovecot.org/SSL/SNIClientSupport
-ENABLE_SSL_SNI=n
-
-# Skip IPv4 check in ACME container - y/n
-
 SKIP_IP_CHECK=n
 
 # Skip HTTP verification in ACME container - y/n
-- 
cgit v1.2.3