From 6e3210163432dd50d752ef8d7530829dd481c699 Mon Sep 17 00:00:00 2001 From: mlan Date: Fri, 8 Jan 2021 18:09:55 +0100 Subject: [PATCH] - [kopano](src/kopano/plugin/movetopublicldap.py) Add sample config file for the kopano-dagent python plugin movetopublicldap. --- CHANGELOG.md | 1 + README.md | 16 ++++----- demo/Makefile | 2 +- demo/docker-compose.yml | 2 +- src/kopano/entry.d/10-kopano-common | 21 ++++++++---- src/kopano/plugin/movetopublicldap.cfg | 32 ++++++++++++++++++ src/kopano/plugin/movetopublicldap.py | 46 +++++++++++++++----------- src/notused/plugin/ldaptocfg.py | 12 +++---- 8 files changed, 90 insertions(+), 42 deletions(-) create mode 100644 src/kopano/plugin/movetopublicldap.cfg diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f92cf6..027fb21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 1.2.7 - [kopano](src/kopano/plugin/movetopublicldap.py) Add LDAP support to the [Move to public](https://documentation.kopano.io/kopanocore_administrator_manual/special_kc_configurations.html#move-to-public ) kopano-dagent python plugin. +- [kopano](src/kopano/plugin/movetopublicldap.py) Add sample config file for the kopano-dagent python plugin movetopublicldap. # 1.2.6 diff --git a/README.md b/README.md index 9a86450..31cca67 100644 --- a/README.md +++ b/README.md @@ -444,13 +444,13 @@ The [Mobile Device Management](https://documentation.kopano.io/webapp_mdm_manual There are two type of stores (folders containing communication elements); private and public stores. There can only be one public store. It is the Kopano dagent that places incoming messages into mail boxes, that is the private and public stores. -Public folders are managed by the system admin and not by individual users. Users have them mapped automatically. The public folders can be synced via [Exchange ActiveSync (EAS)](https://wiki.z-hub.io/display/ZP/Sharing+folders+and+Read-only) . +Public folders are configured by the system admin. Users have them mapped automatically. The shared and public folders can be synced with mobile devices via [Exchange ActiveSync (EAS)](https://wiki.z-hub.io/display/ZP/Sharing+folders+and+Read-only). Each user can manage which shared or public folders to sync with her mobile devices by using the [Mobile Device Management](#mobile-device-management) WebApp plugin. With the current Kopano implementation, delivering to the public store is configured separately from normal user management. There is the [move to public](https://documentation.kopano.io/kopanocore_administrator_manual/special_kc_configurations.html#move-to-public) plugin which moves incoming messages to a folder in the public store. It has a static configuration and does not support LDAP lookup. ### Move to public with LDAP lookup -The `mlan/kopano` image include a extended version of the move to public plugin which use LDAP lookup, instead of a static file based lookup. When the plugin [move to public with LDAP](src/kopano/plugin/movetopublicldap.py) is enabled, `DAGENT_PLUGINS=movetopublicldap.py`, the kopano dagent will do two LDAP queries. The first is to search for an entry/user with matching email address. The second, introduced with this plugin, is to get the public folder from this entry. If found the message will be delivered to the public folder otherwise it will be delivered to the mailbox of the user. +The `mlan/kopano` image include a extended version of the move to public plugin which use LDAP lookup, instead of a static file based lookup. When the plugin [move to public with LDAP](src/kopano/plugin/movetopublicldap.py) is enabled, `DAGENT_PLUGINS=movetopublicldap`, the kopano dagent will do two LDAP queries. The first is to search for an entry/user with matching email address. The second, introduced with this plugin, is to get the public folder from this entry. If found the message will be delivered to the public folder otherwise it will be delivered to the mailbox of the user. Lets demonstrate how delivery to a public folder is configured. With this LDAP entry: @@ -466,11 +466,11 @@ mail: public@example.com kopanoAccount: 1 kopanoHidden: 1 kopanoSharedStoreOnly: 1 -kopanoResourceType: publicStore:Public Stores/public +kopanoResourceType: publicFolder:Public Stores/public ``` messages to `public@example.com` will be delivered to the public store in `Public Stores/public`. -The central [attribute](https://documentation.kopano.io/kopanocore_administrator_manual/appendix_b.html#appendix-b-ldap-attribute-description) is `kopanoResourceType: publicStore:Public Stores/public`. It contains a token and a folder name. The token match is case sensitive and there must be a colon `:` separating +The central [attribute](https://documentation.kopano.io/kopanocore_administrator_manual/appendix_b.html#appendix-b-ldap-attribute-description) is `kopanoResourceType: publicFolder:Public Stores/public`. It contains a token and a folder name. The token match is case sensitive and there must be a colon `:` separating the token and the public folder name. The folder name can contain space and sub folders, which are distinguished using a forward slash `/`. @@ -479,17 +479,17 @@ The LDAP attribute holding the token and the token itself have the following default values, which can be modified in `/etc/kopano/movetopublicldap.cfg` if desired. ```yaml -ldap_public_store_attribute = kopanoResourceType -ldap_public_store_attribute_token = publicStore +ldap_public_folder_attribute = kopanoResourceType +ldap_public_folder_attribute_token = publicFolder ``` -As with other parameters, environment variables can be used to define them: `LDAP_PUBLIC_STORE_ATTRIBUTE=kopanoResourceType` and `LDAP_PUBLIC_STORE_ATTRIBUTE_TOKEN=publicStore`. +As with other parameters, environment variables can be used to define them: `LDAP_PUBLIC_FOLDER_ATTRIBUTE=kopanoResourceType` and `LDAP_PUBLIC_FOLDER_ATTRIBUTE_TOKEN=publicFolder`. ## Shared folders Users can share folders when sufficient permission have been granted. When logged into WebApp with an administrative account (`kopanoAdmin: 1`) you can modify the permissions on users shares and folders. Users can then, when logged into WebApp, open the inbox of other users by selecting `Open Shared Mails`. -The [impersonation](https://wiki.z-hub.io/display/ZP/Impersonation) mechanism allow such shared folders to be synced over [Exchange ActiveSync (EAS)](https://wiki.z-hub.io/display/ZP/Sharing+folders+and+Read-only) too. +The [impersonation](https://wiki.z-hub.io/display/ZP/Impersonation) mechanism allow such shared folders to be synced over [Exchange ActiveSync (EAS)](https://wiki.z-hub.io/display/ZP/Sharing+folders+and+Read-only) too. Each user can manage which shared or public folders to sync with her mobile devices by using the [Mobile Device Management](#mobile-device-management) WebApp plugin. ## Crontab diff --git a/demo/Makefile b/demo/Makefile index 188dc95..b0999e8 100644 --- a/demo/Makefile +++ b/demo/Makefile @@ -179,7 +179,7 @@ mail: $(LDAP_TEST_PUB)@$(MAIL_DOMAIN) kopanoAccount: 1 kopanoHidden: 1 kopanoSharedStoreOnly: 1 -kopanoResourceType: publicStore:Public Stores/public +kopanoResourceType: publicFolder:Public Stores/public endef export LDIF_ADD_STO diff --git a/demo/docker-compose.yml b/demo/docker-compose.yml index 82c088b..8b74096 100644 --- a/demo/docker-compose.yml +++ b/demo/docker-compose.yml @@ -27,7 +27,7 @@ services: - LDAP_GROUP_TYPE_ATTRIBUTE_VALUE=${LDAP_GROUPOBJ-posixGroup} - LDAP_GROUPMEMBERS_ATTRIBUTE_TYPE=dn - LDAP_PROPMAP= - - DAGENT_PLUGINS=movetopublicldap.py + - DAGENT_PLUGINS=movetopublicldap - MYSQL_DATABASE=${MYSQL_DATABASE-kopano} - MYSQL_USER=${MYSQL_USER-kopano} - MYSQL_PASSWORD=${MYSQL_PASSWORD-secret} diff --git a/src/kopano/entry.d/10-kopano-common b/src/kopano/entry.d/10-kopano-common index 100d9fe..2f03f6d 100755 --- a/src/kopano/entry.d/10-kopano-common +++ b/src/kopano/entry.d/10-kopano-common @@ -18,7 +18,7 @@ DOCKER_MAN5_DIR=${DOCKER_MAN5_DIR-/usr/share/man/man5/} DOCKER_APPL_SSL_CERT=${DOCKER_APPL_SSL_CERT-$DOCKER_APPL_SSL_DIR/cert.pem} DOCKER_APPL_SSL_KEY=${DOCKER_APPL_SSL_KEY-$DOCKER_APPL_SSL_DIR/priv_key.pem} DOCKER_LDAP_PMAP_FILE=${DOCKER_LDAP_PMAP_FILE-/usr/share/kopano/ldap.propmap.cfg} -DOCKER_LDAP_SERVICES=${DOCKER_LDAP_SERVICES-archiver dagent gateway ical ldap movetopublicldap search server spamd spooler} +DOCKER_LDAP_SERVICES=${DOCKER_LDAP_SERVICES-archiver dagent gateway ical ldap search server spamd spooler} sqlstate_cfg_file=$DOCKER_CONF_DIR2/backend/sqlstatemachine/config.php zpush_cfg_file=$DOCKER_CONF_DIR2/config.php webapp_cfg_file=$DOCKER_CONF_DIR1/webapp/config.php @@ -30,7 +30,7 @@ webapp_smime_cfg_file=$DOCKER_CONF_DIR1/webapp/config-smime.php # CLI commands # list_parms() { - local services="$DOCKER_LDAP_SERVICES" + local services="$DOCKER_LDAP_SERVICES $DAGENT_PLUGINS" [ $# -ge 0 ] && services="$@" for service in $services; do [ $# -ne 1 ] && echo "# $service" @@ -50,7 +50,7 @@ list_parms() { # kopano_apply_envvars_core() { kopano_enable_envvars_plugin - for service in $DOCKER_LDAP_SERVICES; do + for service in $DOCKER_LDAP_SERVICES $DAGENT_PLUGINS; do kopano_apply_envvars_cfg $service done } @@ -130,7 +130,7 @@ kopano_get_envvars_ext() { local vars case $service in movetopublicldap) - vars="ldap_public_store_attribute ldap_public_store_attribute_token" + vars="ldap_public_folder_attribute ldap_public_folder_attribute_token" ;; esac echo $vars | tr "[:lower:]" "[:upper:]" | tr " " "\n" | sort -u @@ -143,9 +143,16 @@ kopano_enable_envvars_plugin() { for service in dagent spooler; do local envvar=${service^^}_PLUGINS for plugin in ${!envvar}; do - dc_log 5 "Enabling ${service} python plugin ${plugin}" - ln -sf /usr/share/kopano-${service}/python/plugins/${plugin} /var/lib/kopano/${service}/plugins - export ${service^^}_PLUGIN_ENABLED=yes + local pyfile=$(find /usr/share/kopano* -name ${plugin}.py) + if [ -z "$pyfile" ]; then + dc_log 5 "Unable to locate ${service} python plugin ${plugin}" + else + dc_log 5 "Enabling ${service} python plugin ${plugin}" + mkdir -p $DOCKER_CONF_DIR1/${service} + ln -sf ${pyfile} $DOCKER_CONF_DIR1/${service} + export ${service^^}_PLUGIN_PATH=$DOCKER_CONF_DIR1/${service} + export ${service^^}_PLUGIN_ENABLED=yes + fi done done } diff --git a/src/kopano/plugin/movetopublicldap.cfg b/src/kopano/plugin/movetopublicldap.cfg new file mode 100644 index 0000000..1e16b3a --- /dev/null +++ b/src/kopano/plugin/movetopublicldap.cfg @@ -0,0 +1,32 @@ +# movetopublicldap.cfg +# +# Move mail to public ldap configuration +# +# This configuration file is used by the dagent plugin 'movetopublicldap.py' +# The default location is '/etc/kopano/movetopublicldap.cfg' +# + +# +# The plugin reads two coniguration files. +# First it looks for parameters in '/etc/kopano/ldap.cfg' allowing +# common conifuration parameters to be kep tin on place. +# Second it reads '/etc/kopano/movetopublicldap.cfg' +# + +# +# Parameters that can be set in both files: +# +# ldap_uri = +# ldap_starttls = no +# ldap_search_base = +# ldap_bind_user = +# ldap_bind_passwd = +# ldap_user_unique_attribute = uid +# + +# +# Parameters that can only be set in ovetopublicldap.cfg: +# +# ldap_public_folder_attribute = kopanoResourceType +# ldap_public_folder_attribute_token = publicFolder +# diff --git a/src/kopano/plugin/movetopublicldap.py b/src/kopano/plugin/movetopublicldap.py index f2859c5..a93c2f8 100644 --- a/src/kopano/plugin/movetopublicldap.py +++ b/src/kopano/plugin/movetopublicldap.py @@ -8,13 +8,13 @@ store. If folders are missing they will be created. A LDAP entry including: kopanoAccount: 1 -kopanoResourceType: publicStore: +kopanoResourceType: publicFolder: will have its email delivered to the public store in . The token match is case sensitive and there must be a colon ':' separating the token and the public folder name. The folder name can contain space and sub folders, which are distinguished using forward slash '/'. -So if we have 'kopanoResourceType: publicStore:Public Stores/public' emails will +So if we have 'kopanoResourceType: publicFolder:Public Stores/public' emails will be delivered to 'Public Folders/Public Stores/public'. The parameters in /etc/kopano/ldap.cfg will be used for the LDAP query. @@ -22,8 +22,8 @@ The LDAP attribute holding the token and the token itself have the following default values, which can be modified in /etc/kopano/movetopublicldap.cfg if desired. -ldap_public_store_attribute = kopanoResourceType -ldap_public_store_attribute_token = publicStore +ldap_public_folder_attribute = kopanoResourceType +ldap_public_folder_attribute_token = publicFolder """ from sys import hexversion @@ -37,6 +37,7 @@ from plugintemplates import IMapiDAgentPlugin, MP_CONTINUE, MP_STOP_SUCCESS from zconfig import ZConfigParser import configparser import ldap +import os.path class KConfigParser(ZConfigParser): """ Extends zconfig.ZConfigParser to also allow !directive in cfg files """ @@ -55,12 +56,13 @@ class MoveToPublic(IMapiDAgentPlugin): DEFAULTCONFIG = { 'ldap_uri': None, + 'ldap_starttls': "no", 'ldap_search_base': None, 'ldap_bind_user': None, 'ldap_bind_passwd': None, 'ldap_user_unique_attribute': "uid", - 'ldap_public_store_attribute': "kopanoResourceType", - 'ldap_public_store_attribute_token': "publicStore" + 'ldap_public_folder_attribute': "kopanoResourceType", + 'ldap_public_folder_attribute_token': "publicFolder" } def __init__(self, logger): @@ -72,34 +74,40 @@ class MoveToPublic(IMapiDAgentPlugin): options = [opt.split('_', 1)[1] for opt in defaultconfig.keys()] config = None for configfile in configfiles: - if not config: - config = KConfigParser(configfile, defaultconfig) + if os.path.isfile(configfile): + self.logger.logDebug("*--- Reading file {}".format(configfile)) + if not config: + config = KConfigParser(configfile, defaultconfig) + else: + config = KConfigParser(configfile, config.options()) else: - config = KConfigParser(configfile, config.options()) + self.logger.logDebug("*--- Can't find file {}".format(configfile)) self.config = config.getdict('ldap',options) self.logger.logDebug("*--- Config list {}".format(self.config)) return self.config def searchfilter(self, recipient): - """ (&(uid=recipient)(kopanoResourceType=publicStore:*)) """ + """ (&(uid=recipient)(kopanoResourceType=publicFolder:*)) """ return ("(&({}={})({}={}:*))" .format(self.config['user_unique_attribute'], recipient, - self.config['public_store_attribute'], - self.config['public_store_attribute_token'])) + self.config['public_folder_attribute'], + self.config['public_folder_attribute_token'])) def searchquery(self, recipient): """ Query a LDAP/AD driectory server to lookup recipient using - search_base and return public_store_attribute + search_base and return public_folder_attribute """ - if (self.config['uri'] is None): + if not self.config or not self.config['uri']: self.logger.logError(("!--- ldap_uri is not defined." - " Please check {}" .format(self.CONFIGFILES[0]))) + " Please check {}" .format(self.CONFIGFILES))) return None else: l = ldap.initialize(self.config['uri']) try: l.protocol_version = ldap.VERSION3 + if self.config['starttls'] == 'yes': + l.start_tls_s() l.simple_bind_s(self.config['bind_user'] or u'', \ self.config['bind_passwd'] or u'') except ldap.SERVER_DOWN as e: @@ -108,7 +116,7 @@ class MoveToPublic(IMapiDAgentPlugin): return None except ldap.INVALID_CREDENTIALS as e: self.logger.logError(("!--- Invalid LDAP credentials {}" - " Please check {}" .format(e, self.CONFIGFILES[0]))) + " Please check {}" .format(e, self.CONFIGFILES))) l.unbind_s() return None except ldap.LDAPError as e: @@ -118,19 +126,19 @@ class MoveToPublic(IMapiDAgentPlugin): try: result = l.search_s(self.config['search_base'], \ ldap.SCOPE_SUBTREE, self.searchfilter(recipient), \ - [self.config['public_store_attribute']]) + [self.config['public_folder_attribute']]) except ldap.LDAPError as e: self.logger.logError("!--- LDAPError {}".format(e)) l.unbind_s() return result def publicfolder(self, recipient): - """ Check for ldap_public_store_attribute_token and return folder """ + """ Check for ldap_public_folder_attribute_token and return folder """ destination_folder = [] result = self.searchquery(recipient) if result: tokenandfolder = (result[0][1] - .get(self.config['public_store_attribute'])[0].decode('utf-8')) + .get(self.config['public_folder_attribute'])[0].decode('utf-8')) if tokenandfolder: destination_folder = tokenandfolder.split(':')[1] if destination_folder: diff --git a/src/notused/plugin/ldaptocfg.py b/src/notused/plugin/ldaptocfg.py index db4e49c..e87aba3 100644 --- a/src/notused/plugin/ldaptocfg.py +++ b/src/notused/plugin/ldaptocfg.py @@ -25,9 +25,9 @@ class ldapstores(): 'ldap_bind_passwd': None, 'ldap_user_unique_attribute': "uid", 'ldap_user_search_filter': "(kopanoAccount=1)", -# 'ldap_user_search_filter': "(&(kopanoAccount=1)(kopanoResourceType=publicStore:*))", - 'ldap_public_store_attribute': "kopanoResourceType", - 'ldap_public_store_attribute_token': "publicStore" +# 'ldap_user_search_filter': "(&(kopanoAccount=1)(kopanoResourceType=publicFolder:*))", + 'ldap_public_folder_attribute': "kopanoResourceType", + 'ldap_public_folder_attribute_token': "publicFolder" } def __init__(self, configfile = '/etc/kopano/ldap.cfg'): @@ -58,7 +58,7 @@ class ldapstores(): ldap_result_id = l.search(self.config['search_base'], \ ldap.SCOPE_SUBTREE, self.config['user_search_filter'], \ [self.config['user_unique_attribute'], \ - self.config['public_store_attribute']]) + self.config['public_folder_attribute']]) results = [] while 1: result_type, result_data = l.result(ldap_result_id, 0) @@ -77,11 +77,11 @@ class ldapstores(): public = [] for store in stores: recipient = store[1].get(self.config['user_unique_attribute']) - tokenandfolder = store[1].get(self.config['ldap_public_store_attribute']) + tokenandfolder = store[1].get(self.config['ldap_public_folder_attribute']) if tokenandfolder: token = tokenandfolder.split(':')[0] destination_folder = tokenandfolder.split(':')[1] - if (token == self.config['ldap_public_store_attribute_token']); + if (token == self.config['ldap_public_folder_attribute_token']); public[recipient] = destination_folder return public