This repository has been archived on 2021-10-24. You can view files and clone it, but cannot push or open issues or pull requests.
email/mailcow/data/Dockerfiles/ldap/syncer.py
Georg 98fa66b5ad
Init MC update + Dovecot/SOGo LDAP configuration
Signed-off-by: Georg <georg@lysergic.dev>
2021-09-13 09:40:35 +02:00

187 lines
6.8 KiB
Python

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()