- [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.
parent
f82c64cd47
commit
b9c2529745
|
@ -1,3 +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.
|
||||
|
||||
# 1.2.6
|
||||
|
||||
- [docker](src/docker/bin/docker-config.sh) Allow the crontab support to work also when the file `/etc/kopano/docker-crontab` is missing.
|
||||
|
|
|
@ -17,6 +17,7 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
|||
DOCKER_CRONTAB_DIR=/etc/cron.d \
|
||||
DOCKER_CONF_DIR1=/etc/kopano \
|
||||
DOCKER_SMPL_DIR1=/usr/share/doc/kopano/example-config \
|
||||
DOCKER_PLUG_DIR=/usr/share/kopano-dagent/python/plugins \
|
||||
DOCKER_CONF_DIR2=/usr/share/z-push \
|
||||
DOCKER_APPL_LIB=/var/lib/kopano \
|
||||
DOCKER_APPL_SSL_DIR=/etc/kopano/ssl \
|
||||
|
@ -35,6 +36,7 @@ COPY src/*/bin $DOCKER_BIN_DIR/
|
|||
COPY src/*/entry.d $DOCKER_ENTRY_DIR/
|
||||
COPY src/*/exit.d $DOCKER_EXIT_DIR/
|
||||
COPY src/*/config $DOCKER_CONF_DIR1/
|
||||
COPY src/*/plugin $DOCKER_PLUG_DIR/
|
||||
|
||||
#
|
||||
# Install helpers. Set bash as default shell. Setup syslogs service.
|
||||
|
@ -115,6 +117,7 @@ RUN mkdir -p $DOCKER_BUILD_DEB_DIR \
|
|||
&& for i in $(seq ${DOCKER_BUILD_PASSES}); do echo "\033[1;36mKOPANO CORE INSTALL PASS: $i\033[0m" \
|
||||
&& dpkg --install --force-depends --skip-same-version --recursive $DOCKER_BUILD_DEB_DIR \
|
||||
&& apt-get install --yes --no-install-recommends --fix-broken; done \
|
||||
&& apt-get install --yes --no-install-recommends python3-ldap \
|
||||
&& mkdir -p /var/lib/kopano/attachments && chown $DOCKER_APPL_RUNAS: /var/lib/kopano/attachments \
|
||||
&& mkdir -p $DOCKER_APPL_SSL_DIR \
|
||||
&& mkdir -p $DOCKER_ACME_SSL_DIR \
|
||||
|
|
57
README.md
57
README.md
|
@ -3,8 +3,9 @@
|
|||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
This (non official) repository provides dockerized web mail service as well as Exchange ActiveSync (EAS), IMAP, POP3 and ICAL service (and their secure variants IMAPS, POP3S and ICALS). It is based on [Kopano](https://kopano.com) core components, as well as the Kopano WebApp and [Z-Push](http://z-push.org/). The image uses [nightly built packages](https://download.kopano.io/community/) which are provided by the Kopano community.
|
||||
|
||||
|
@ -21,6 +22,7 @@ Hopefully this repository can be retired once the Kopano community make official
|
|||
- Configuration using environment variables
|
||||
- Log directed to docker daemon with configurable level
|
||||
- Built in utility script [run](src/docker/bin/run) helping configuring Kopano components, WebApp and Z-Push
|
||||
- [Move to public with LDAP lookup](#move-to-public-with-ldap-lookup)
|
||||
- [Crontab](https://en.wikipedia.org/wiki/Cron) support.
|
||||
- Health check
|
||||
- Hook for theming
|
||||
|
@ -438,13 +440,64 @@ make app-create_smime
|
|||
|
||||
The [Mobile Device Management](https://documentation.kopano.io/webapp_mdm_manual/) WebApp plugin comes pre-installed. With it you can resync, remove, refresh and even wipe your devices, connected via [Exchange ActiveSync (EAS)](https://en.wikipedia.org/wiki/Exchange_ActiveSync).
|
||||
|
||||
## Public folders
|
||||
|
||||
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) .
|
||||
|
||||
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.
|
||||
|
||||
Lets demonstrate how delivery to a public folder is configured. With this LDAP entry:
|
||||
|
||||
```yaml
|
||||
dn: uid=public,ou=users,dc=example,dc=com
|
||||
cn: public
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: kopano-user
|
||||
sn: public
|
||||
uid: public
|
||||
mail: public@example.com
|
||||
kopanoAccount: 1
|
||||
kopanoHidden: 1
|
||||
kopanoSharedStoreOnly: 1
|
||||
kopanoResourceType: publicStore: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 token and the public folder name. The folder name can contain space and
|
||||
sub folders, which are distinguished using a forward slash `/`.
|
||||
|
||||
The parameters in `/etc/kopano/ldap.cfg` will be used to arrange the LDAP queries.
|
||||
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
|
||||
```
|
||||
|
||||
As with other parameters, environment variables can be used to define them: `LDAP_PUBLIC_STORE_ATTRIBUTE=kopanoResourceType` and `LDAP_PUBLIC_STORE_ATTRIBUTE_TOKEN=publicStore`.
|
||||
|
||||
## 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.
|
||||
|
||||
## Crontab
|
||||
|
||||
The `mlan/kopano` has a [cron](https://en.wikipedia.org/wiki/Cron) service activated. You can use environment variables to set up [crontab](https://man7.org/linux/man-pages/man5/crontab.5.html) entries. Any environment variable name staring with `CRONTAB_ENTRY` will be use to add entries to cron.
|
||||
|
||||
One trivial example is `CRONTAB_ENTRY_TEST=* * * * * root logger -t cron -p user.notice "SHELL=$$SHELL, PATH=$$PATH"`.
|
||||
|
||||
During the initial configuration procedure any `CRONTAB_ENTRY` will add crontab entries to the file `/etc/kopano/docker-crontab`, all the while previously present entries are deleted. This file defines the `PATH` variable so that you don't need to give full path names to commands in the crontab entry. This is, you need to provide the full path names to commands if this `PATH` definition is missing in the `/etc/kopano/docker-crontab` file.
|
||||
During the initial configuration procedure any `CRONTAB_ENTRY` will add crontab entries to the file `/etc/kopano/docker-crontab`, all the while previously present entries are deleted. This file defines the `PATH` variable so that you don't need to give full path names to commands in the crontab entry. This is, you need to provide the full path names to commands if this `PATH` definition is missing in the `/etc/kopano/docker-crontab` file.
|
||||
|
||||
## Mail transfer agent interaction
|
||||
|
||||
|
|
|
@ -15,8 +15,12 @@ LDAP_USERFLT=
|
|||
LDAP_GROUPOU=groups
|
||||
LDAP_GROUPOBJ=kopano-group
|
||||
LDAP_TEST_USER=demo
|
||||
LDAP_TEST_PASSWD=demo
|
||||
LDAP_TEST_USERPW=demo
|
||||
LDAP_TEST_ADMIN=admin
|
||||
LDAP_TEST_ADMINPW=admin
|
||||
LDAP_TEST_GROUP=team
|
||||
LDAP_TEST_PUB=public
|
||||
LDAP_TEST_SHR=shared
|
||||
MYSQL_ROOT_PASSWORD=secret
|
||||
MYSQL_DATABASE=kopano
|
||||
MYSQL_USER=kopano
|
||||
|
|
108
demo/Makefile
108
demo/Makefile
|
@ -76,7 +76,7 @@ wait_%:
|
|||
web:
|
||||
firefox localhost:8008 &
|
||||
|
||||
auth-init: wait_11 auth-mod_conf auth-add_schema auth-add_data
|
||||
auth-init: wait_11 auth-mod_conf auth-add_schema auth-add_data auth-add_sto
|
||||
|
||||
define LDIF_MOD_CONF
|
||||
dn: olcDatabase={-1}frontend,cn=config
|
||||
|
@ -115,9 +115,27 @@ cn: $(LDAP_TEST_GROUP)
|
|||
objectClass: top
|
||||
objectClass: groupOfNames
|
||||
objectClass: kopano-group
|
||||
member: uid=$(LDAP_TEST_ADMIN),ou=$(LDAP_USEROU),$(LDAP_BASE)
|
||||
member: uid=$(LDAP_TEST_USER),ou=$(LDAP_USEROU),$(LDAP_BASE)
|
||||
mail: $(LDAP_TEST_GROUP)@$(MAIL_DOMAIN)
|
||||
|
||||
dn: uid=$(LDAP_TEST_ADMIN),ou=$(LDAP_USEROU),$(LDAP_BASE)
|
||||
changetype: add
|
||||
cn: $(LDAP_TEST_ADMIN)
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: kopano-user
|
||||
sn: $(LDAP_TEST_ADMIN)
|
||||
uid: $(LDAP_TEST_ADMIN)
|
||||
mail: $(LDAP_TEST_ADMIN)@$(MAIL_DOMAIN)
|
||||
userPassword: $(LDAP_TEST_ADMINPW)
|
||||
telephoneNumber: 0123 987654321
|
||||
title: System Admin
|
||||
kopanoAccount: 1
|
||||
kopanoAdmin: 1
|
||||
kopanoEnabledFeatures: imap
|
||||
kopanoEnabledFeatures: pop3
|
||||
|
||||
dn: uid=$(LDAP_TEST_USER),ou=$(LDAP_USEROU),$(LDAP_BASE)
|
||||
changetype: add
|
||||
cn: $(LDAP_TEST_USER)
|
||||
|
@ -127,22 +145,54 @@ objectClass: kopano-user
|
|||
sn: $(LDAP_TEST_USER)
|
||||
uid: $(LDAP_TEST_USER)
|
||||
mail: $(LDAP_TEST_USER)@$(MAIL_DOMAIN)
|
||||
userPassword: $(LDAP_TEST_PASSWD)
|
||||
userPassword: $(LDAP_TEST_USERPW)
|
||||
telephoneNumber: 0123 123456789
|
||||
title: MCP
|
||||
title: First User
|
||||
kopanoAccount: 1
|
||||
kopanoAdmin: 1
|
||||
kopanoEnabledFeatures: imap
|
||||
kopanoEnabledFeatures: pop3
|
||||
endef
|
||||
export LDIF_ADD_DATA
|
||||
|
||||
define LDIF_ADD_STO
|
||||
dn: uid=$(LDAP_TEST_SHR),ou=$(LDAP_USEROU),$(LDAP_BASE)
|
||||
changetype: add
|
||||
cn: $(LDAP_TEST_SHR)
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: kopano-user
|
||||
sn: $(LDAP_TEST_SHR)
|
||||
uid: $(LDAP_TEST_SHR)
|
||||
mail: $(LDAP_TEST_SHR)@$(MAIL_DOMAIN)
|
||||
kopanoAccount: 1
|
||||
kopanoSharedStoreOnly: 1
|
||||
|
||||
dn: uid=$(LDAP_TEST_PUB),ou=$(LDAP_USEROU),$(LDAP_BASE)
|
||||
changetype: add
|
||||
cn: $(LDAP_TEST_PUB)
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: kopano-user
|
||||
sn: $(LDAP_TEST_PUB)
|
||||
uid: $(LDAP_TEST_PUB)
|
||||
mail: $(LDAP_TEST_PUB)@$(MAIL_DOMAIN)
|
||||
kopanoAccount: 1
|
||||
kopanoHidden: 1
|
||||
kopanoSharedStoreOnly: 1
|
||||
kopanoResourceType: publicStore:Public Stores/public
|
||||
|
||||
endef
|
||||
export LDIF_ADD_STO
|
||||
|
||||
auth-mod_conf:
|
||||
echo "$$LDIF_MOD_CONF" | docker-compose exec -T auth ldap modify
|
||||
|
||||
auth-add_data:
|
||||
echo "$$LDIF_ADD_DATA" | docker-compose exec -T auth ldap modify
|
||||
|
||||
auth-add_sto:
|
||||
echo "$$LDIF_ADD_STO" | docker-compose exec -T auth ldap modify
|
||||
|
||||
auth-add_schema:
|
||||
docker-compose exec app zcat /usr/share/doc/kopano/kopano.ldif.gz \
|
||||
| docker-compose exec -T auth ldapadd -H ldapi://%2Fvar%2Frun%2Fopenldap%2Fldapi/ -Y EXTERNAL
|
||||
|
@ -162,14 +212,14 @@ auth-show_cat1:
|
|||
docker-compose exec auth slapcat -n1
|
||||
|
||||
auth-gui-up:
|
||||
docker run -d --rm --name auth-gui --network demo_backend \
|
||||
docker run -d --name auth-gui --network demo_backend \
|
||||
-p 127.0.0.1:8001:80 -e PHPLDAPADMIN_LDAP_HOSTS=auth \
|
||||
-e PHPLDAPADMIN_HTTPS=false osixia/phpldapadmin || true
|
||||
sleep 2
|
||||
firefox localhost:8001 &
|
||||
|
||||
auth-gui-down:
|
||||
docker stop auth-gui || true
|
||||
docker rm -f auth-gui || true
|
||||
|
||||
mta-init:
|
||||
|
||||
|
@ -184,7 +234,7 @@ mta-test_smtp:
|
|||
mta-test_smtps:
|
||||
printf "From: A tester <test@example.biz>\nTo: <$(LDAP_TEST_USER)@$(MAIL_DOMAIN)>\nDate: $$(date)\nSubject: A SMTPS test message\n\nGreat news! You can receive secure email.\n" \
|
||||
| curl smtps://localhost -T - --mail-from test@example.biz -k \
|
||||
-u $(LDAP_TEST_USER):$(LDAP_TEST_PASSWD) \
|
||||
-u $(LDAP_TEST_USER):$(LDAP_TEST_USERPW) \
|
||||
--mail-rcpt $(LDAP_TEST_USER)@$(MAIL_DOMAIN) $(curl_dbg)
|
||||
|
||||
mta-test_smtp2:
|
||||
|
@ -192,6 +242,16 @@ mta-test_smtp2:
|
|||
| curl smtp://localhost -T - --mail-from test@example.biz \
|
||||
--mail-rcpt $(LDAP_TEST_USER)@$(MAIL_DOMAIN) $(curl_dbg)
|
||||
|
||||
mta-test_shared: all-test_quiet
|
||||
printf "From: A tester <test@example.biz>\nTo: <$(LDAP_TEST_SHR)@$(MAIL_DOMAIN)>\nDate: $$(date)\nSubject: A SMTP test message\n\nGreat news! A shared store can receive email.\n" \
|
||||
| curl smtp://localhost -T - --mail-from test@example.biz \
|
||||
--mail-rcpt $(LDAP_TEST_SHR)@$(MAIL_DOMAIN) $(curl_dbg)
|
||||
|
||||
mta-test_public: all-test_quiet
|
||||
printf "From: A tester <test@example.biz>\nTo: <$(LDAP_TEST_PUB)@$(MAIL_DOMAIN)>\nDate: $$(date)\nSubject: A SMTP test message\n\nGreat news! A public store can receive email.\n" \
|
||||
| curl smtp://localhost -T - --mail-from test@example.biz \
|
||||
--mail-rcpt $(LDAP_TEST_PUB)@$(MAIL_DOMAIN) $(curl_dbg)
|
||||
|
||||
mta-razor:
|
||||
docker-compose exec mta run amavis_register_razor
|
||||
|
||||
|
@ -234,15 +294,15 @@ mta-hostaddr:
|
|||
$(eval myhost := $(call _ip,$(COMPOSE_PROJECT_NAME)_mta_1))
|
||||
|
||||
mta-test_auth:
|
||||
docker-compose exec mta doveadm auth test $(LDAP_TEST_USER) $(LDAP_TEST_PASSWD)
|
||||
docker-compose exec mta doveadm auth test $(LDAP_TEST_USER) $(LDAP_TEST_USERPW)
|
||||
|
||||
mta-test_imap: mta-hostaddr
|
||||
curl imap://$(myhost) -X CAPABILITY
|
||||
curl imap://$(myhost) -u $(LDAP_TEST_USER):$(LDAP_TEST_PASSWD)
|
||||
curl imap://$(myhost) -u $(LDAP_TEST_USER):$(LDAP_TEST_USERPW)
|
||||
|
||||
mta-test_rimap:
|
||||
docker-compose exec mta curl imap://app -X CAPABILITY
|
||||
docker-compose exec mta curl imap://app -u $(LDAP_TEST_USER):$(LDAP_TEST_PASSWD)
|
||||
docker-compose exec mta curl imap://app -u $(LDAP_TEST_USER):$(LDAP_TEST_USERPW)
|
||||
|
||||
mta-test_ldap: mta-debugtools
|
||||
docker-compose exec mta ldapsearch -H ldap://auth:389 -xLLL -s base namingContexts
|
||||
|
@ -252,7 +312,17 @@ db-init:
|
|||
db-test:
|
||||
docker-compose exec db mysqlshow -u $(MYSQL_USER) $(MYSQL_DATABASE) -p$(MYSQL_PASSWORD)
|
||||
|
||||
app-init: #wait_21 app-public_store
|
||||
db-gui-up:
|
||||
docker run -d --name db-gui --network demo_backend \
|
||||
-p 127.0.0.1:8002:80 -e PMA_HOST=db \
|
||||
phpmyadmin/phpmyadmin || true
|
||||
sleep 2
|
||||
firefox localhost:8002 &
|
||||
|
||||
db-gui-down:
|
||||
docker rm -f db-gui || true
|
||||
|
||||
app-init: #wait_92 app-public_store
|
||||
|
||||
app-debugtools:
|
||||
docker-compose exec app apt-get update
|
||||
|
@ -279,22 +349,22 @@ app-test_lmtp: app-hostaddr
|
|||
app-test_all: all-test_muted app-test_imap app-test_pop3 app-test_ical app-test_imaps app-test_pop3s app-test_icals
|
||||
|
||||
app-test_imap: app-hostaddr
|
||||
curl imap://$(myhost) -u $(LDAP_TEST_USER):$(LDAP_TEST_PASSWD) $(curl_dbg)
|
||||
curl imap://$(myhost) -u $(LDAP_TEST_USER):$(LDAP_TEST_USERPW) $(curl_dbg)
|
||||
|
||||
app-test_imaps: app-hostaddr
|
||||
curl imaps://$(myhost) -k -u $(LDAP_TEST_USER):$(LDAP_TEST_PASSWD) $(curl_dbg)
|
||||
curl imaps://$(myhost) -k -u $(LDAP_TEST_USER):$(LDAP_TEST_USERPW) $(curl_dbg)
|
||||
|
||||
app-test_pop3: app-hostaddr
|
||||
curl pop3://$(myhost) -u $(LDAP_TEST_USER):$(LDAP_TEST_PASSWD) $(curl_dbg)
|
||||
curl pop3://$(myhost) -u $(LDAP_TEST_USER):$(LDAP_TEST_USERPW) $(curl_dbg)
|
||||
|
||||
app-test_pop3s: app-hostaddr
|
||||
curl pop3s://$(myhost) -k -u $(LDAP_TEST_USER):$(LDAP_TEST_PASSWD) $(curl_dbg)
|
||||
curl pop3s://$(myhost) -k -u $(LDAP_TEST_USER):$(LDAP_TEST_USERPW) $(curl_dbg)
|
||||
|
||||
app-test_ical: app-hostaddr
|
||||
curl http://$(myhost):8080 -u $(LDAP_TEST_USER):$(LDAP_TEST_PASSWD) $(curl_dbg)
|
||||
curl http://$(myhost):8080 -u $(LDAP_TEST_USER):$(LDAP_TEST_USERPW) $(curl_dbg)
|
||||
|
||||
app-test_icals: app-hostaddr
|
||||
curl https://$(myhost):8443 -k -u $(LDAP_TEST_USER):$(LDAP_TEST_PASSWD) $(curl_dbg)
|
||||
curl https://$(myhost):8443 -k -u $(LDAP_TEST_USER):$(LDAP_TEST_USERPW) $(curl_dbg)
|
||||
|
||||
app-test_tls: app-hostaddr
|
||||
docker run --rm -it --network demo_backend drwetter/testssl.sh app:993 || true
|
||||
|
@ -312,7 +382,7 @@ app-create_store:
|
|||
docker-compose exec app kopano-admin --create-store $(LDAP_TEST_USER)
|
||||
|
||||
app-public_store:
|
||||
docker-compose exec app kopano-storeadm -h default: -P
|
||||
docker-compose exec app kopano-storeadm -P
|
||||
|
||||
$(addprefix app-parms_,archiver dagent gateway ical ldap search server spamd spooler):
|
||||
docker-compose exec app run list_parms $(patsubst app-parms_%,%,$@)
|
||||
|
@ -336,7 +406,7 @@ all-destroy_smime:
|
|||
|
||||
%.p12: %.crt
|
||||
openssl pkcs12 -export -in $< -inkey $*.key -out $@ \
|
||||
-passout pass:$(LDAP_TEST_PASSWD)
|
||||
-passout pass:$(LDAP_TEST_USERPW)
|
||||
|
||||
%.csr: %.key
|
||||
openssl req -new -key $< -out $@ \
|
||||
|
|
|
@ -27,6 +27,7 @@ services:
|
|||
- LDAP_GROUP_TYPE_ATTRIBUTE_VALUE=${LDAP_GROUPOBJ-posixGroup}
|
||||
- LDAP_GROUPMEMBERS_ATTRIBUTE_TYPE=dn
|
||||
- LDAP_PROPMAP=
|
||||
- DAGENT_PLUGINS=movetopublicldap.py
|
||||
- MYSQL_DATABASE=${MYSQL_DATABASE-kopano}
|
||||
- MYSQL_USER=${MYSQL_USER-kopano}
|
||||
- MYSQL_PASSWORD=${MYSQL_PASSWORD-secret}
|
||||
|
|
|
@ -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 search server spamd spooler}
|
||||
DOCKER_LDAP_SERVICES=${DOCKER_LDAP_SERVICES-archiver dagent gateway ical ldap movetopublicldap 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
|
||||
|
@ -36,6 +36,7 @@ list_parms() {
|
|||
[ $# -ne 1 ] && echo "# $service"
|
||||
local man_file=$(kopano_gen_filename_man $service)
|
||||
kopano_get_envvars_man $man_file
|
||||
kopano_get_envvars_ext $service
|
||||
[ $# -ne 1 ] && echo
|
||||
done
|
||||
}
|
||||
|
@ -48,6 +49,7 @@ list_parms() {
|
|||
# With kopano-webapp and z-push use installed config file to find valid keys.
|
||||
#
|
||||
kopano_apply_envvars_core() {
|
||||
kopano_enable_envvars_plugin
|
||||
for service in $DOCKER_LDAP_SERVICES; do
|
||||
kopano_apply_envvars_cfg $service
|
||||
done
|
||||
|
@ -71,17 +73,18 @@ kopano_apply_envvars_cfg() {
|
|||
local service=$1
|
||||
local cfg_file=$(kopano_gen_filename_cfg $service)
|
||||
local man_file=$(kopano_gen_filename_man $service)
|
||||
local env_vars="$(kopano_get_envvars_ext $service)"
|
||||
if [ -f $man_file ]; then
|
||||
local env_vars="$(kopano_get_envvars_man $man_file)"
|
||||
if [ -e $cfg_file ]; then
|
||||
mv -f $cfg_file $cfg_file.bak
|
||||
fi
|
||||
for env_var in $env_vars; do
|
||||
kopano_set_envvars_cfg $service $env_var
|
||||
done
|
||||
env_vars="$(kopano_get_envvars_man $man_file) $env_vars"
|
||||
else
|
||||
dc_log 4 "Could not find $man_file"
|
||||
dc_log 6 "Could not find $man_file"
|
||||
fi
|
||||
if [ -e $cfg_file ]; then
|
||||
mv -f $cfg_file $cfg_file.bak
|
||||
fi
|
||||
for env_var in $env_vars; do
|
||||
kopano_set_envvars_cfg $service $env_var
|
||||
done
|
||||
}
|
||||
|
||||
kopano_apply_envvars_php() {
|
||||
|
@ -118,6 +121,35 @@ kopano_set_envvars_cfg() {
|
|||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# External parameters
|
||||
# services without a man file or additional parameters are listed here.
|
||||
#
|
||||
kopano_get_envvars_ext() {
|
||||
local service=$1
|
||||
local vars
|
||||
case $service in
|
||||
movetopublicldap)
|
||||
vars="ldap_public_store_attribute ldap_public_store_attribute_token"
|
||||
;;
|
||||
esac
|
||||
echo $vars | tr "[:lower:]" "[:upper:]" | tr " " "\n" | sort -u
|
||||
}
|
||||
|
||||
#
|
||||
# Python plugins
|
||||
#
|
||||
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
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
#
|
||||
# Helpers
|
||||
#
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
""" movetopublicldap.py
|
||||
|
||||
This is an LDAP lookup extension to the move to public plugin.
|
||||
|
||||
The move to public plugin moves incoming messages to a folder in the public
|
||||
store. If folders are missing they will be created.
|
||||
|
||||
A LDAP entry including:
|
||||
|
||||
kopanoAccount: 1
|
||||
kopanoResourceType: publicStore:<public folder>
|
||||
|
||||
will have its email delivered to the public store in <public folder>.
|
||||
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
|
||||
be delivered to 'Public Folders/Public Stores/public'.
|
||||
|
||||
The parameters in /etc/kopano/ldap.cfg will be used for the LDAP query.
|
||||
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
|
||||
|
||||
"""
|
||||
from sys import hexversion
|
||||
from MAPI.Util import GetPublicStore
|
||||
from MAPI.Struct import NEWMAIL_NOTIFICATION
|
||||
from MAPI import MAPI_UNICODE, MAPI_MODIFY, OPEN_IF_EXISTS, MDB_WRITE
|
||||
from MAPI.Tags import (PR_RECEIVED_BY_EMAIL_ADDRESS_W, PR_EC_COMPANY_NAME_W,
|
||||
PR_IPM_PUBLIC_FOLDERS_ENTRYID, PR_ENTRYID, PR_MAILBOX_OWNER_ENTRYID,
|
||||
IID_IMessage, IID_IExchangeManageStore)
|
||||
from plugintemplates import IMapiDAgentPlugin, MP_CONTINUE, MP_STOP_SUCCESS
|
||||
from zconfig import ZConfigParser
|
||||
import configparser
|
||||
import ldap
|
||||
|
||||
class KConfigParser(ZConfigParser):
|
||||
""" Extends zconfig.ZConfigParser to also allow !directive in cfg files """
|
||||
def __init__(self, configfile, defaultconfig={}):
|
||||
self.config = configparser.ConfigParser(defaults=defaultconfig,
|
||||
delimiters=('='), comment_prefixes=('#', '!'))
|
||||
self.readZConfig(configfile)
|
||||
|
||||
class MoveToPublic(IMapiDAgentPlugin):
|
||||
|
||||
prioPreDelivery = 50
|
||||
|
||||
config = {}
|
||||
|
||||
CONFIGFILES = ['/etc/kopano/ldap.cfg', '/etc/kopano/movetopublicldap.cfg']
|
||||
|
||||
DEFAULTCONFIG = {
|
||||
'ldap_uri': None,
|
||||
'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"
|
||||
}
|
||||
|
||||
def __init__(self, logger):
|
||||
IMapiDAgentPlugin.__init__(self, logger)
|
||||
self.readconfig(self.CONFIGFILES, self.DEFAULTCONFIG)
|
||||
|
||||
def readconfig(self, configfiles=CONFIGFILES, defaultconfig=DEFAULTCONFIG):
|
||||
""" Reads ldap.cfg and movetopublicldap.cfg into self.config """
|
||||
options = [opt.split('_', 1)[1] for opt in defaultconfig.keys()]
|
||||
config = None
|
||||
for configfile in configfiles:
|
||||
if not config:
|
||||
config = KConfigParser(configfile, defaultconfig)
|
||||
else:
|
||||
config = KConfigParser(configfile, config.options())
|
||||
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:*)) """
|
||||
return ("(&({}={})({}={}:*))"
|
||||
.format(self.config['user_unique_attribute'],
|
||||
recipient,
|
||||
self.config['public_store_attribute'],
|
||||
self.config['public_store_attribute_token']))
|
||||
|
||||
def searchquery(self, recipient):
|
||||
""" Query a LDAP/AD driectory server to lookup recipient using
|
||||
search_base and return public_store_attribute
|
||||
"""
|
||||
if (self.config['uri'] is None):
|
||||
self.logger.logError(("!--- ldap_uri is not defined."
|
||||
" Please check {}" .format(self.CONFIGFILES[0])))
|
||||
return None
|
||||
else:
|
||||
l = ldap.initialize(self.config['uri'])
|
||||
try:
|
||||
l.protocol_version = ldap.VERSION3
|
||||
l.simple_bind_s(self.config['bind_user'] or u'', \
|
||||
self.config['bind_passwd'] or u'')
|
||||
except ldap.SERVER_DOWN as e:
|
||||
self.logger.logError(("!--- LDAP server is not reachable {}"
|
||||
.format(e)))
|
||||
return None
|
||||
except ldap.INVALID_CREDENTIALS as e:
|
||||
self.logger.logError(("!--- Invalid LDAP credentials {}"
|
||||
" Please check {}" .format(e, self.CONFIGFILES[0])))
|
||||
l.unbind_s()
|
||||
return None
|
||||
except ldap.LDAPError as e:
|
||||
self.logger.logError("!--- LDAPError {}".format(e))
|
||||
l.unbind_s()
|
||||
return None
|
||||
try:
|
||||
result = l.search_s(self.config['search_base'], \
|
||||
ldap.SCOPE_SUBTREE, self.searchfilter(recipient), \
|
||||
[self.config['public_store_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 """
|
||||
destination_folder = []
|
||||
result = self.searchquery(recipient)
|
||||
if result:
|
||||
tokenandfolder = (result[0][1]
|
||||
.get(self.config['public_store_attribute'])[0].decode('utf-8'))
|
||||
if tokenandfolder:
|
||||
destination_folder = tokenandfolder.split(':')[1]
|
||||
if destination_folder:
|
||||
self.logger.logDebug(("*--- Found public folder {}"
|
||||
"for recipient {}".format(
|
||||
destination_folder.encode('utf-8'),
|
||||
recipient.encode('utf-8'))))
|
||||
return destination_folder
|
||||
|
||||
def PreDelivery(self, session, addrbook, store, folder, message):
|
||||
|
||||
props = message.GetProps([PR_RECEIVED_BY_EMAIL_ADDRESS_W], 0)
|
||||
if props[0].ulPropTag != PR_RECEIVED_BY_EMAIL_ADDRESS_W:
|
||||
self.logger.logError("!--- Not received by emailaddress")
|
||||
return MP_CONTINUE,
|
||||
|
||||
recipient = props[0].Value.lower()
|
||||
if not recipient:
|
||||
self.logger.logError("!--- No recipient in props {}".format(props))
|
||||
return MP_CONTINUE,
|
||||
|
||||
recipfolder = self.publicfolder(recipient)
|
||||
if not recipfolder:
|
||||
self.logger.logDebug(("*--- No public folder for recipient {}"
|
||||
.format(recipient.encode('utf-8'))))
|
||||
return MP_CONTINUE,
|
||||
|
||||
publicstore = GetPublicStore(session)
|
||||
if not publicstore:
|
||||
storeprops = store.GetProps([PR_MAILBOX_OWNER_ENTRYID], 0)
|
||||
if storeprops[0].ulPropTag == PR_MAILBOX_OWNER_ENTRYID:
|
||||
user = addrbook.OpenEntry(storeprops[0].Value, None, 0)
|
||||
userprops = user.GetProps([PR_EC_COMPANY_NAME_W], 0)
|
||||
if userprops[0].ulPropTag == PR_EC_COMPANY_NAME_W:
|
||||
companyname = userprops[0].Value
|
||||
else:
|
||||
companyname = None
|
||||
|
||||
if not companyname:
|
||||
self.logger.logError(("!--- Cannot open a public store."
|
||||
' Use "kopano-storeadm -P"'
|
||||
" to create one if it is missing."))
|
||||
return MP_CONTINUE,
|
||||
|
||||
ema = store.QueryInterface(IID_IExchangeManageStore)
|
||||
publicstoreid = ema.CreateStoreEntryID(None, companyname, MAPI_UNICODE)
|
||||
publicstore = session.OpenMsgStore(0, publicstoreid, None, MDB_WRITE)
|
||||
|
||||
publicfolders = publicstore.OpenEntry(
|
||||
publicstore.GetProps([PR_IPM_PUBLIC_FOLDERS_ENTRYID], 0)[0].Value,
|
||||
None, MAPI_MODIFY)
|
||||
|
||||
folderlist = recipfolder.split('/')
|
||||
folder = publicfolders
|
||||
for foldername in folderlist:
|
||||
if len(foldername) > 0:
|
||||
if hexversion >= 0x03000000:
|
||||
folder = folder.CreateFolder(0, foldername,
|
||||
"Create by Move to Public plugin", None,
|
||||
OPEN_IF_EXISTS | MAPI_UNICODE)
|
||||
else:
|
||||
folder = folder.CreateFolder(0, foldername,
|
||||
"Create by Move to Public plugin", None, OPEN_IF_EXISTS)
|
||||
|
||||
msgnew = folder.CreateMessage(None, 0)
|
||||
tags = message.GetPropList(MAPI_UNICODE)
|
||||
message.CopyProps(tags, 0, None, IID_IMessage, msgnew, 0)
|
||||
|
||||
msgnew.SaveChanges(0)
|
||||
folderid = folder.GetProps([PR_ENTRYID], 0)[0].Value
|
||||
msgid = msgnew.GetProps([PR_ENTRYID], 0)[0].Value
|
||||
|
||||
publicstore.NotifyNewMail(NEWMAIL_NOTIFICATION(msgid, folderid, 0, None, 0))
|
||||
|
||||
self.logger.logInfo(("*--- Message moved to public folder {}"
|
||||
.format(recipfolder)))
|
||||
|
||||
return MP_STOP_SUCCESS,
|
|
@ -0,0 +1,97 @@
|
|||
"""
|
||||
This code will query a LDAP/AD driectory server and updated the cgf file
|
||||
/etc/kopano/movetopublic.cfg
|
||||
/usr/share/kopano-dagent/python/plugins/movetopublic.cfg
|
||||
|
||||
export PYTHONPATH=/usr/share/kopano-dagent/python
|
||||
|
||||
"""
|
||||
from zconfig import ZConfigParser
|
||||
import configparser
|
||||
import ldap
|
||||
|
||||
class KConfigParser(ZConfigParser):
|
||||
""" allow !directive in cfg files """
|
||||
def __init__(self, configfile, defaultconfig={}):
|
||||
self.config = configparser.ConfigParser(defaults=defaultconfig, \
|
||||
delimiters=('='), comment_prefixes=('#', '!'))
|
||||
self.readZConfig(configfile)
|
||||
|
||||
class ldapstores():
|
||||
defaultconfig = {
|
||||
'ldap_uri': None,
|
||||
'ldap_search_base': None,
|
||||
'ldap_bind_user': None,
|
||||
'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"
|
||||
}
|
||||
|
||||
def __init__(self, configfile = '/etc/kopano/ldap.cfg'):
|
||||
self.readconfig(configfile)
|
||||
|
||||
def readconfig(self, configfile):
|
||||
config = KConfigParser(configfile, self.defaultconfig)
|
||||
options = [opt.split('_', 1)[1] for opt in self.defaultconfig.keys()]
|
||||
self.config = config.getdict('ldap',options)
|
||||
return self.config
|
||||
|
||||
def searchquery(self):
|
||||
if (self.config['uri'] is None):
|
||||
print ("ldap_uri is None")
|
||||
sys.exit(0)
|
||||
else:
|
||||
l = ldap.initialize(self.config['uri'])
|
||||
try:
|
||||
l.protocol_version = ldap.VERSION3
|
||||
l.simple_bind_s(self.config['bind_user'] or u'', \
|
||||
self.config['bind_passwd'] or u'')
|
||||
except ldap.INVALID_CREDENTIALS:
|
||||
sys.exit(0)
|
||||
except ldap.LDAPError as e:
|
||||
print (e)
|
||||
sys.exit(0)
|
||||
try:
|
||||
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']])
|
||||
results = []
|
||||
while 1:
|
||||
result_type, result_data = l.result(ldap_result_id, 0)
|
||||
if (result_data == []):
|
||||
break
|
||||
else:
|
||||
if result_type == ldap.RES_SEARCH_ENTRY:
|
||||
results.append(result_data[0])
|
||||
except ldap.LDAPError as e:
|
||||
print (e)
|
||||
l.unbind_s()
|
||||
return results
|
||||
|
||||
def findpublic(self):
|
||||
stores = self.searchquery()
|
||||
public = []
|
||||
for store in stores:
|
||||
recipient = store[1].get(self.config['user_unique_attribute'])
|
||||
tokenandfolder = store[1].get(self.config['ldap_public_store_attribute'])
|
||||
if tokenandfolder:
|
||||
token = tokenandfolder.split(':')[0]
|
||||
destination_folder = tokenandfolder.split(':')[1]
|
||||
if (token == self.config['ldap_public_store_attribute_token']);
|
||||
public[recipient] = destination_folder
|
||||
return public
|
||||
|
||||
def printpublic(self, outputfile = '/etc/kopano/movetopublic.cfg'):
|
||||
public = self.findpublic()
|
||||
i = 1
|
||||
for recipient in public.keys():
|
||||
print ("rule%d_recipient = %s", i, recipient)
|
||||
print ("rule%d_destination_folder = %s", i, public[recipient])
|
||||
i += 1
|
||||
|
||||
s = ldapstores()
|
||||
s.printpublic()
|
Loading…
Reference in New Issue