diff --git a/Dockerfile b/Dockerfile
index 93ff400..8a95dbb 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,8 @@
FROM python:3-alpine
-RUN apk --no-cache add build-base openldap-dev python2-dev python3-dev
+RUN apk --no-cache add build-base openldap-dev python2-dev python3-dev ca-certificates
RUN pip3 install python-ldap sqlalchemy requests
+RUN update-ca-certificates
COPY templates ./templates
COPY api.py filedb.py syncer.py ./
@@ -10,4 +11,5 @@ VOLUME [ "/db" ]
VOLUME [ "/conf/dovecot" ]
VOLUME [ "/conf/sogo" ]
-ENTRYPOINT [ "python3", "syncer.py" ]
\ No newline at end of file
+COPY ./entrypoint.sh /entrypoint.sh
+CMD ["/bin/ash", "/entrypoint.sh"]
diff --git a/README.md b/README.md
index dc5d8f1..74f8682 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,7 @@ A python script periodically checks and creates new LDAP accounts and deactivate
- ./data/ldap:/db:rw
- ./data/conf/dovecot:/conf/dovecot:rw
- ./data/conf/sogo:/conf/sogo:rw
+ #- /usr/local/share/ca-certificates/:/usr/local/share/ca-certificates/
environment:
- LDAP-MAILCOW_LDAP_URI=ldap(s)://dc.example.local
- LDAP-MAILCOW_LDAP_BASE_DN=OU=Mail Users,DC=example,DC=local
@@ -40,6 +41,8 @@ A python script periodically checks and creates new LDAP accounts and deactivate
- LDAP-MAILCOW_SYNC_INTERVAL=300
- LDAP-MAILCOW_LDAP_FILTER=(&(objectClass=user)(objectCategory=person)(memberOf:1.2.840.113556.1.4.1941:=CN=Group,CN=Users,DC=example DC=local))
- LDAP-MAILCOW_SOGO_LDAP_FILTER=objectClass='user' AND objectCategory='person' AND memberOf:1.2.840.113556.1.4.1941:='CN=Group,CN=Users,DC=example DC=local'
+ - LDAP-MAILCOW_USER_ATTR=userPrincipalName
+ #- LDAP-MAILCOW_REPLACE_DOMAIN=example.com
```
3. Configure environmental variables:
@@ -51,9 +54,11 @@ A python script periodically checks and creates new LDAP accounts and deactivate
* `LDAP-MAILCOW_API_HOST` - mailcow API url. Make sure it's enabled and accessible from within the container for both reads and writes
* `LDAP-MAILCOW_API_KEY` - mailcow API key (read/write)
* `LDAP-MAILCOW_SYNC_INTERVAL` - interval in seconds between LDAP synchronizations
+ * `LDAP-MAILCOW_USER_ATTR` - user attribute to use
* **Optional** LDAP filters (see example above). SOGo uses special syntax, so you either have to **specify both or none**:
* `LDAP-MAILCOW_LDAP_FILTER` - LDAP filter to apply, defaults to `(&(objectClass=user)(objectCategory=person))`
* `LDAP-MAILCOW_SOGO_LDAP_FILTER` - LDAP filter to apply for SOGo ([special syntax](https://sogo.nu/files/docs/SOGoInstallationGuide.html#_authentication_using_ldap)), defaults to `objectClass='user' AND objectCategory='person'`
+ * `LDAP-MAILCOW_REPLACE_DOMAIN` - **Optional** Replace domain (eg you have public domain and internal AD domain)
4. Start additional container: `docker-compose up -d ldap-mailcow`
5. Check logs `docker-compose logs ldap-mailcow`
diff --git a/api.py b/api.py
index 8fd3dca..48bc4cf 100644
--- a/api.py
+++ b/api.py
@@ -18,7 +18,9 @@ def __post_request(url, json_data):
if rsp['type'] != 'success':
sys.exit(f"API {url}: {rsp['type']} - {rsp['msg']}")
-def add_user(email, name, active):
+def add_user(email, name, active, replaceDomain):
+ if replaceDomain is not None:
+ email = email.replace(email.split('@')[1], replaceDomain)
password = ''.join(random.choices(string.ascii_letters + string.digits, k=20))
json_data = {
'local_part':email.split('@')[0],
@@ -31,7 +33,9 @@ def add_user(email, name, active):
__post_request('api/v1/add/mailbox', json_data)
-def edit_user(email, active=None, name=None):
+def edit_user(email, replaceDomain, active=None, name=None):
+ if replaceDomain is not None:
+ email = email.replace(email.split('@')[1], replaceDomain)
attr = {}
if (active is not None):
attr['active'] = 1 if active else 0
@@ -45,12 +49,16 @@ def edit_user(email, active=None, name=None):
__post_request('api/v1/edit/mailbox', json_data)
-def __delete_user(email):
+def __delete_user(email, replaceDomain):
+ if replaceDomain is not None:
+ email = email.replace(email.split('@')[1], replaceDomain)
json_data = [email]
__post_request('api/v1/delete/mailbox', json_data)
-def check_user(email):
+def check_user(email, replaceDomain):
+ if replaceDomain is not None:
+ email = email.replace(email.split('@')[1], replaceDomain)
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)
diff --git a/entrypoint.sh b/entrypoint.sh
new file mode 100644
index 0000000..95d694a
--- /dev/null
+++ b/entrypoint.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+update-ca-certificates
+python3 syncer.py
diff --git a/syncer.py b/syncer.py
index a1fd1e7..1cb7dbe 100644
--- a/syncer.py
+++ b/syncer.py
@@ -40,18 +40,23 @@ def sync():
ldap_results = ldap_connector.search_s(config['LDAP_BASE_DN'], ldap.SCOPE_SUBTREE,
config['LDAP_FILTER'],
- ['userPrincipalName', 'cn', 'userAccountControl'])
+ [config['USER_ATTR'], 'cn', 'userAccountControl'])
ldap_results = map(lambda x: (
- x[1]['userPrincipalName'][0].decode(),
+ x[1][config['USER_ATTR']][0].decode(),
x[1]['cn'][0].decode(),
False if int(x[1]['userAccountControl'][0].decode()) & 0b10 else True), ldap_results)
filedb.session_time = datetime.datetime.now()
+ if 'REPLACE_DOMAIN' in config:
+ replaceDomain = config['REPLACE_DOMAIN']
+ else:
+ replaceDomain = None
+
for (email, ldap_name, ldap_active) in ldap_results:
(db_user_exists, db_user_active) = filedb.check_user(email)
- (api_user_exists, api_user_active, api_name) = api.check_user(email)
+ (api_user_exists, api_user_active, api_name) = api.check_user(email, replaceDomain)
unchanged = True
@@ -62,7 +67,7 @@ def sync():
unchanged = False
if not api_user_exists:
- api.add_user(email, ldap_name, ldap_active)
+ api.add_user(email, ldap_name, ldap_active, replaceDomain)
(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
@@ -73,12 +78,12 @@ def sync():
unchanged = False
if api_user_active != ldap_active:
- api.edit_user(email, active=ldap_active)
+ api.edit_user(email, replaceDomain, 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)
+ api.edit_user(email, replaceDomain, name=ldap_name)
logging.info (f"Changed name of {email} in Mailcow to {ldap_name}")
unchanged = False
@@ -86,10 +91,10 @@ def sync():
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)
+ (api_user_exists, api_user_active, _) = api.check_user(email, replaceDomain)
if (api_user_active and api_user_active):
- api.edit_user(email, active=False)
+ api.edit_user(email, replaceDomain, active=False)
logging.info (f"Deactivated user {email} in Mailcow, not found in LDAP")
filedb.user_set_active_to(email, False)
@@ -148,6 +153,12 @@ def read_config():
config['LDAP_FILTER'] = os.environ['LDAP-MAILCOW_LDAP_FILTER'] if 'LDAP-MAILCOW_LDAP_FILTER' in os.environ else '(&(objectClass=user)(objectCategory=person))'
config['SOGO_LDAP_FILTER'] = os.environ['LDAP-MAILCOW_SOGO_LDAP_FILTER'] if 'LDAP-MAILCOW_SOGO_LDAP_FILTER' in os.environ else "objectClass='user' AND objectCategory='person'"
+ if 'LDAP-MAILCOW_REPLACE_DOMAIN' in os.environ:
+ config['REPLACE_DOMAIN'] = os.environ['LDAP-MAILCOW_REPLACE_DOMAIN']
+
+ if 'LDAP-MAILCOW_USER_ATTR' in os.environ:
+ config['USER_ATTR'] = os.environ['LDAP-MAILCOW_USER_ATTR']
+
return config
def read_dovecot_passdb_conf_template():
diff --git a/templates/sogo/plist_ldap b/templates/sogo/plist_ldap
index cf39ee5..011f003 100644
--- a/templates/sogo/plist_ldap
+++ b/templates/sogo/plist_ldap
@@ -10,7 +10,7 @@
IDFieldName
cn
UIDFieldName
- userPrincipalName
+ $user_attr
baseDN
$ldap_base_dn
@@ -21,7 +21,7 @@
$ldap_bind_dn_password
bindFields
- userPrincipalName
+ $user_attr
bindAsCurrentUser
@@ -42,4 +42,4 @@
scope
SUB
-
\ No newline at end of file
+